Re: [RFC PATCH v1 07/11] selftests/landlock: Drain stale audit records on init

Günther Noack
2026-03-24T13:27:51Z

On Thu, Mar 12, 2026 at 11:04:40AM +0100, Mickaël Salaün wrote:
> Non-audit Landlock tests generate audit records as side effects when
> audit_enabled is non-zero (e.g. from boot configuration). These records
> accumulate in the kernel audit backlog while no audit daemon socket is
> open. When the next test opens a new netlink socket and registers as
> the audit daemon, the stale backlog is delivered, causing baseline
> record count checks to fail spuriously.
>
> Fix this by draining all pending records in audit_init() right after
> setting the receive timeout. The 1-usec SO_RCVTIMEO causes audit_recv()
> to return -EAGAIN once the backlog is empty, naturally terminating the
> drain loop.
>
> Domain deallocation records are emitted asynchronously from a work
> queue, so they may still arrive after the drain. Remove records.domain
> == 0 checks from tests where a stale deallocation record from a previous
> test could cause spurious failures.
>
> Also fix a socket file descriptor leak on error paths in audit_init():
> if audit_set_status() or setsockopt() fails (e.g. when another audit
> daemon is already registered), close the socket before returning.
>
> Fix off-by-one checks in matches_log_domain_allocated() and
> matches_log_domain_deallocated() where snprintf() truncation was
> detected with ">" instead of ">=" (snprintf() returns the length
> excluding the NUL terminator, so equality means truncation).
>
> Cc: Günther Noack <gnoack@google.com>
> Fixes: 6a500b22971c ("selftests/landlock: Add tests for audit flags and domain IDs")
> Signed-off-by: Mickaël Salaün <mic@digikod.net>
> ---
> tools/testing/selftests/landlock/audit.h | 29 +++++++++++++++----
> tools/testing/selftests/landlock/audit_test.c | 2 --
> 2 files changed, 23 insertions(+), 8 deletions(-)
>
> diff --git a/tools/testing/selftests/landlock/audit.h b/tools/testing/selftests/landlock/audit.h
> index 44eb433e9666..550acaafcc1e 100644
> --- a/tools/testing/selftests/landlock/audit.h
> +++ b/tools/testing/selftests/landlock/audit.h
> @@ -309,7 +309,7 @@ static int __maybe_unused matches_log_domain_allocated(int audit_fd, pid_t pid,
>
> log_match_len =
> snprintf(log_match, sizeof(log_match), log_template, pid);
> - if (log_match_len > sizeof(log_match))
> + if (log_match_len >= sizeof(log_match))
> return -E2BIG;
>
> return audit_match_record(audit_fd, AUDIT_LANDLOCK_DOMAIN, log_match,
> @@ -326,7 +326,7 @@ static int __maybe_unused matches_log_domain_deallocated(
>
> log_match_len = snprintf(log_match, sizeof(log_match), log_template,
> num_denials);
> - if (log_match_len > sizeof(log_match))
> + if (log_match_len >= sizeof(log_match))
> return -E2BIG;
>
> return audit_match_record(audit_fd, AUDIT_LANDLOCK_DOMAIN, log_match,
> @@ -379,19 +379,36 @@ static int audit_init(void)
>
> err = audit_set_status(fd, AUDIT_STATUS_ENABLED, 1);
> if (err)
> - return err;
> + goto err_close;
>
> err = audit_set_status(fd, AUDIT_STATUS_PID, getpid());
> if (err)
> - return err;
> + goto err_close;
>
> /* Sets a timeout for negative tests. */
> err = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &audit_tv_default,
> sizeof(audit_tv_default));
> - if (err)
> - return -errno;
> + if (err) {
> + err = -errno;
> + goto err_close;
> + }
> +
> + /*
> + * Drains stale audit records that accumulated in the kernel backlog
> + * while no audit daemon socket was open. This happens when
> + * non-audit Landlock tests create domains or trigger denials while
> + * audit_enabled is non-zero (e.g. from boot configuration), or when
> + * domain deallocation records arrive asynchronously after a
> + * previous test's socket was closed.
> + */
> + while (audit_recv(fd, NULL) == 0)
> + ;
>
> return fd;
> +
> +err_close:
> + close(fd);
> + return err;
> }
>
> static int audit_init_filter_exe(struct audit_filter *filter, const char *path)
> diff --git a/tools/testing/selftests/landlock/audit_test.c b/tools/testing/selftests/landlock/audit_test.c
> index 46d02d49835a..f92ba6774faa 100644
> --- a/tools/testing/selftests/landlock/audit_test.c
> +++ b/tools/testing/selftests/landlock/audit_test.c
> @@ -412,7 +412,6 @@ TEST_F(audit_flags, signal)
> } else {
> EXPECT_EQ(1, records.access);
> }
> - EXPECT_EQ(0, records.domain);
>
> /* Updates filter rules to match the drop record. */
> set_cap(_metadata, CAP_AUDIT_CONTROL);
> @@ -601,7 +600,6 @@ TEST_F(audit_exec, signal_and_open)
> /* Tests that there was no denial until now. */
> EXPECT_EQ(0, audit_count_records(self->audit_fd, &records));
> EXPECT_EQ(0, records.access);
> - EXPECT_EQ(0, records.domain);
>
> /*
> * Wait for the child to do a first denied action by layer1 and
> --
> 2.53.0
>

Ooh, nice catch! I have definitely stumbled across this bug in the
past (especially when the kernel is compiled with more debugging
options), and I know from Justin that he ran into it as well.
Draining the audit logs before sending a new stimulus for audit
logging looks like a good approach.

Reviewed-by: Günther Noack <gnoack@google.com>

—Günther