From: "Günther Noack" <gnoack3000@gmail.com>
To: "Mickaël Salaün" <mic@digikod.net>
Cc: linux-security-module@vger.kernel.org,
James Morris <jmorris@namei.org>,
Paul Moore <paul@paul-moore.com>,
"Serge E . Hallyn" <serge@hallyn.com>
Subject: Re: [PATCH v4 2/4] selftests/landlock: Selftests for file truncation support
Date: Wed, 17 Aug 2022 20:00:17 +0200 [thread overview]
Message-ID: <Yv0ssfnx8BcUf0Lp@nuc> (raw)
In-Reply-To: <6ed7d884-89cb-546f-db0a-1c16209f1c29@digikod.net>
On Tue, Aug 16, 2022 at 07:08:20PM +0200, Mickaël Salaün wrote:
> On 14/08/2022 21:26, Günther Noack wrote:
> > +/*
> > + * Opens the file and invokes ftruncate(2).
> > + *
> > + * Returns the errno of ftruncate if ftruncate() fails.
> > + * Returns EBADFD if open() or close() fail (should not happen).
> > + * Returns 0 if ftruncate(), open() and close() were successful.
> > + */
> > +static int test_ftruncate(struct __test_metadata *const _metadata,
>
> _metadata is no longer needed. Same for test_creat().
Thanks, well spotted!
>
>
> > + const char *const path, int flags)
> > +{
> > + int res, err, fd;
> > +
> > + fd = open(path, flags | O_CLOEXEC);
> > + if (fd < 0)
> > + return EBADFD;
>
> Instead of EBADFD, which is a valid open(2) error, you can use ENOSYS and
> add a comment explaining that we are not interested by this open(2) error
> code but only the ftruncate(2) one because we are sure opening such path is
> allowed or because open(2) is already tested before calls to
> test_ftruncate().
Changed to ENOSYS and added a comment in the code and as function documentation.
The explanation follows the reasoning that callers must guarantee that
open() and close() will work, in order to test ftruncate() correctly.
If open() or close() fail, we return ENOSYS.
Technically EBADFD does not get returned by open(2) according to the
man page, but I changed it to ENOSYS anyway (EBADF and EBADFD are easy
to mix up).
> > +
> > + res = ftruncate(fd, 10);
> > + err = errno;
> > +
> > + if (close(fd) != 0)
> > + return EBADFD;
>
> Why not returning errno? See test_open_rel() comments.
OK, changed to return errno for consistency, with the same comment.
>
>
> > +
> > + if (res < 0)
> > + return err;
> > + return 0;
> > +}
> > +
> > +/* Invokes truncate(2) and returns the errno or 0. */
> > +static int test_truncate(const char *const path)
> > +{
> > + if (truncate(path, 10) < 0)
> > + return errno;
> > + return 0;
> > +}
> > +
> > +/*
> > + * Invokes creat(2) and returns its errno or 0.
> > + * Closes the opened file descriptor on success.
> > + * Returns EBADFD if close() returns an error (should not happen).
> > + */
> > +static int test_creat(struct __test_metadata *const _metadata,
> > + const char *const path, mode_t mode)
> > +{
> > + int fd = creat(path, mode);
> > +
> > + if (fd < 0)
> > + return errno;
> > +
> > + if (close(fd) < 0)
> > + return EBADFD;
>
> Why not returning errno?
Done. (Same)
> > + // Implicitly: No access rights for file_none.
>
> Please use /* */ comments everywhere.
Done. (in both places)
> > + /*
> > + * Checks read, write and truncate rights: truncation works.
> > + *
> > + * Note: Independent of Landlock, ftruncate(2) on read-only
> > + * file descriptors never works.
> > + */
> > + EXPECT_EQ(0, test_ftruncate(_metadata, file_rwt, O_WRONLY));
> > + EXPECT_EQ(EINVAL, test_ftruncate(_metadata, file_rwt, O_RDONLY));
> > + EXPECT_EQ(0, test_truncate(file_rwt));
> > + EXPECT_EQ(0, test_open(file_rwt, O_WRONLY | O_TRUNC));
>
> Could you please interleave the test_open() and test_ftruncate() with the
> same open flags, and start with test_open() to make sure assumptions are
> correct (cf. previous comment about test_ftruncate)? Like this (everywhere):
>
> EXPECT_EQ(0, test_open(file_rwt, O_RDONLY | O_TRUNC));
> EXPECT_EQ(EINVAL, test_ftruncate(file_rwt, O_RDONLY));
> EXPECT_EQ(0, test_open(file_rwt, O_WRONLY | O_TRUNC));
> EXPECT_EQ(0, test_ftruncate(file_rwt, O_WRONLY));
> EXPECT_EQ(0, test_truncate(file_rwt));
OK, I ordered it like that.
When calling
EXPECT_EQ(foo, test_ftruncate(...));
and "foo" is not ENOSYS (which it should never be!), then that alone
is enough to guarantee that the open() in test_ftruncate worked, so in
a sense this is already tested implicitly.
The only thing to make sure is to never *expect* ENOSYS from
test_ftruncate(), but I documented in test_ftruncate() now that it is
better to use test_open() for testing the result of open().
>
>
> > + EXPECT_EQ(0, test_open(file_rwt, O_RDONLY | O_TRUNC));
> > +
> > + /* Checks read and write rights: no truncate variant works. */
> > + EXPECT_EQ(EACCES, test_ftruncate(_metadata, file_rw, O_WRONLY));
> > + EXPECT_EQ(EINVAL, test_ftruncate(_metadata, file_rw, O_RDONLY));
> > + EXPECT_EQ(EACCES, test_truncate(file_rw));
> > + EXPECT_EQ(EACCES, test_open(file_rw, O_WRONLY | O_TRUNC));
> > + EXPECT_EQ(EACCES, test_open(file_rw, O_RDONLY | O_TRUNC));
> > +
> > + /*
> > + * Checks read and truncate rights: truncation works.
> > + *
> > + * Note: Files opened in O_RDONLY can get truncated as part of
> > + * the same operation.
> > + */
> > + EXPECT_EQ(0, test_open(file_rt, O_RDONLY));
> > + EXPECT_EQ(0, test_open(file_rt, O_RDONLY | O_TRUNC));
>
> Why not a test_ftruncate() check here?
The twist here is, althrough truncate() works, ftruncate() cannot
truncate the file in this case:
(a) when trying to open the file for writing, Landlock policy keeps
you from doing that (tested with test_open).
(b) when opening the file read-only, that works, but read-only fds can
never be ftruncated (explained in the man page).
It's slightly surprising when just glancing over the test, so I added
the check for extra clarity:
EXPECT_EQ(EINVAL, test_ftruncate(file_rt, O_RDONLY));
The check for test_open with O_WRONLY was already there.
> > + EXPECT_EQ(EACCES, test_open(file_rt, O_WRONLY));
> > + EXPECT_EQ(EACCES, test_open(file_rt, O_WRONLY));
>
> There is a missing "| O_TRUNC"
Well spotted! Fixed.
> > + EXPECT_EQ(0, test_truncate(file_rt));
> > +
> > + /* Checks truncate right: truncate works, but can't open file. */
> > + EXPECT_EQ(EACCES, test_open(file_t, O_WRONLY));
> > + EXPECT_EQ(EACCES, test_open(file_t, O_RDONLY));
> > + EXPECT_EQ(EACCES, test_open(file_t, O_WRONLY | O_TRUNC));
> > + EXPECT_EQ(EACCES, test_open(file_t, O_RDONLY | O_TRUNC));
> > + EXPECT_EQ(0, test_truncate(file_t));
> > +
> > + /* Checks "no rights" case: No form of truncation works. */
> > + EXPECT_EQ(EACCES, test_open(file_none, O_WRONLY));
> > + EXPECT_EQ(EACCES, test_open(file_none, O_RDONLY));
> > + EXPECT_EQ(EACCES, test_open(file_none, O_WRONLY | O_TRUNC));
> > + EXPECT_EQ(EACCES, test_open(file_none, O_RDONLY | O_TRUNC));
> > + EXPECT_EQ(EACCES, test_truncate(file_none));
> > +
> > + /* Checks truncate right on directory: truncate works on contained files */
> > + EXPECT_EQ(EACCES, test_open(file_in_dir_t, O_WRONLY));
> > + EXPECT_EQ(EACCES, test_open(file_in_dir_t, O_RDONLY));
> > + EXPECT_EQ(EACCES, test_open(file_in_dir_t, O_WRONLY | O_TRUNC));
> > + EXPECT_EQ(EACCES, test_open(file_in_dir_t, O_RDONLY | O_TRUNC));
> > + EXPECT_EQ(0, test_truncate(file_in_dir_t));
> > +
> > + /*
> > + * 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(_metadata, file_in_dir_w, 0600));
> > +
> > + ASSERT_EQ(0, unlink(file_in_dir_w));
> > + EXPECT_EQ(0, test_creat(_metadata, file_in_dir_w, 0600));
> > +}
> > +
> > +/*
> > + * 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)
>
> Can you move layout1.truncate_unhandled tests before layout1.truncate?
Done.
>
>
> > +{
> > + const char *const file_r = file1_s1d1;
> > + const char *const file_w = file2_s1d1;
> > + const char *const file_none = file1_s1d2;
> > + 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;
> > + const int 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: truncation should work through truncate and open. */
> > + EXPECT_EQ(0, test_truncate(file_r));
>
> We should be consistent to also check with test_ftruncate() (and order the
> EXPECT checks appropriately).
Added.
>
>
> > + EXPECT_EQ(0, test_open(file_r, O_RDONLY | O_TRUNC));
> > + EXPECT_EQ(EACCES, test_open(file_r, O_WRONLY | O_TRUNC));
> > +
> > + /* Checks write right: truncation should work through truncate, ftruncate and open. */
>
> Please align to 80 columns.
Done.
>
>
> > + EXPECT_EQ(0, test_truncate(file_w));
> > + EXPECT_EQ(0, test_ftruncate(_metadata, file_w, O_WRONLY));
> > + EXPECT_EQ(EACCES, test_open(file_w, O_RDONLY | O_TRUNC));
> > + EXPECT_EQ(0, test_open(file_w, O_WRONLY | O_TRUNC));
> > +
> > + /* Checks "no rights" case: truncate works but all open attempts fail. */
> > + EXPECT_EQ(0, test_truncate(file_none));
> > + 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_open(file_none, O_WRONLY));
>
> Can you test with test_creat() here?
Added.
Thanks for the careful review!
—Günther
--
next prev parent reply other threads:[~2022-08-17 18:00 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-08-14 19:25 [PATCH v4 0/4] landlock: truncate support Günther Noack
2022-08-14 19:26 ` [PATCH v4 1/4] landlock: Support file truncation Günther Noack
2022-08-16 19:20 ` Mickaël Salaün
2022-08-17 16:31 ` Günther Noack
2022-08-14 19:26 ` [PATCH v4 2/4] selftests/landlock: Selftests for file truncation support Günther Noack
2022-08-16 17:08 ` Mickaël Salaün
2022-08-17 18:00 ` Günther Noack [this message]
2022-08-17 19:35 ` Günther Noack
2022-08-18 11:26 ` Mickaël Salaün
2022-08-14 19:26 ` [PATCH v4 3/4] samples/landlock: Extend sample tool to support LANDLOCK_ACCESS_FS_TRUNCATE Günther Noack
2022-08-14 19:26 ` [PATCH v4 4/4] landlock: Document Landlock's file truncation support Günther Noack
2022-08-16 19:18 ` Mickaël Salaün
2022-08-17 18:21 ` Günther Noack
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=Yv0ssfnx8BcUf0Lp@nuc \
--to=gnoack3000@gmail.com \
--cc=jmorris@namei.org \
--cc=linux-security-module@vger.kernel.org \
--cc=mic@digikod.net \
--cc=paul@paul-moore.com \
--cc=serge@hallyn.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).