From: Justin Suess <utilityemal77@gmail.com>
To: Tingmao Wang <m@maowtm.org>
Cc: "Mickaël Salaün" <mic@digikod.net>,
"Günther Noack" <gnoack3000@gmail.com>, "Jan Kara" <jack@suse.cz>,
"Abhinav Saxena" <xandfury@gmail.com>,
linux-security-module@vger.kernel.org
Subject: Re: [PATCH v10 6/9] selftests/landlock: add tests for quiet flag with fs rules
Date: Fri, 5 Jun 2026 15:04:04 -0400 [thread overview]
Message-ID: <aiMciEe6zVnisbQJ@zenbox> (raw)
In-Reply-To: <9208d19c3aeba778f317defcfee9c62c44600e3b.1780272022.git.m@maowtm.org>
On Mon, Jun 01, 2026 at 01:00:40AM +0100, Tingmao Wang wrote:
> Test various interactions of the quiet flag with filesystem rules:
> - Non-optional access (tested with open and rename).
> - Optional access (tested with truncate and ioctl).
> - Behaviour around mounts matches with normal Landlock rules.
> - Behaviour around disconnected directories matches with normal Landlock
> rules (test expected behaviour of 9a868cdbe66a ("landlock: Fix handling of
> disconnected directories") applied to the collected quiet flag).
> - Multiple layers works as expected.
>
> Assisted-by: GitHub Copilot:claude-opus-4.6 copilot-review
> Signed-off-by: Tingmao Wang <m@maowtm.org>
> ---
>
> Changes in v10:
> - Fix grammar in some comments
> - if brackets
>
> Changes in v8:
> - Rebase, resolve conflict, then clang-format
> - Remove previously added comment about domain allocation record leakage -
> this is now documented properly by 239fd9a6f948 ("selftests/landlock:
> Drain stale audit records on init")
> - Fix missing EXPECT_EQ on audit_count_records() return value
>
> Changes in v6:
> - Change quiet bool argument of add_path_beneath into a __u32 flags
> (suggested by Justin Suess)
> - Rename quiet_behind_mountpoint_ignored_disconnected to
> quiet_behind_mountpoint_disconnected and fix test due to disconnected
> directory handling changes
>
> Changes in v5:
> - Add quiet_two_layers_different_handled_{1,2,3} variants.
>
> Changes in v3:
> - New patch
>
> tools/testing/selftests/landlock/fs_test.c | 2447 +++++++++++++++++++-
> 1 file changed, 2438 insertions(+), 9 deletions(-)
>
> diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c
> index 10d9355ade5f..5f5d75fabe07 100644
> --- a/tools/testing/selftests/landlock/fs_test.c
> +++ b/tools/testing/selftests/landlock/fs_test.c
> @@ -720,7 +720,7 @@ TEST_F_FORK(layout1, rule_with_unhandled_access)
>
> static void add_path_beneath(struct __test_metadata *const _metadata,
> const int ruleset_fd, const __u64 allowed_access,
> - const char *const path)
> + const char *const path, __u32 flags)
> {
> struct landlock_path_beneath_attr path_beneath = {
> .allowed_access = allowed_access,
> @@ -733,7 +733,7 @@ static void add_path_beneath(struct __test_metadata *const _metadata,
> strerror(errno));
> }
> ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
> - &path_beneath, 0))
> + &path_beneath, flags))
> {
> TH_LOG("Failed to update the ruleset with \"%s\": %s", path,
> strerror(errno));
> @@ -780,7 +780,7 @@ static int create_ruleset(struct __test_metadata *const _metadata,
> continue;
>
> add_path_beneath(_metadata, ruleset_fd, rules[i].access,
> - rules[i].path);
> + rules[i].path, 0);
> }
> return ruleset_fd;
> }
> @@ -1310,7 +1310,7 @@ TEST_F_FORK(layout1, inherit_subset)
> * ANDed with the previous ones.
> */
> add_path_beneath(_metadata, ruleset_fd, LANDLOCK_ACCESS_FS_WRITE_FILE,
> - dir_s1d2);
> + dir_s1d2, 0);
> /*
> * According to ruleset_fd, dir_s1d2 should now have the
> * LANDLOCK_ACCESS_FS_READ_FILE and LANDLOCK_ACCESS_FS_WRITE_FILE
> @@ -1342,7 +1342,7 @@ TEST_F_FORK(layout1, inherit_subset)
> * Try to get more privileges by adding new access rights to the parent
> * directory: dir_s1d1.
> */
> - add_path_beneath(_metadata, ruleset_fd, ACCESS_RW, dir_s1d1);
> + add_path_beneath(_metadata, ruleset_fd, ACCESS_RW, dir_s1d1, 0);
> enforce_ruleset(_metadata, ruleset_fd);
>
> /* Same tests and results as above. */
> @@ -1365,7 +1365,7 @@ TEST_F_FORK(layout1, inherit_subset)
> * that there was no rule tied to it before.
> */
> add_path_beneath(_metadata, ruleset_fd, LANDLOCK_ACCESS_FS_WRITE_FILE,
> - dir_s1d3);
> + dir_s1d3, 0);
> enforce_ruleset(_metadata, ruleset_fd);
> ASSERT_EQ(0, close(ruleset_fd));
>
> @@ -1417,7 +1417,7 @@ TEST_F_FORK(layout1, inherit_superset)
> add_path_beneath(_metadata, ruleset_fd,
> LANDLOCK_ACCESS_FS_READ_FILE |
> LANDLOCK_ACCESS_FS_READ_DIR,
> - dir_s1d2);
> + dir_s1d2, 0);
> enforce_ruleset(_metadata, ruleset_fd);
> EXPECT_EQ(0, close(ruleset_fd));
>
> @@ -3970,7 +3970,7 @@ static int ioctl_error(struct __test_metadata *const _metadata, int fd,
> unsigned int cmd)
> {
> char buf[128]; /* sufficiently large */
> - int res, stdinbak_fd;
> + int res, stdinbak_fd, err;
>
> /*
> * Depending on the IOCTL command, parts of the zeroed-out buffer might
> @@ -3985,13 +3985,14 @@ static int ioctl_error(struct __test_metadata *const _metadata, int fd,
> /* Invokes the IOCTL with a zeroed-out buffer. */
> bzero(&buf, sizeof(buf));
> res = ioctl(fd, cmd, &buf);
> + err = errno;
>
> /* Restores the old FD 0 and closes the backup FD. */
> ASSERT_EQ(0, dup2(stdinbak_fd, 0));
> ASSERT_EQ(0, close(stdinbak_fd));
>
> if (res < 0)
> - return errno;
> + return err;
>
> return 0;
> }
> @@ -4789,6 +4790,7 @@ FIXTURE(layout1_bind) {};
>
> static const char bind_dir_s1d3[] = TMP_DIR "/s2d1/s2d2/s1d3";
> static const char bind_file1_s1d3[] = TMP_DIR "/s2d1/s2d2/s1d3/f1";
> +static const char bind_file2_s1d3[] = TMP_DIR "/s2d1/s2d2/s1d3/f2";
>
> /* Move targets for disconnected path tests. */
> static const char dir_s4d1[] = TMP_DIR "/s4d1";
> @@ -7764,4 +7766,2431 @@ TEST_F(audit_layout1, mount)
> EXPECT_EQ(1, records.domain);
> }
>
> +static bool debug_quiet_tests;
> +
> +FIXTURE(audit_quiet_layout1)
> +{
> + struct audit_filter audit_filter;
> + int audit_fd;
> +};
> +
> +FIXTURE_SETUP(audit_quiet_layout1)
> +{
> + prepare_layout(_metadata);
> + create_layout1(_metadata);
> +
> + set_cap(_metadata, CAP_AUDIT_CONTROL);
> + self->audit_fd = audit_init_with_exe_filter(&self->audit_filter);
> + EXPECT_LE(0, self->audit_fd);
> + clear_cap(_metadata, CAP_AUDIT_CONTROL);
> +
> + if (getenv("DEBUG_QUIET_TESTS"))
> + debug_quiet_tests = true;
> +}
> +
> +FIXTURE_TEARDOWN_PARENT(audit_quiet_layout1)
> +{
> + remove_layout1(_metadata);
> + cleanup_layout(_metadata);
> +
> + set_cap(_metadata, CAP_AUDIT_CONTROL);
> + EXPECT_EQ(0, audit_cleanup(-1, NULL));
> + clear_cap(_metadata, CAP_AUDIT_CONTROL);
> +}
> +
> +struct a_rule {
> + const char *path;
> + __u64 access;
> + bool quiet;
> +};
> +
> +struct a_layer {
> + __u64 handled_access_fs;
> + __u64 quiet_access_fs;
> + struct a_rule rules[6];
> + __u64 restrict_flags;
> +};
> +
> +struct a_target {
> + /* File/dir to try open. */
> + const char *target;
> + /* Open mode (one of O_RDONLY, O_WRONLY, or O_RDWR). */
> + int open_mode;
> + /* Should open succeed? */
> + bool expect_open_success;
> + /* If open fails, whether to expect an audit log for read. */
> + bool audit_read_blocked;
> + /* If open fails, whether to expect an audit log for write. */
> + bool audit_write_blocked;
> + /* If ftruncate() is expected to be allowed. */
> + bool expect_truncate_success;
> + /* If ftruncate fails, whether to expect an audit log. */
> + bool audit_truncate;
> + /*
> + * If ioctl() is expected to be allowed (ioctl not attempted if
> + * neither this nor expect_ioctl_denied is set).
> + */
> + bool expect_ioctl_allowed;
> + /* If ioctl() is expected to be denied. */
> + bool expect_ioctl_denied;
> + /* If ioctl fails, whether to expect an audit log. */
> + bool audit_ioctl;
> +};
> +
> +#define AUDIT_QUIET_MAX_TARGETS 10
> +
> +FIXTURE_VARIANT(audit_quiet_layout1)
> +{
> + struct a_layer layers[3];
> + struct a_target targets[AUDIT_QUIET_MAX_TARGETS];
> +};
> +
> +#define FS_R LANDLOCK_ACCESS_FS_READ_FILE
> +#define FS_W LANDLOCK_ACCESS_FS_WRITE_FILE
> +#define FS_TRUNC LANDLOCK_ACCESS_FS_TRUNCATE
> +#define FS_IOCTL LANDLOCK_ACCESS_FS_IOCTL_DEV
> +
> +static int sprint_access_bits(char *buf, size_t buflen, __u64 access)
> +{
> + size_t offset = 0;
> +
> + if (buflen < strlen("rwti make_reg remove_file refer") + 1)
> + abort();
> +
> + buf[0] = '\0';
> + if (access & FS_R)
> + offset += snprintf(buf + offset, buflen - offset, "r");
> + if (access & FS_W)
> + offset += snprintf(buf + offset, buflen - offset, "w");
> + if (access & FS_TRUNC)
> + offset += snprintf(buf + offset, buflen - offset, "t");
> + if (access & FS_IOCTL)
> + offset += snprintf(buf + offset, buflen - offset, "i");
> + if (access & LANDLOCK_ACCESS_FS_MAKE_REG)
> + offset += snprintf(buf + offset, buflen - offset, ",make_reg");
> + if (access & LANDLOCK_ACCESS_FS_REMOVE_FILE)
> + offset +=
> + snprintf(buf + offset, buflen - offset, ",remove_file");
> + if (access & LANDLOCK_ACCESS_FS_REFER)
> + offset += snprintf(buf + offset, buflen - offset, ",refer");
> +
> + if (buf[0] == ',') {
> + offset--;
> + memmove(buf, buf + 1, offset);
> + buf[offset] = '\0';
> + }
> +
> + return offset;
> +}
> +
> +static int apply_a_layer(struct __test_metadata *const _metadata,
> + const struct a_layer *l)
> +{
> + struct landlock_ruleset_attr rs_attr = {
> + .handled_access_fs = l->handled_access_fs,
> + .quiet_access_fs = l->quiet_access_fs,
> + };
> + int rs_fd;
> + int i;
> + const struct a_rule *r;
> + char handled_access_s[33], quiet_access_s[33], rule_access_s[33];
> +
> + if (!l->handled_access_fs)
> + return 0;
> +
> + rs_fd = landlock_create_ruleset(&rs_attr, sizeof(rs_attr), 0);
> + ASSERT_LE(0, rs_fd);
> +
> + for (i = 0; i < ARRAY_SIZE(l->rules); i++) {
> + r = &l->rules[i];
> + if (!r->path)
> + continue;
> +
> + add_path_beneath(_metadata, rs_fd, r->access, r->path,
> + r->quiet ? LANDLOCK_ADD_RULE_QUIET : 0);
> + }
> +
> + ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
> + ASSERT_EQ(0, landlock_restrict_self(rs_fd, l->restrict_flags))
> + {
> + TH_LOG("Failed to enforce ruleset: %s", strerror(errno));
> + }
> + ASSERT_EQ(0, close(rs_fd));
> +
> + if (debug_quiet_tests) {
> + sprint_access_bits(handled_access_s, sizeof(handled_access_s),
> + l->handled_access_fs);
> + sprint_access_bits(quiet_access_s, sizeof(quiet_access_s),
> + l->quiet_access_fs);
> + TH_LOG("applied layer: handled=%s quiet=%s restrict_flags=0x%llx",
> + handled_access_s, quiet_access_s,
> + (unsigned long long)l->restrict_flags);
> + for (i = 0; i < ARRAY_SIZE(l->rules); i++) {
> + r = &l->rules[i];
> + if (!r->path)
> + continue;
> +
> + sprint_access_bits(rule_access_s, sizeof(rule_access_s),
> + r->access);
> + TH_LOG(" rule[%d]: path=%s access=%s quiet=%d", i,
> + r->path, rule_access_s, r->quiet);
> + }
> + }
> + return 0;
> +}
> +
> +void audit_quiet_layout1_test_body(struct __test_metadata *const _metadata,
> + FIXTURE_DATA(audit_quiet_layout1) * self,
> + const struct a_target *targets)
> +{
> + struct audit_records records = {};
> + int i;
> + const struct a_target *target;
> + int fd = -1;
> + int open_mode;
> + int ret;
> + bool expect_audit;
> + const char *blocker;
> +
> + for (i = 0; i < AUDIT_QUIET_MAX_TARGETS; i++) {
> + target = &targets[i];
> + if (!target->target)
> + continue;
> +
> + open_mode = target->open_mode & (O_RDONLY | O_WRONLY | O_RDWR);
> +
> + EXPECT_TRUE(open_mode == O_RDONLY || open_mode == O_WRONLY ||
> + open_mode == O_RDWR);
> +
> + if (target->expect_open_success) {
> + EXPECT_FALSE(target->audit_read_blocked);
> + EXPECT_FALSE(target->audit_write_blocked);
> + }
> + if (target->expect_truncate_success)
> + EXPECT_TRUE(target->expect_open_success &&
> + !target->audit_truncate);
> +
> + if (debug_quiet_tests)
> + TH_LOG("Try open \"%s\" with %s%s", target->target,
> + open_mode != O_WRONLY ? "r" : "",
> + open_mode != O_RDONLY ? "w" : "");
> +
> + fd = openat(AT_FDCWD, target->target, open_mode | O_CLOEXEC);
> + if (target->expect_open_success) {
> + ASSERT_LE(0, fd)
> + {
> + TH_LOG("Failed to open \"%s\": %s",
> + target->target, strerror(errno));
> + };
> + } else {
> + ASSERT_EQ(-1, fd);
> + ASSERT_EQ(EACCES, errno);
> + }
> +
> + expect_audit = true;
> +
> + if (target->audit_read_blocked && target->audit_write_blocked)
> + blocker = "fs\\.write_file,fs\\.read_file";
> + else if (target->audit_read_blocked)
> + blocker = "fs\\.read_file";
> + else if (target->audit_write_blocked)
> + blocker = "fs\\.write_file";
> + else
> + expect_audit = false;
> +
> + if (expect_audit)
> + ASSERT_EQ(0, matches_log_fs(_metadata, self->audit_fd,
> + blocker, target->target));
> +
> + /* Check that we see no (other) logs. */
> + EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
> + ASSERT_EQ(0, records.access);
> +
> + if (target->expect_open_success && fd >= 0) {
> + if (debug_quiet_tests)
> + TH_LOG("Try ftruncate \"%s\"", target->target);
> +
> + ret = ftruncate(fd, 0);
> + if (target->expect_truncate_success) {
> + ASSERT_EQ(0, ret);
> + } else {
> + ASSERT_EQ(-1, ret);
> + if (open_mode != O_RDONLY)
> + ASSERT_EQ(EACCES, errno);
> + }
> +
> + if (target->audit_truncate)
> + ASSERT_EQ(0, matches_log_fs(_metadata,
> + self->audit_fd,
> + "fs\\.truncate",
> + target->target));
> +
> + if (target->expect_ioctl_allowed ||
> + target->expect_ioctl_denied) {
> + if (debug_quiet_tests)
> + TH_LOG("Try ioctl FIONREAD on \"%s\"",
> + target->target);
> +
> + ret = ioctl_error(_metadata, fd, FIONREAD);
> + if (target->expect_ioctl_allowed)
> + ASSERT_NE(EACCES, ret);
> + else
> + ASSERT_EQ(EACCES, ret);
This doesn't compile.
make: Entering directory '/home/justin/Code/linux-next/tools/testing/selftests'
CC fs_test
fs_test.c: In function ‘audit_quiet_layout1_test_body’:
fs_test.c:8456:33: error: expected ‘}’ before ‘else’
8456 | else
| ^~~~
fs_test.c: At top level:
fs_test.c:8474:1: error: expected identifier or ‘(’ before ‘}’ token
8474 | }
| ^
make[1]: *** [../lib.mk:225: /home/justin/Code/linux-next/.out-landlock_archlinux-base-devel/kselftest/landlock/fs_test] Error 1
The ASSERT_* macros doen't properly handle braceless if statements...
(easy to miss if you forget to recompile after clang format and/or
checkpatch --fix...)
Adding braces to this as with previous versions of this series should
fix it.
Justin
next prev parent reply other threads:[~2026-06-05 19:04 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-01 0:00 [PATCH v10 0/9] Implement LANDLOCK_ADD_RULE_QUIET Tingmao Wang
2026-06-01 0:00 ` [PATCH v10 1/9] landlock: Add a place for flags to layer rules Tingmao Wang
2026-06-01 0:00 ` [PATCH v10 2/9] landlock: Add API support and docs for the quiet flags Tingmao Wang
2026-06-01 0:00 ` [PATCH v10 3/9] landlock: Suppress logging when quiet flag is present Tingmao Wang
2026-06-01 0:00 ` [PATCH v10 4/9] samples/landlock: Add quiet flag support to sandboxer Tingmao Wang
2026-06-01 0:00 ` [PATCH v10 5/9] selftests/landlock: Replace hard-coded 16 with a constant Tingmao Wang
2026-06-01 0:00 ` [PATCH v10 6/9] selftests/landlock: add tests for quiet flag with fs rules Tingmao Wang
2026-06-05 19:04 ` Justin Suess [this message]
2026-06-01 0:00 ` [PATCH v10 7/9] selftests/landlock: add tests for quiet flag with net rules Tingmao Wang
2026-06-01 0:00 ` [PATCH v10 8/9] selftests/landlock: Add tests for quiet flag with scope Tingmao Wang
2026-06-01 0:00 ` [PATCH v10 9/9] selftests/landlock: Add tests for invalid use of quiet flag Tingmao Wang
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=aiMciEe6zVnisbQJ@zenbox \
--to=utilityemal77@gmail.com \
--cc=gnoack3000@gmail.com \
--cc=jack@suse.cz \
--cc=linux-security-module@vger.kernel.org \
--cc=m@maowtm.org \
--cc=mic@digikod.net \
--cc=xandfury@gmail.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