Message ID | 20220817203006.21769-3-gnoack3000@gmail.com (mailing list archive) |
---|---|
State | Handled Elsewhere |
Headers | show |
Series | landlock: truncate support | expand |
On 17/08/2022 22:30, Günther Noack wrote: > These tests exercise the following truncation operations: > > * truncate() (truncate by path) > * ftruncate() (truncate by file descriptor) > * open with the O_TRUNC flag > * special case: creat(), which is open with O_CREAT|O_WRONLY|O_TRUNC. > > in the following scenarios: > > * Files with read, write and truncate rights. > * Files with read and truncate rights. > * Files with the truncate right. > * Files without the truncate right. > > In particular, the following scenarios are enforced with the test: > > * ftruncate() requires the truncate right, > even when the thread already has the right to write to the file. > * open() with O_TRUNC requires the truncate right, if it truncates a file. > open() already checks security_path_truncate() in this case, > and it required no additional check in the Landlock LSM's file_open hook. > * creat() requires the truncate right > when called with an existing filename. > * creat() does *not* require the truncate right > when it's creating a new file. > > Signed-off-by: Günther Noack <gnoack3000@gmail.com> > --- > tools/testing/selftests/landlock/fs_test.c | 250 +++++++++++++++++++++ > 1 file changed, 250 insertions(+) > > diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c > index cb77eaa01c91..010d4c59139e 100644 > --- a/tools/testing/selftests/landlock/fs_test.c > +++ b/tools/testing/selftests/landlock/fs_test.c > @@ -58,6 +58,7 @@ static const char file1_s2d3[] = TMP_DIR "/s2d1/s2d2/s2d3/f1"; > static const char file2_s2d3[] = TMP_DIR "/s2d1/s2d2/s2d3/f2"; > > static const char dir_s3d1[] = TMP_DIR "/s3d1"; > +static const char file1_s3d1[] = TMP_DIR "/s3d1/f1"; > /* dir_s3d2 is a mount point. */ > static const char dir_s3d2[] = TMP_DIR "/s3d1/s3d2"; > static const char dir_s3d3[] = TMP_DIR "/s3d1/s3d2/s3d3"; > @@ -83,6 +84,7 @@ static const char dir_s3d3[] = TMP_DIR "/s3d1/s3d2/s3d3"; > * │ ├── f1 > * │ └── f2 > * └── s3d1 > + * ├── f1 > * └── s3d2 > * └── s3d3 > */ > @@ -208,6 +210,7 @@ static void create_layout1(struct __test_metadata *const _metadata) > create_file(_metadata, file1_s2d3); > create_file(_metadata, file2_s2d3); > > + create_file(_metadata, file1_s3d1); > create_directory(_metadata, dir_s3d2); > set_cap(_metadata, CAP_SYS_ADMIN); > ASSERT_EQ(0, mount("tmp", dir_s3d2, "tmpfs", 0, "size=4m,mode=700")); > @@ -230,6 +233,7 @@ static void remove_layout1(struct __test_metadata *const _metadata) > EXPECT_EQ(0, remove_path(file1_s2d2)); > EXPECT_EQ(0, remove_path(file1_s2d1)); > > + EXPECT_EQ(0, remove_path(file1_s3d1)); > EXPECT_EQ(0, remove_path(dir_s3d3)); > set_cap(_metadata, CAP_SYS_ADMIN); > umount(dir_s3d2); > @@ -3023,6 +3027,252 @@ TEST_F_FORK(layout1, proc_pipe) > ASSERT_EQ(0, close(pipe_fds[1])); > } > > +/* Invokes truncate(2) and returns its errno or 0. */ > +static int test_truncate(const char *const path) > +{ > + if (truncate(path, 10) < 0) > + return errno; > + return 0; > +} > + > +/* Invokes ftruncate(2) and returns its errno or 0. */ > +static int test_ftruncate(int fd) > +{ > + if (ftruncate(fd, 10) < 0) > + return errno; > + return 0; > +} > + > +/* > + * Invokes creat(2) and returns its errno or 0. > + * Closes the opened file descriptor on success. > + */ > +static int test_creat(const char *const path, mode_t mode) This "mode" argument is always 0600. If it's OK with you, I hard code this mode and push this series to -next with some small cosmetic fixes. > +{ > + int fd = creat(path, mode); > + > + if (fd < 0) > + return errno; > + > + /* > + * Mixing error codes from close(2) and creat(2) should not lead to any > + * (access type) confusion for this test. > + */ > + if (close(fd) < 0) > + return errno; > + return 0; > +} > + > +/* > + * Exercises file truncation when it's not restricted, > + * as it was the case before LANDLOCK_ACCESS_FS_TRUNCATE existed. > + */ > +TEST_F_FORK(layout1, truncate_unhandled) > +{ > + const char *const file_r = file1_s1d1; > + const char *const file_w = file2_s1d1; > + const char *const file_none = file1_s1d2; > + int file_r_fd, file_w_fd, file_none_fd; > + const struct rule rules[] = { > + { > + .path = file_r, > + .access = LANDLOCK_ACCESS_FS_READ_FILE, > + }, > + { > + .path = file_w, > + .access = LANDLOCK_ACCESS_FS_WRITE_FILE, > + }, > + /* Implicitly: No rights for file_none. */ > + {}, > + }; > + > + const __u64 handled = LANDLOCK_ACCESS_FS_READ_FILE | > + LANDLOCK_ACCESS_FS_WRITE_FILE; > + int ruleset_fd; > + > + /* > + * Open some writable file descriptors before enabling Landlock, so that > + * we can test ftruncate() without making open() a prerequisite. > + */ > + file_r_fd = open(file_r, O_WRONLY | O_CLOEXEC); > + ASSERT_LE(0, file_r_fd); > + file_w_fd = open(file_w, O_WRONLY | O_CLOEXEC); > + ASSERT_LE(0, file_w_fd); > + file_none_fd = open(file_none, O_WRONLY | O_CLOEXEC); > + ASSERT_LE(0, file_none_fd); > + > + /* Enable Landlock. */ > + ruleset_fd = create_ruleset(_metadata, handled, rules); > + > + ASSERT_LE(0, ruleset_fd); > + enforce_ruleset(_metadata, ruleset_fd); > + ASSERT_EQ(0, close(ruleset_fd)); > + > + /* > + * Checks read right: truncate, ftruncate and open with O_TRUNC work, > + * unless the file is attempted to be opened for writing. > + */ > + EXPECT_EQ(0, test_truncate(file_r)); > + EXPECT_EQ(0, test_ftruncate(file_r_fd)); > + EXPECT_EQ(0, test_open(file_r, O_RDONLY | O_TRUNC)); > + EXPECT_EQ(EACCES, test_open(file_r, O_WRONLY | O_TRUNC)); > + EXPECT_EQ(EACCES, test_creat(file_r, 0600)); > + > + /* > + * Checks write right: truncate, ftruncate and open with O_TRUNC work, > + * unless the file is attempted to be opened for reading. > + */ > + EXPECT_EQ(0, test_truncate(file_w)); > + EXPECT_EQ(0, test_ftruncate(file_w_fd)); > + EXPECT_EQ(EACCES, test_open(file_w, O_RDONLY | O_TRUNC)); > + EXPECT_EQ(0, test_open(file_w, O_WRONLY | O_TRUNC)); > + EXPECT_EQ(0, test_creat(file_w, 0600)); > + > + /* > + * Checks "no rights" case: truncate and ftruncate work but all open > + * attempts fail, including creat. > + */ > + EXPECT_EQ(0, test_truncate(file_none)); > + EXPECT_EQ(0, test_ftruncate(file_none_fd)); > + EXPECT_EQ(EACCES, test_open(file_none, O_RDONLY | O_TRUNC)); > + EXPECT_EQ(EACCES, test_open(file_none, O_WRONLY | O_TRUNC)); > + EXPECT_EQ(EACCES, test_creat(file_none, 0600)); > + > + ASSERT_EQ(0, close(file_r_fd)); > + ASSERT_EQ(0, close(file_w_fd)); > + ASSERT_EQ(0, close(file_none_fd)); > +} > + > +TEST_F_FORK(layout1, truncate) > +{ > + const char *const file_rwt = file1_s1d1; > + const char *const file_rw = file2_s1d1; > + const char *const file_rt = file1_s1d2; > + const char *const file_t = file2_s1d2; > + const char *const file_none = file1_s1d3; > + const char *const dir_t = dir_s2d1; > + const char *const file_in_dir_t = file1_s2d1; > + const char *const dir_w = dir_s3d1; > + const char *const file_in_dir_w = file1_s3d1; > + int file_rwt_fd, file_rw_fd, file_rt_fd, file_t_fd, file_none_fd; > + int file_in_dir_t_fd, file_in_dir_w_fd; > + const struct rule rules[] = { > + { > + .path = file_rwt, > + .access = LANDLOCK_ACCESS_FS_READ_FILE | > + LANDLOCK_ACCESS_FS_WRITE_FILE | > + LANDLOCK_ACCESS_FS_TRUNCATE, > + }, > + { > + .path = file_rw, > + .access = LANDLOCK_ACCESS_FS_READ_FILE | > + LANDLOCK_ACCESS_FS_WRITE_FILE, > + }, > + { > + .path = file_rt, > + .access = LANDLOCK_ACCESS_FS_READ_FILE | > + LANDLOCK_ACCESS_FS_TRUNCATE, > + }, > + { > + .path = file_t, > + .access = LANDLOCK_ACCESS_FS_TRUNCATE, > + }, > + /* Implicitly: No access rights for file_none. */ > + { > + .path = dir_t, > + .access = LANDLOCK_ACCESS_FS_TRUNCATE, > + }, > + { > + .path = dir_w, > + .access = LANDLOCK_ACCESS_FS_WRITE_FILE, > + }, > + {}, > + }; > + const __u64 handled = LANDLOCK_ACCESS_FS_READ_FILE | > + LANDLOCK_ACCESS_FS_WRITE_FILE | > + LANDLOCK_ACCESS_FS_TRUNCATE; > + int ruleset_fd; > + > + /* > + * Open some writable file descriptors before enabling Landlock, so that > + * we can test ftruncate() without making open() a prerequisite. > + */ > + file_rwt_fd = open(file_rwt, O_WRONLY | O_CLOEXEC); > + ASSERT_LE(0, file_rwt_fd); > + file_rw_fd = open(file_rw, O_WRONLY | O_CLOEXEC); > + ASSERT_LE(0, file_rw_fd); > + file_rt_fd = open(file_rt, O_WRONLY | O_CLOEXEC); > + ASSERT_LE(0, file_rt_fd); > + file_t_fd = open(file_t, O_WRONLY | O_CLOEXEC); > + ASSERT_LE(0, file_t_fd); > + file_none_fd = open(file_none, O_WRONLY | O_CLOEXEC); > + ASSERT_LE(0, file_none_fd); > + file_in_dir_t_fd = open(file_in_dir_t, O_WRONLY | O_CLOEXEC); > + ASSERT_LE(0, file_in_dir_t_fd); > + file_in_dir_w_fd = open(file_in_dir_w, O_WRONLY | O_CLOEXEC); > + ASSERT_LE(0, file_in_dir_w_fd); > + > + /* Enable Landlock. */ > + ruleset_fd = create_ruleset(_metadata, handled, rules); > + > + ASSERT_LE(0, ruleset_fd); > + enforce_ruleset(_metadata, ruleset_fd); > + ASSERT_EQ(0, close(ruleset_fd)); > + > + /* Checks read, write and truncate rights: truncation works. */ > + EXPECT_EQ(0, test_truncate(file_rwt)); > + EXPECT_EQ(0, test_ftruncate(file_rwt_fd)); > + EXPECT_EQ(0, test_open(file_rwt, O_RDONLY | O_TRUNC)); > + EXPECT_EQ(0, test_open(file_rwt, O_WRONLY | O_TRUNC)); > + > + /* Checks read and write rights: no truncate variant works. */ > + EXPECT_EQ(EACCES, test_truncate(file_rw)); > + EXPECT_EQ(EACCES, test_ftruncate(file_rw_fd)); > + EXPECT_EQ(EACCES, test_open(file_rw, O_RDONLY | O_TRUNC)); > + EXPECT_EQ(EACCES, test_open(file_rw, O_WRONLY | O_TRUNC)); > + > + /* > + * Checks read and truncate rights: truncation works. > + * > + * Note: Files can get truncated using open() even with O_RDONLY. > + */ > + EXPECT_EQ(0, test_truncate(file_rt)); > + EXPECT_EQ(0, test_ftruncate(file_rt_fd)); > + EXPECT_EQ(0, test_open(file_rt, O_RDONLY | O_TRUNC)); > + EXPECT_EQ(EACCES, test_open(file_rt, O_WRONLY | O_TRUNC)); > + > + /* Checks truncate right: truncate works, but can't open file. */ > + EXPECT_EQ(0, test_truncate(file_t)); > + EXPECT_EQ(0, test_ftruncate(file_t_fd)); > + EXPECT_EQ(EACCES, test_open(file_t, O_RDONLY | O_TRUNC)); > + EXPECT_EQ(EACCES, test_open(file_t, O_WRONLY | O_TRUNC)); > + > + /* Checks "no rights" case: No form of truncation works. */ > + EXPECT_EQ(EACCES, test_truncate(file_none)); > + EXPECT_EQ(EACCES, test_ftruncate(file_none_fd)); This test is interesting because it shows that the access control may still restrict opened FD (when it makes sense). The truncate access right is kind of the first one to be testable this way. > + EXPECT_EQ(EACCES, test_open(file_none, O_RDONLY | O_TRUNC)); > + EXPECT_EQ(EACCES, test_open(file_none, O_WRONLY | O_TRUNC)); > + > + /* > + * Checks truncate right on directory: truncate works on contained > + * files. > + */ > + EXPECT_EQ(0, test_truncate(file_in_dir_t)); > + EXPECT_EQ(0, test_ftruncate(file_in_dir_t_fd)); > + EXPECT_EQ(EACCES, test_open(file_in_dir_t, O_RDONLY | O_TRUNC)); > + EXPECT_EQ(EACCES, test_open(file_in_dir_t, O_WRONLY | O_TRUNC)); > + > + /* > + * Checks creat in dir_w: This requires the truncate right when > + * overwriting an existing file, but does not require it when the file > + * is new. > + */ > + EXPECT_EQ(EACCES, test_creat(file_in_dir_w, 0600)); > + > + ASSERT_EQ(0, unlink(file_in_dir_w)); > + EXPECT_EQ(0, test_creat(file_in_dir_w, 0600)); > +} > + > /* clang-format off */ > FIXTURE(layout1_bind) {}; > /* clang-format on */
On Thu, Aug 18, 2022 at 10:39:27PM +0200, Mickaël Salaün wrote: > On 17/08/2022 22:30, Günther Noack wrote: > > +/* > > + * Invokes creat(2) and returns its errno or 0. > > + * Closes the opened file descriptor on success. > > + */ > > +static int test_creat(const char *const path, mode_t mode) > > This "mode" argument is always 0600. If it's OK with you, I hard code this > mode and push this series to -next with some small cosmetic fixes. Yes, absolutely. Please do these fixes and push it to -next. :) Thanks, —Günther --
Ok, it should be in -next soon. Thanks for your contribution! Would you like to write a syzkaller test to cover this new access right? You only need to update the landlock_fs_accesses file with a call to truncate() returning EACCES and check that it covers hook_path_truncate(). You can take inspiration from this PR: https://github.com/google/syzkaller/pull/3133 Please CC me, I can help. Regards, Mickaël On 19/08/2022 07:24, Günther Noack wrote: > On Thu, Aug 18, 2022 at 10:39:27PM +0200, Mickaël Salaün wrote: >> On 17/08/2022 22:30, Günther Noack wrote: >>> +/* >>> + * Invokes creat(2) and returns its errno or 0. >>> + * Closes the opened file descriptor on success. >>> + */ >>> +static int test_creat(const char *const path, mode_t mode) >> >> This "mode" argument is always 0600. If it's OK with you, I hard code this >> mode and push this series to -next with some small cosmetic fixes. > > Yes, absolutely. Please do these fixes and push it to -next. :) > > Thanks, > —Günther > > --
FYI, my -next branch is here: https://git.kernel.org/pub/scm/linux/kernel/git/mic/linux.git/log/?h=next Günther, let me know if everything is OK. Konstantin, please rebase your work on it. It should mainly conflict with changes related to the Landlock ABI version. On 19/08/2022 10:15, Mickaël Salaün wrote: > Ok, it should be in -next soon. Thanks for your contribution! > > Would you like to write a syzkaller test to cover this new access right? > You only need to update the landlock_fs_accesses file with a call to > truncate() returning EACCES and check that it covers > hook_path_truncate(). You can take inspiration from this PR: > https://github.com/google/syzkaller/pull/3133 > Please CC me, I can help. > > Regards, > Mickaël > > > On 19/08/2022 07:24, Günther Noack wrote: >> On Thu, Aug 18, 2022 at 10:39:27PM +0200, Mickaël Salaün wrote: >>> On 17/08/2022 22:30, Günther Noack wrote: >>>> +/* >>>> + * Invokes creat(2) and returns its errno or 0. >>>> + * Closes the opened file descriptor on success. >>>> + */ >>>> +static int test_creat(const char *const path, mode_t mode) >>> >>> This "mode" argument is always 0600. If it's OK with you, I hard code this >>> mode and push this series to -next with some small cosmetic fixes. >> >> Yes, absolutely. Please do these fixes and push it to -next. :) >> >> Thanks, >> —Günther >> >> --
8/19/2022 11:36 AM, Mickaël Salaün пишет: > FYI, my -next branch is here: > https://git.kernel.org/pub/scm/linux/kernel/git/mic/linux.git/log/?h=next > > Günther, let me know if everything is OK. > > Konstantin, please rebase your work on it. It should mainly conflict > with changes related to the Landlock ABI version. Ok. I will rebase. Thnaks. Do I need to keep my next versions on your -next branch? > > > On 19/08/2022 10:15, Mickaël Salaün wrote: >> Ok, it should be in -next soon. Thanks for your contribution! >> >> Would you like to write a syzkaller test to cover this new access right? >> You only need to update the landlock_fs_accesses file with a call to >> truncate() returning EACCES and check that it covers >> hook_path_truncate(). You can take inspiration from this PR: >> https://github.com/google/syzkaller/pull/3133 >> Please CC me, I can help. >> >> Regards, >> Mickaël >> >> >> On 19/08/2022 07:24, Günther Noack wrote: >>> On Thu, Aug 18, 2022 at 10:39:27PM +0200, Mickaël Salaün wrote: >>>> On 17/08/2022 22:30, Günther Noack wrote: >>>>> +/* >>>>> + * Invokes creat(2) and returns its errno or 0. >>>>> + * Closes the opened file descriptor on success. >>>>> + */ >>>>> +static int test_creat(const char *const path, mode_t mode) >>>> >>>> This "mode" argument is always 0600. If it's OK with you, I hard code this >>>> mode and push this series to -next with some small cosmetic fixes. >>> >>> Yes, absolutely. Please do these fixes and push it to -next. :) >>> >>> Thanks, >>> —Günther >>> >>> -- > .
On 19/08/2022 10:55, Konstantin Meskhidze (A) wrote: > > > 8/19/2022 11:36 AM, Mickaël Salaün пишет: >> FYI, my -next branch is here: >> https://git.kernel.org/pub/scm/linux/kernel/git/mic/linux.git/log/?h=next >> >> Günther, let me know if everything is OK. >> >> Konstantin, please rebase your work on it. It should mainly conflict >> with changes related to the Landlock ABI version. > > Ok. I will rebase. Thnaks. Do I need to keep my next versions on your > -next branch? Yes please.
On Fri, Aug 19, 2022 at 10:36:15AM +0200, Mickaël Salaün wrote: > FYI, my -next branch is here: > https://git.kernel.org/pub/scm/linux/kernel/git/mic/linux.git/log/?h=next > > Günther, let me know if everything is OK. Thanks, looks good. Good idea to also fix the date in the documentation, I had overlooked that. > Konstantin, please rebase your work on it. It should mainly conflict with > changes related to the Landlock ABI version. +1, I believe we have both increased the ABI version to 3 in the two patch sets. --
diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c index cb77eaa01c91..010d4c59139e 100644 --- a/tools/testing/selftests/landlock/fs_test.c +++ b/tools/testing/selftests/landlock/fs_test.c @@ -58,6 +58,7 @@ static const char file1_s2d3[] = TMP_DIR "/s2d1/s2d2/s2d3/f1"; static const char file2_s2d3[] = TMP_DIR "/s2d1/s2d2/s2d3/f2"; static const char dir_s3d1[] = TMP_DIR "/s3d1"; +static const char file1_s3d1[] = TMP_DIR "/s3d1/f1"; /* dir_s3d2 is a mount point. */ static const char dir_s3d2[] = TMP_DIR "/s3d1/s3d2"; static const char dir_s3d3[] = TMP_DIR "/s3d1/s3d2/s3d3"; @@ -83,6 +84,7 @@ static const char dir_s3d3[] = TMP_DIR "/s3d1/s3d2/s3d3"; * │ ├── f1 * │ └── f2 * └── s3d1 + * ├── f1 * └── s3d2 * └── s3d3 */ @@ -208,6 +210,7 @@ static void create_layout1(struct __test_metadata *const _metadata) create_file(_metadata, file1_s2d3); create_file(_metadata, file2_s2d3); + create_file(_metadata, file1_s3d1); create_directory(_metadata, dir_s3d2); set_cap(_metadata, CAP_SYS_ADMIN); ASSERT_EQ(0, mount("tmp", dir_s3d2, "tmpfs", 0, "size=4m,mode=700")); @@ -230,6 +233,7 @@ static void remove_layout1(struct __test_metadata *const _metadata) EXPECT_EQ(0, remove_path(file1_s2d2)); EXPECT_EQ(0, remove_path(file1_s2d1)); + EXPECT_EQ(0, remove_path(file1_s3d1)); EXPECT_EQ(0, remove_path(dir_s3d3)); set_cap(_metadata, CAP_SYS_ADMIN); umount(dir_s3d2); @@ -3023,6 +3027,252 @@ TEST_F_FORK(layout1, proc_pipe) ASSERT_EQ(0, close(pipe_fds[1])); } +/* Invokes truncate(2) and returns its errno or 0. */ +static int test_truncate(const char *const path) +{ + if (truncate(path, 10) < 0) + return errno; + return 0; +} + +/* Invokes ftruncate(2) and returns its errno or 0. */ +static int test_ftruncate(int fd) +{ + if (ftruncate(fd, 10) < 0) + return errno; + return 0; +} + +/* + * Invokes creat(2) and returns its errno or 0. + * Closes the opened file descriptor on success. + */ +static int test_creat(const char *const path, mode_t mode) +{ + int fd = creat(path, mode); + + if (fd < 0) + return errno; + + /* + * Mixing error codes from close(2) and creat(2) should not lead to any + * (access type) confusion for this test. + */ + if (close(fd) < 0) + return errno; + return 0; +} + +/* + * Exercises file truncation when it's not restricted, + * as it was the case before LANDLOCK_ACCESS_FS_TRUNCATE existed. + */ +TEST_F_FORK(layout1, truncate_unhandled) +{ + const char *const file_r = file1_s1d1; + const char *const file_w = file2_s1d1; + const char *const file_none = file1_s1d2; + int file_r_fd, file_w_fd, file_none_fd; + const struct rule rules[] = { + { + .path = file_r, + .access = LANDLOCK_ACCESS_FS_READ_FILE, + }, + { + .path = file_w, + .access = LANDLOCK_ACCESS_FS_WRITE_FILE, + }, + /* Implicitly: No rights for file_none. */ + {}, + }; + + const __u64 handled = LANDLOCK_ACCESS_FS_READ_FILE | + LANDLOCK_ACCESS_FS_WRITE_FILE; + int ruleset_fd; + + /* + * Open some writable file descriptors before enabling Landlock, so that + * we can test ftruncate() without making open() a prerequisite. + */ + file_r_fd = open(file_r, O_WRONLY | O_CLOEXEC); + ASSERT_LE(0, file_r_fd); + file_w_fd = open(file_w, O_WRONLY | O_CLOEXEC); + ASSERT_LE(0, file_w_fd); + file_none_fd = open(file_none, O_WRONLY | O_CLOEXEC); + ASSERT_LE(0, file_none_fd); + + /* Enable Landlock. */ + ruleset_fd = create_ruleset(_metadata, handled, rules); + + ASSERT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + /* + * Checks read right: truncate, ftruncate and open with O_TRUNC work, + * unless the file is attempted to be opened for writing. + */ + EXPECT_EQ(0, test_truncate(file_r)); + EXPECT_EQ(0, test_ftruncate(file_r_fd)); + EXPECT_EQ(0, test_open(file_r, O_RDONLY | O_TRUNC)); + EXPECT_EQ(EACCES, test_open(file_r, O_WRONLY | O_TRUNC)); + EXPECT_EQ(EACCES, test_creat(file_r, 0600)); + + /* + * Checks write right: truncate, ftruncate and open with O_TRUNC work, + * unless the file is attempted to be opened for reading. + */ + EXPECT_EQ(0, test_truncate(file_w)); + EXPECT_EQ(0, test_ftruncate(file_w_fd)); + EXPECT_EQ(EACCES, test_open(file_w, O_RDONLY | O_TRUNC)); + EXPECT_EQ(0, test_open(file_w, O_WRONLY | O_TRUNC)); + EXPECT_EQ(0, test_creat(file_w, 0600)); + + /* + * Checks "no rights" case: truncate and ftruncate work but all open + * attempts fail, including creat. + */ + EXPECT_EQ(0, test_truncate(file_none)); + EXPECT_EQ(0, test_ftruncate(file_none_fd)); + EXPECT_EQ(EACCES, test_open(file_none, O_RDONLY | O_TRUNC)); + EXPECT_EQ(EACCES, test_open(file_none, O_WRONLY | O_TRUNC)); + EXPECT_EQ(EACCES, test_creat(file_none, 0600)); + + ASSERT_EQ(0, close(file_r_fd)); + ASSERT_EQ(0, close(file_w_fd)); + ASSERT_EQ(0, close(file_none_fd)); +} + +TEST_F_FORK(layout1, truncate) +{ + const char *const file_rwt = file1_s1d1; + const char *const file_rw = file2_s1d1; + const char *const file_rt = file1_s1d2; + const char *const file_t = file2_s1d2; + const char *const file_none = file1_s1d3; + const char *const dir_t = dir_s2d1; + const char *const file_in_dir_t = file1_s2d1; + const char *const dir_w = dir_s3d1; + const char *const file_in_dir_w = file1_s3d1; + int file_rwt_fd, file_rw_fd, file_rt_fd, file_t_fd, file_none_fd; + int file_in_dir_t_fd, file_in_dir_w_fd; + const struct rule rules[] = { + { + .path = file_rwt, + .access = LANDLOCK_ACCESS_FS_READ_FILE | + LANDLOCK_ACCESS_FS_WRITE_FILE | + LANDLOCK_ACCESS_FS_TRUNCATE, + }, + { + .path = file_rw, + .access = LANDLOCK_ACCESS_FS_READ_FILE | + LANDLOCK_ACCESS_FS_WRITE_FILE, + }, + { + .path = file_rt, + .access = LANDLOCK_ACCESS_FS_READ_FILE | + LANDLOCK_ACCESS_FS_TRUNCATE, + }, + { + .path = file_t, + .access = LANDLOCK_ACCESS_FS_TRUNCATE, + }, + /* Implicitly: No access rights for file_none. */ + { + .path = dir_t, + .access = LANDLOCK_ACCESS_FS_TRUNCATE, + }, + { + .path = dir_w, + .access = LANDLOCK_ACCESS_FS_WRITE_FILE, + }, + {}, + }; + const __u64 handled = LANDLOCK_ACCESS_FS_READ_FILE | + LANDLOCK_ACCESS_FS_WRITE_FILE | + LANDLOCK_ACCESS_FS_TRUNCATE; + int ruleset_fd; + + /* + * Open some writable file descriptors before enabling Landlock, so that + * we can test ftruncate() without making open() a prerequisite. + */ + file_rwt_fd = open(file_rwt, O_WRONLY | O_CLOEXEC); + ASSERT_LE(0, file_rwt_fd); + file_rw_fd = open(file_rw, O_WRONLY | O_CLOEXEC); + ASSERT_LE(0, file_rw_fd); + file_rt_fd = open(file_rt, O_WRONLY | O_CLOEXEC); + ASSERT_LE(0, file_rt_fd); + file_t_fd = open(file_t, O_WRONLY | O_CLOEXEC); + ASSERT_LE(0, file_t_fd); + file_none_fd = open(file_none, O_WRONLY | O_CLOEXEC); + ASSERT_LE(0, file_none_fd); + file_in_dir_t_fd = open(file_in_dir_t, O_WRONLY | O_CLOEXEC); + ASSERT_LE(0, file_in_dir_t_fd); + file_in_dir_w_fd = open(file_in_dir_w, O_WRONLY | O_CLOEXEC); + ASSERT_LE(0, file_in_dir_w_fd); + + /* Enable Landlock. */ + ruleset_fd = create_ruleset(_metadata, handled, rules); + + ASSERT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + /* Checks read, write and truncate rights: truncation works. */ + EXPECT_EQ(0, test_truncate(file_rwt)); + EXPECT_EQ(0, test_ftruncate(file_rwt_fd)); + EXPECT_EQ(0, test_open(file_rwt, O_RDONLY | O_TRUNC)); + EXPECT_EQ(0, test_open(file_rwt, O_WRONLY | O_TRUNC)); + + /* Checks read and write rights: no truncate variant works. */ + EXPECT_EQ(EACCES, test_truncate(file_rw)); + EXPECT_EQ(EACCES, test_ftruncate(file_rw_fd)); + EXPECT_EQ(EACCES, test_open(file_rw, O_RDONLY | O_TRUNC)); + EXPECT_EQ(EACCES, test_open(file_rw, O_WRONLY | O_TRUNC)); + + /* + * Checks read and truncate rights: truncation works. + * + * Note: Files can get truncated using open() even with O_RDONLY. + */ + EXPECT_EQ(0, test_truncate(file_rt)); + EXPECT_EQ(0, test_ftruncate(file_rt_fd)); + EXPECT_EQ(0, test_open(file_rt, O_RDONLY | O_TRUNC)); + EXPECT_EQ(EACCES, test_open(file_rt, O_WRONLY | O_TRUNC)); + + /* Checks truncate right: truncate works, but can't open file. */ + EXPECT_EQ(0, test_truncate(file_t)); + EXPECT_EQ(0, test_ftruncate(file_t_fd)); + EXPECT_EQ(EACCES, test_open(file_t, O_RDONLY | O_TRUNC)); + EXPECT_EQ(EACCES, test_open(file_t, O_WRONLY | O_TRUNC)); + + /* Checks "no rights" case: No form of truncation works. */ + EXPECT_EQ(EACCES, test_truncate(file_none)); + EXPECT_EQ(EACCES, test_ftruncate(file_none_fd)); + EXPECT_EQ(EACCES, test_open(file_none, O_RDONLY | O_TRUNC)); + EXPECT_EQ(EACCES, test_open(file_none, O_WRONLY | O_TRUNC)); + + /* + * Checks truncate right on directory: truncate works on contained + * files. + */ + EXPECT_EQ(0, test_truncate(file_in_dir_t)); + EXPECT_EQ(0, test_ftruncate(file_in_dir_t_fd)); + EXPECT_EQ(EACCES, test_open(file_in_dir_t, O_RDONLY | O_TRUNC)); + EXPECT_EQ(EACCES, test_open(file_in_dir_t, O_WRONLY | O_TRUNC)); + + /* + * Checks creat in dir_w: This requires the truncate right when + * overwriting an existing file, but does not require it when the file + * is new. + */ + EXPECT_EQ(EACCES, test_creat(file_in_dir_w, 0600)); + + ASSERT_EQ(0, unlink(file_in_dir_w)); + EXPECT_EQ(0, test_creat(file_in_dir_w, 0600)); +} + /* clang-format off */ FIXTURE(layout1_bind) {}; /* clang-format on */
These tests exercise the following truncation operations: * truncate() (truncate by path) * ftruncate() (truncate by file descriptor) * open with the O_TRUNC flag * special case: creat(), which is open with O_CREAT|O_WRONLY|O_TRUNC. in the following scenarios: * Files with read, write and truncate rights. * Files with read and truncate rights. * Files with the truncate right. * Files without the truncate right. In particular, the following scenarios are enforced with the test: * ftruncate() requires the truncate right, even when the thread already has the right to write to the file. * open() with O_TRUNC requires the truncate right, if it truncates a file. open() already checks security_path_truncate() in this case, and it required no additional check in the Landlock LSM's file_open hook. * creat() requires the truncate right when called with an existing filename. * creat() does *not* require the truncate right when it's creating a new file. Signed-off-by: Günther Noack <gnoack3000@gmail.com> --- tools/testing/selftests/landlock/fs_test.c | 250 +++++++++++++++++++++ 1 file changed, 250 insertions(+)