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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.