Fix Landlock audit test flakiness
Subsystems (4)
Mickaël Salaün
maintainerv3 cover Fix Landlock audit test flakiness
This series fixes two classes of audit selftest failures plus two minor bugs in the audit test helpers.
The main issue is that domain deallocation audit records are emitted asynchronously from kworker threads and can arrive after a previous test's socket has been closed. This causes two distinct failure modes:
- audit_match_record() picks up a stale deallocation record from a previous test instead of the expected one, causing a domain ID mismatch. The audit.layers test (which reads 16 deallocation records in sequence) is particularly vulnerable because the large read window allows stale records to interleave. Patch 4 fixes this by filtering deallocation records by domain ID and skipping type-matching records with wrong content patterns.
- audit_count_records() counts stale deallocation records from a previous test, incrementing records.domain from the expected 0 to 1. Patch 3 fixes this by draining stale records at audit_init() time and removing records.domain == 0 checks that are not preceded by audit_match_record() calls (which would consume stale records).
These races are more likely to manifest when additional instrumentation changes kworker timing in the deallocation path (e.g. with the upcoming Landlock tracepoints work).
The two minor fixes (patches 1-2) correct a snprintf truncation check off-by-one and socket file descriptor leaks on error paths in audit_init(), audit_init_with_exe_filter(), and audit_cleanup(). Patch 5 fixes a __u64 format warning reported by the kbuild bot on powerpc64.
Patch 1 is an exact subset of the v1 combined patch, which is why it carries the Reviewed-by tag. Patches 2 and 3 extend beyond what was in v1, so the Reviewed-by is not carried. Patches 4 and 5 are new.
Changes since v2:
https://lore.kernel.org/r/[email protected]
- Patches 4-5: fix __u64 format warnings on powerpc64 (cast to unsigned long long for %llx). Patch 5 is new.
Changes since v1:
https://lore.kernel.org/r/[email protected]
- Split the combined drain fix into four separate patches.
- Patch 2: extend fd leak fix to audit_init_with_exe_filter() and audit_cleanup().
- Patch 3: also remove domain checks from audit.trace and scoped_audit.connect_to_child, document constraint, explain why a longer drain timeout was rejected.
- Patch 4: new, add domain ID filtering and timeout management to matches_log_domain_deallocated(), skip stale records in audit_match_record().
Mickaël Salaün (5):
selftests/landlock: Fix snprintf truncation checks in audit helpers selftests/landlock: Fix socket file descriptor leaks in audit helpers selftests/landlock: Drain stale audit records on init selftests/landlock: Skip stale records in audit_match_record() selftests/landlock: Fix format warning for __u64 in net_test
tools/testing/selftests/landlock/audit.h | 133 ++++++++++++++---- tools/testing/selftests/landlock/audit_test.c | 36 ++--- tools/testing/selftests/landlock/net_test.c | 2 +- .../testing/selftests/landlock/ptrace_test.c | 1 - .../landlock/scoped_abstract_unix_test.c | 1 - 5 files changed, 119 insertions(+), 54 deletions(-)
Signature · 1 line
2.53.0
Mickaël Salaün
maintainerPATCH v3 1/5 selftests/landlock: Fix snprintf truncation checks in audit helpers
snprintf() returns the number of characters that would have been written, excluding the terminating NUL byte. When the output is truncated, this return value equals or exceeds the buffer size. Fix matches_log_domain_allocated() and matches_log_domain_deallocated() to detect truncation with ">=" instead of ">".
---
Changes since v1:
https://lore.kernel.org/r/[email protected]
- New patch (split from the drain fix).
--- tools/testing/selftests/landlock/audit.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
tools/testing/selftests/landlock/audit.h +2 -2
@@ -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,
Signature · 1 line
2.53.0
Re: [PATCH v3 1/5] selftests/landlock: Fix snprintf truncation checks in audit helpers
Quoted 45 lines · Mickaël Salaün
snprintf()returns the number of characters that would have been written, excluding the terminating NUL byte. When the output is truncated, this return value equals or exceeds the buffer size. Fixmatches_log_domain_allocated()andmatches_log_domain_deallocated()to detect truncation with ">=" instead of ">".Cc Günther Noack <[email protected]>Fixes 6a500b22971c ("selftests/landlock: Add tests for audit flags and domain IDs")Reviewed-by Günther Noack <[email protected]>Signed-off-by Mickaël Salaün <[email protected]>---
Changes since v1:
https://lore.kernel.org/r/[email protected]
- New patch (split from the drain fix).--- tools/testing/selftests/landlock/audit.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)tools/testing/selftests/landlock/audit.h +2
-2@@ -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,Signature · 1 line
2.53.0
(I noticed the Reviewed-by tag was already there, re-sending to confirm that this also applies to this subset of the original patch)
–Günther
Mickaël Salaün
maintainerPATCH v3 2/5 selftests/landlock: Fix socket file descriptor leaks in audit helpers
In reply to Mickaël Salaünaudit_init() opens a netlink socket and configures it, but leaks the file descriptor if audit_set_status() or setsockopt() fails. Fix this by jumping to an error path that closes the socket before returning.
Apply the same fix to audit_init_with_exe_filter(), which leaks the file descriptor from audit_init() if audit_init_filter_exe() or audit_filter_exe() fails, and to audit_cleanup(), which leaks it if audit_init_filter_exe() fails in FIXTURE_TEARDOWN_PARENT().
---
Changes since v1:
https://lore.kernel.org/r/[email protected]
- New patch (split from the drain fix, extended to audit_init_with_exe_filter() and audit_cleanup()).
--- tools/testing/selftests/landlock/audit.h | 26 +++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-)
tools/testing/selftests/landlock/audit.h +19 -7
@@ -379,19 +379,25 @@ 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; + }return fd; + +err_close: +close (fd); +return err; }static int audit_init_filter_exe (struct audit_filter *filter,const char *path)
@@ -441,8 +447,10 @@ static int audit_cleanup(int audit_fd, struct audit_filter *filter) filter = &new_filter; err =audit_init_filter_exe (filter,NULL ); -if (err) +if (err) { +close (audit_fd);return err; + } }/* Filters might not be in place. */
@@ -468,11 +476,15 @@ static int audit_init_with_exe_filter(struct audit_filter *filter) err =audit_init_filter_exe (filter,NULL );if (err) -return err; + goto err_close; err =audit_filter_exe (fd, filter,AUDIT_ADD_RULE );if (err) -return err; + goto err_close;return fd; + +err_close: +close (fd); +return err; }
Signature · 1 line
2.53.0
Re: [PATCH v3 2/5] selftests/landlock: Fix socket file descriptor leaks in audit helpers
Quoted 90 lines · Mickaël Salaün
audit_init()opens a netlink socket and configures it, but leaks the file descriptor ifaudit_set_status()orsetsockopt()fails. Fix this by jumping to an error path that closes the socket before returning.Apply the same fix to
audit_init_with_exe_filter(), which leaks the file descriptor fromaudit_init()ifaudit_init_filter_exe()oraudit_filter_exe()fails, and toaudit_cleanup(), which leaks it ifaudit_init_filter_exe()fails inFIXTURE_TEARDOWN_PARENT().Cc Günther Noack <[email protected]>Fixes 6a500b22971c ("selftests/landlock: Add tests for audit flags and domain IDs")Signed-off-by Mickaël Salaün <[email protected]>---
Changes since v1:
https://lore.kernel.org/r/[email protected]
- New patch (split from the drain fix, extended toaudit_init_with_exe_filter()andaudit_cleanup()).--- tools/testing/selftests/landlock/audit.h | 26 +++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-)tools/testing/selftests/landlock/audit.h +19
-7@@ -379,19 +379,25 @@ 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; + }return fd; + +err_close: +close (fd); +return err; }static int audit_init_filter_exe (struct audit_filter *filter,const char *path)@@ -441,8 +447,10 @@ static int audit_cleanup(int audit_fd, struct audit_filter *filter) filter = &new_filter; err =audit_init_filter_exe (filter,NULL ); -if (err) +if (err) { +close (audit_fd);return err; + } }/* Filters might not be in place. */ @@ -468,11 +476,15 @@ static int audit_init_with_exe_filter(struct audit_filter *filter) err =audit_init_filter_exe (filter,NULL );if (err) -return err; + goto err_close; err =audit_filter_exe (fd, filter,AUDIT_ADD_RULE );if (err) -return err; + goto err_close;return fd; + +err_close: +close (fd); +return err; }Signature · 1 line
2.53.0
Mickaël Salaün
maintainerPATCH v3 3/5 selftests/landlock: Drain stale audit records on init
In reply to Mickaël SalaünNon-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 that are not preceded by audit_match_record() calls, which would otherwise consume stale records before the count. Document this constraint above audit_count_records().
Increasing the drain timeout to catch in-flight deallocation records was considered but rejected: a longer timeout adds latency to every audit_init() call even when no stale record is pending, and any fixed timeout is still not guaranteed to catch all records under load. Removing the unprotected checks is simpler and avoids the spurious failures.
---
Changes since v1:
https://lore.kernel.org/r/[email protected]
- Also remove domain checks from audit.trace and scoped_audit.connect_to_child.
- Document records.domain == 0 constraint above audit_count_records().
- Explain why a longer drain timeout was rejected.
- Drop Reviewed-by (new code comment not in v1).
- Split snprintf and fd leak fixes into separate patches.
--- tools/testing/selftests/landlock/audit.h | 19 +++++++++++++++++++ tools/testing/selftests/landlock/audit_test.c | 2 -- .../testing/selftests/landlock/ptrace_test.c | 1 - .../landlock/scoped_abstract_unix_test.c | 1 - 4 files changed, 19 insertions(+), 4 deletions(-)
tools/testing/selftests/landlock/audit.h +19 -0
@@ -338,6 +338,15 @@ struct audit_records {size_t domain; }; +/* +* WARNING: Do not assert records.domain == 0 without a preceding +* audit_match_record() call. Domain deallocation records are emitted +* asynchronously from kworker threads and can arrive after the drain in +* audit_init(), corrupting the domain count. A preceding audit_match_record() +* call consumes stale records while scanning, making the assertion safe in +* practice because stale deallocation records arrive before the expected access +* records. +*/ static int audit_count_records (int audit_fd,struct audit_records *records) {struct audit_message msg;
@@ -393,6 +402,16 @@ static int audit_init(void) 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 generate records 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:
tools/testing/selftests/landlock/audit_test.c +0 -2
@@ -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
tools/testing/selftests/landlock/ptrace_test.c +0 -1
@@ -342,7 +342,6 @@ TEST_F(audit, trace)/* Makes sure there is no superfluous logged records. */ EXPECT_EQ (0 ,audit_count_records (self->audit_fd, &records));EXPECT_EQ (0 , records.access); -EXPECT_EQ (0 , records.domain); yama_ptrace_scope =get_yama_ptrace_scope ();ASSERT_LE (0 , yama_ptrace_scope);
tools/testing/selftests/landlock/scoped_abstract_unix_test.c +0 -1
@@ -312,7 +312,6 @@ TEST_F(scoped_audit, connect_to_child)/* Makes sure there is no superfluous logged records. */ EXPECT_EQ (0 ,audit_count_records (self->audit_fd, &records));EXPECT_EQ (0 , records.access); -EXPECT_EQ (0 , records.domain);ASSERT_EQ (0 ,pipe2 (pipe_child,O_CLOEXEC ));ASSERT_EQ (0 ,pipe2 (pipe_parent,O_CLOEXEC ));
Signature · 1 line
2.53.0
Re: [PATCH v3 3/5] selftests/landlock: Drain stale audit records on init
Quoted 131 lines · Mickaël Salaün
Non-audit Landlock tests generate audit records as side effects when
audit_enabledis 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-usecSO_RCVTIMEOcausesaudit_recv()to return-EAGAINonce 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 that are not preceded by
audit_match_record()calls, which would otherwise consume stale records before the count. Document this constraint aboveaudit_count_records().Increasing the drain timeout to catch in-flight deallocation records was considered but rejected: a longer timeout adds latency to every
audit_init()call even when no stale record is pending, and any fixed timeout is still not guaranteed to catch all records under load. Removing the unprotected checks is simpler and avoids the spurious failures.Cc Günther Noack <[email protected]>Fixes 6a500b22971c ("selftests/landlock: Add tests for audit flags and domain IDs")Signed-off-by Mickaël Salaün <[email protected]>---
Changes since v1:
https://lore.kernel.org/r/[email protected]
- Also remove domain checks from audit.trace and scoped_audit.connect_to_child.
- Document records.domain == 0 constraint aboveaudit_count_records().
- Explain why a longer drain timeout was rejected.
- Drop Reviewed-by (new code comment not in v1).
- Split snprintf and fd leak fixes into separate patches.--- tools/testing/selftests/landlock/audit.h | 19 +++++++++++++++++++ tools/testing/selftests/landlock/audit_test.c | 2 -- .../testing/selftests/landlock/ptrace_test.c | 1 - .../landlock/scoped_abstract_unix_test.c | 1 - 4 files changed, 19 insertions(+), 4 deletions(-)tools/testing/selftests/landlock/audit.h +19
-0@@ -338,6 +338,15 @@ struct audit_records {size_t domain; }; +/* +* WARNING: Do not assert records.domain == 0 without a preceding +* audit_match_record() call. Domain deallocation records are emitted +* asynchronously from kworker threads and can arrive after the drain in +* audit_init(), corrupting the domain count. A preceding audit_match_record() +* call consumes stale records while scanning, making the assertion safe in +* practice because stale deallocation records arrive before the expected access +* records. +*/ static int audit_count_records (int audit_fd,struct audit_records *records) {struct audit_message msg;@@ -393,6 +402,16 @@ static int audit_init(void) 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 generate records 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:tools/testing/selftests/landlock/audit_test.c +0
-2@@ -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 tools/testing/selftests/landlock/ptrace_test.c +0
-1@@ -342,7 +342,6 @@ TEST_F(audit, trace)/* Makes sure there is no superfluous logged records. */ EXPECT_EQ (0 ,audit_count_records (self->audit_fd, &records));EXPECT_EQ (0 , records.access); -EXPECT_EQ (0 , records.domain); yama_ptrace_scope =get_yama_ptrace_scope ();ASSERT_LE (0 , yama_ptrace_scope);tools/testing/selftests/landlock/scoped_abstract_unix_test.c +0
-1@@ -312,7 +312,6 @@ TEST_F(scoped_audit, connect_to_child)/* Makes sure there is no superfluous logged records. */ EXPECT_EQ (0 ,audit_count_records (self->audit_fd, &records));EXPECT_EQ (0 , records.access); -EXPECT_EQ (0 , records.domain);ASSERT_EQ (0 ,pipe2 (pipe_child,O_CLOEXEC ));ASSERT_EQ (0 ,pipe2 (pipe_parent,O_CLOEXEC ));Signature · 1 line
2.53.0
Mickaël Salaün
maintainerPATCH v3 4/5 selftests/landlock: Skip stale records in audit_match_record()
In reply to Mickaël SalaünDomain deallocation records are emitted asynchronously from kworker threads (via free_ruleset_work()). Stale deallocation records from a previous test can arrive during the current test's deallocation read loop and be picked up by audit_match_record() instead of the expected record, causing a domain ID mismatch. The audit.layers test (which creates 16 nested domains) is particularly vulnerable because it reads 16 deallocation records in sequence, providing a large window for stale records to interleave.
The same issue affects audit_flags.signal, where deallocation records from a previous test (audit.layers) can leak into the next test and be picked up by audit_match_record() instead of the expected record.
Fix this by continuing to read records when the type matches but the content pattern does not. Stale records are silently consumed, and the loop only stops when both type and pattern match (or the socket times out with -EAGAIN).
Additionally, extend matches_log_domain_deallocated() with an expected_domain_id parameter. When set, the regex pattern includes the specific domain ID as a literal hex value, so that deallocation records for a different domain do not match the pattern at all. This handles the case where the stale record has the same denial count as the expected one (e.g. both have denials=1), which the type+pattern loop alone cannot distinguish. Callers that already know the expected domain ID (from a prior denial or allocation record) now pass it to filter precisely.
When expected_domain_id is set, matches_log_domain_deallocated() also temporarily increases the socket timeout to audit_tv_dom_drop (1 second) to wait for the asynchronous kworker deallocation, and restores audit_tv_default afterward. This removes the need for callers to manage the timeout switch manually.
---
Changes since v2:
https://lore.kernel.org/r/[email protected]
- Fix __u64 format warnings on powerpc64 (cast to unsigned long long for %llx).
Changes since v1:
https://lore.kernel.org/r/[email protected]
- New patch.
--- tools/testing/selftests/landlock/audit.h | 82 ++++++++++++++----- tools/testing/selftests/landlock/audit_test.c | 34 ++++---- 2 files changed, 77 insertions(+), 39 deletions(-)
tools/testing/selftests/landlock/audit.h +62 -20
@@ -249,9 +249,9 @@ static __maybe_unused char *regex_escape(const char *const src, char *dst,static int audit_match_record (int audit_fd,const __u16 type,const char *const pattern,__u64 *domain_id) { -struct audit_message msg; +struct audit_message msg, last_mismatch = {};int ret, err =0 ; -bool matches_record = !type; +int num_type_match =0 ;regmatch_t matches[2 ];regex_t regex;
@@ -259,21 +259,35 @@ static int audit_match_record(int audit_fd, const __u16 type,if (ret)return -EINVAL ; -do { +/* +* Reads records until one matches both the expected type and the +* pattern. Type-matching records with non-matching content are +* silently consumed, which handles stale domain deallocation records +* from a previous test emitted asynchronously by kworker threads. +*/ +while (true) {memset (&msg,0 ,sizeof (msg)); err =audit_recv (audit_fd, &msg); -if (err) +if (err) { +if (num_type_match) { +printf ("DATA: %s\n" , last_mismatch.data); +printf ("ERROR: %d record(s) matched type %u" +" but not pattern: %s\n" , + num_type_match, type, pattern); + } goto out; + } -if (msg.header.nlmsg_type == type) - matches_record = true; - }while (!matches_record); +if (type && msg.header.nlmsg_type != type) +continue ; - ret =regexec (®ex, msg.data,ARRAY_SIZE (matches), matches,0 ); -if (ret) { -printf ("DATA: %s\n" , msg.data); -printf ("ERROR: no match for pattern: %s\n" , pattern); - err = -ENOENT ; + ret =regexec (®ex, msg.data,ARRAY_SIZE (matches), matches, +0 ); +if (!ret) +break ; + + num_type_match++; + last_mismatch = msg; }if (domain_id) {
@@ -316,21 +330,49 @@ static int __maybe_unused matches_log_domain_allocated(int audit_fd, pid_t pid, domain_id); } -static int __maybe_unused matches_log_domain_deallocated ( -int audit_fd,unsigned int num_denials,__u64 *domain_id) +/* +* Matches a domain deallocation record. When expected_domain_id is non-zero, +* the pattern includes the specific domain ID so that stale deallocation +* records from a previous test (with a different domain ID) are skipped by +* audit_match_record(), and the socket timeout is temporarily increased to +* audit_tv_dom_drop to wait for the asynchronous kworker deallocation. +*/ +static int __maybe_unused +matches_log_domain_deallocated (int audit_fd,unsigned int num_denials, +__u64 expected_domain_id,__u64 *domain_id) {static const char log_template[] =REGEX_LANDLOCK_PREFIX " status=deallocated denials=%u$" ; -char log_match[sizeof (log_template) +10 ]; -int log_match_len; +static const char log_template_with_id[] = +"^audit([0-9.:]\\+): domain=\\(%llx\\)" +" status=deallocated denials=%u$" ; +char log_match[sizeof (log_template_with_id) +32 ]; +int log_match_len, err; + +if (expected_domain_id) + log_match_len =snprintf (log_match,sizeof (log_match), + log_template_with_id, + (unsigned long long )expected_domain_id, + num_denials); +else + log_match_len =snprintf (log_match,sizeof (log_match), + log_template, num_denials); - log_match_len =snprintf (log_match,sizeof (log_match), log_template, - num_denials);if (log_match_len >=sizeof (log_match))return -E2BIG ; -return audit_match_record (audit_fd,AUDIT_LANDLOCK_DOMAIN , log_match, - domain_id); +if (expected_domain_id) +setsockopt (audit_fd,SOL_SOCKET ,SO_RCVTIMEO , + &audit_tv_dom_drop,sizeof (audit_tv_dom_drop)); + + err =audit_match_record (audit_fd,AUDIT_LANDLOCK_DOMAIN , log_match, + domain_id); + +if (expected_domain_id) +setsockopt (audit_fd,SOL_SOCKET ,SO_RCVTIMEO , &audit_tv_default, +sizeof (audit_tv_default)); + +return err; }struct audit_records {
tools/testing/selftests/landlock/audit_test.c +15 -19
@@ -139,23 +139,24 @@ TEST_F(audit, layers) WEXITSTATUS(status) != EXIT_SUCCESS) _metadata->exit_code =KSFT_FAIL ; -/* Purges log from deallocated domains. */ -EXPECT_EQ (0 ,setsockopt (self->audit_fd,SOL_SOCKET ,SO_RCVTIMEO , - &audit_tv_dom_drop,sizeof (audit_tv_dom_drop))); +/* +* Purges log from deallocated domains. Records arrive in LIFO order +* (innermost domain first) because landlock_put_hierarchy() walks the +* chain sequentially in a single kworker context. +*/ for (i =ARRAY_SIZE (*domain_stack) -1 ; i >=0 ; i--) {__u64 deallocated_dom =2 ;EXPECT_EQ (0 ,matches_log_domain_deallocated (self->audit_fd,1 , + (*domain_stack)[i], &deallocated_dom)); EXPECT_EQ((*domain_stack)[i], deallocated_dom) {TH_LOG ("Failed to match domain %llx (#%d)" , - (*domain_stack)[i], i); + (unsigned long long )(*domain_stack)[i], i); } }EXPECT_EQ (0 ,munmap (domain_stack,sizeof (*domain_stack))); -EXPECT_EQ (0 ,setsockopt (self->audit_fd,SOL_SOCKET ,SO_RCVTIMEO , - &audit_tv_default,sizeof (audit_tv_default)));EXPECT_EQ (0 ,close (ruleset_fd)); }
@@ -270,13 +271,9 @@ TEST_F(audit, thread)EXPECT_EQ (0 ,close (pipe_parent[1 ]));ASSERT_EQ (0 ,pthread_join (thread,NULL )); -EXPECT_EQ (0 ,setsockopt (self->audit_fd,SOL_SOCKET ,SO_RCVTIMEO , - &audit_tv_dom_drop,sizeof (audit_tv_dom_drop))); -EXPECT_EQ (0 ,matches_log_domain_deallocated (self->audit_fd,1 , - &deallocated_dom)); +EXPECT_EQ (0 ,matches_log_domain_deallocated ( + self->audit_fd,1 , denial_dom, &deallocated_dom));EXPECT_EQ (denial_dom, deallocated_dom); -EXPECT_EQ (0 ,setsockopt (self->audit_fd,SOL_SOCKET ,SO_RCVTIMEO , - &audit_tv_default,sizeof (audit_tv_default))); }FIXTURE (audit_flags)
@@ -432,22 +429,21 @@ TEST_F(audit_flags, signal)if (variant->restrict_flags &LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF ) { +/* +* No deallocation record: denials=0 never matches a real +* record. +*/ EXPECT_EQ (-EAGAIN , -matches_log_domain_deallocated (self->audit_fd,0 , +matches_log_domain_deallocated (self->audit_fd,0 ,0 , &deallocated_dom));EXPECT_EQ (deallocated_dom,2 ); }else { -EXPECT_EQ (0 ,setsockopt (self->audit_fd,SOL_SOCKET ,SO_RCVTIMEO , - &audit_tv_dom_drop, -sizeof (audit_tv_dom_drop)));EXPECT_EQ (0 ,matches_log_domain_deallocated (self->audit_fd,2 , + *self->domain_id, &deallocated_dom));EXPECT_NE (deallocated_dom,2 );EXPECT_NE (deallocated_dom,0 );EXPECT_EQ (deallocated_dom, *self->domain_id); -EXPECT_EQ (0 ,setsockopt (self->audit_fd,SOL_SOCKET ,SO_RCVTIMEO , - &audit_tv_default, -sizeof (audit_tv_default))); } }
Signature · 1 line
2.53.0
Mickaël Salaün
maintainerPATCH v3 5/5 selftests/landlock: Fix format warning for __u64 in net_test
In reply to Mickaël SalaünOn architectures where __u64 is unsigned long (e.g. powerpc64), using %llx to format a __u64 triggers a -Wformat warning because %llx expects unsigned long long. Cast the argument to unsigned long long.
---
Changes since v2:
- New patch.
--- tools/testing/selftests/landlock/net_test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
tools/testing/selftests/landlock/net_test.c +1 -1
@@ -1356,7 +1356,7 @@ TEST_F(mini, network_access_rights) &net_port, 0)) {TH_LOG ("Failed to add rule with access 0x%llx: %s" , - access,strerror (errno)); + (unsigned long long )access,strerror (errno)); } }EXPECT_EQ (0 ,close (ruleset_fd));
Signature · 1 line
2.53.0
Re: [PATCH v3 5/5] selftests/landlock: Fix format warning for __u64 in net_test
Quoted 34 lines · Mickaël Salaün
On architectures where
__u64is unsigned long (e.g. powerpc64), using %llx to format a__u64triggers a -Wformat warning because %llx expects unsigned long long. Cast the argument to unsigned long long.Cc Günther Noack <[email protected]>Fixes a549d055a22e ("selftests/landlock: Add network tests")Reported-by kernel test robot <[email protected]>Signed-off-by Mickaël Salaün <[email protected]>---
Changes since v2:
- New patch.--- tools/testing/selftests/landlock/net_test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)tools/testing/selftests/landlock/net_test.c +1
-1@@ -1356,7 +1356,7 @@ TEST_F(mini, network_access_rights) &net_port, 0)) {TH_LOG ("Failed to add rule with access 0x%llx: %s" , - access,strerror (errno)); + (unsigned long long )access,strerror (errno)); } }EXPECT_EQ (0 ,close (ruleset_fd));Signature · 1 line
2.53.0
Hello!
Quoted 59 lines · Mickaël Salaün
This series fixes two classes of audit selftest failures plus two minor bugs in the audit test helpers.
The main issue is that domain deallocation audit records are emitted asynchronously from kworker threads and can arrive after a previous test's socket has been closed. This causes two distinct failure modes:
-
audit_match_record()picks up a stale deallocation record from a previous test instead of the expected one, causing a domain ID mismatch. The audit.layers test (which reads 16 deallocation records in sequence) is particularly vulnerable because the large read window allows stale records to interleave. Patch 4 fixes this by filtering deallocation records by domain ID and skipping type-matching records with wrong content patterns.-
audit_count_records()counts stale deallocation records from a previous test, incrementing records.domain from the expected 0 to 1. Patch 3 fixes this by draining stale records ataudit_init()time and removing records.domain == 0 checks that are not preceded byaudit_match_record()calls (which would consume stale records).These races are more likely to manifest when additional instrumentation changes kworker timing in the deallocation path (e.g. with the upcoming Landlock tracepoints work).
The two minor fixes (patches 1-2) correct a snprintf truncation check off-by-one and socket file descriptor leaks on error paths in
audit_init(),audit_init_with_exe_filter(), andaudit_cleanup(). Patch 5 fixes a__u64format warning reported by the kbuild bot on powerpc64.Patch 1 is an exact subset of the v1 combined patch, which is why it carries the Reviewed-by tag. Patches 2 and 3 extend beyond what was in v1, so the Reviewed-by is not carried. Patches 4 and 5 are new.
Changes since v2:
https://lore.kernel.org/r/[email protected]
- Patches 4-5: fix__u64format warnings on powerpc64 (cast to unsigned long long for %llx). Patch 5 is new.Changes since v1:
https://lore.kernel.org/r/[email protected]
- Split the combined drain fix into four separate patches.
- Patch 2: extend fd leak fix toaudit_init_with_exe_filter()andaudit_cleanup().
- Patch 3: also remove domain checks from audit.trace and scoped_audit.connect_to_child, document constraint, explain why a longer drain timeout was rejected.
- Patch 4: new, add domain ID filtering and timeout management tomatches_log_domain_deallocated(), skip stale records inaudit_match_record().Mickaël Salaün (5):
selftests/landlock: Fix snprintf truncation checks in audit helpers selftests/landlock: Fix socket file descriptor leaks in audit helpers selftests/landlock: Drain stale audit records on init selftests/landlock: Skip stale records inaudit_match_record()selftests/landlock: Fix format warning for__u64innet_test
Quoted 10 lines
tools/testing/selftests/landlock/audit.h | 133 ++++++++++++++---- tools/testing/selftests/landlock/audit_test.c | 36 ++--- tools/testing/selftests/landlock/net_test.c | 2 +- .../testing/selftests/landlock/ptrace_test.c | 1 - .../landlock/scoped_abstract_unix_test.c | 1 - 5 files changed, 119 insertions(+), 54 deletions(-)Signature · 1 line
2.53.0
I am still getting flaky audit tests even with these patches, I am afraid. It differs which of these tests is flaking, some of them still do, for example:
# RUN audit_layout1.remove_dir ...
# fs_test.c:7281:remove_dir:Expected 0 (0) == matches_log_fs(_metadata, self->audit_fd, "fs\\.remove_dir", dir_s1d2) (-11) # remove_dir: Test failed
# ❌ FAIL audit_layout1.remove_dir
not ok 191 audit_layout1.remove_dir
# RUN audit_layout1.read_dir ...
# ✅ OK audit_layout1.read_dir
ok 192 audit_layout1.read_dir
# RUN audit_layout1.read_file ...
# ✅ OK audit_layout1.read_file
ok 193 audit_layout1.read_file
# RUN audit_layout1.write_file ...
# fs_test.c:7221:write_file:Expected 0 (0) == matches_log_fs(_metadata, self->audit_fd, "fs\\.write_file", file1_s1d1) (-11) # fs_test.c:7224:write_file:Expected 0 (0) == records.access (1)
# write_file: Test failed
# ❌ FAIL audit_layout1.write_file
not ok 194 audit_layout1.write_file
My kernel config is this:
make defconfig
make kvm_guest.config
KCONFIG_CONFIG="${KBUILD_OUTPUT}/.config" ./scripts/kconfig/merge_config.sh "${KBUILD_OUTPUT}/.config" tools/testing/selftests/landlock/config
make debug.config
echo "CONFIG_RANDOMIZE_BASE=n" >> "${KBUILD_OUTPUT}/.config"
make olddefconfigand then I run the selftests in Qemu with these flags:
qemu-system-x86_64 \
-nographic \
-m 4G \
-enable-kvm \
-append "console=ttyS0 lsm=landlock no_hash_pointers" \
-kernel "${KBUILD_OUTPUT}/arch/x86/boot/bzImage" \
-initrd "${INITRAMFS}"This is using my own selftest runner scripts which builds an initramfs with the statically linked selftests.
Do you have a hunch what might be missing there? In the test run above, I have applied your V4 patch set on top of the current master, 5619b098e2fbf3a23bf13d91897056a1fe238c6d ("Merge tag 'for-7.0-rc6-tag' of git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux").
–Günther
Quoted 8 lines · Günther Noack
My kernel config is this:
make defconfig make kvm_guest.config KCONFIG_CONFIG="${KBUILD_OUTPUT}/.config" ./scripts/kconfig/merge_config.sh "${KBUILD_OUTPUT}/.config" tools/testing/selftests/landlock/config make debug.config echo "CONFIG_RANDOMIZE_BASE=n" >> "${KBUILD_OUTPUT}/.config" make olddefconfig
P.S.: I should point out, everytime that I have observed these flakiness problems with the audit tests, it was in this debug config. I suspect that it adds delays in a way that makes it more likely.
–Günther
Mickaël Salaün
maintainerQuoted 86 lines · Günther Noack
Hello!
Quoted 59 lines · Mickaël Salaün
This series fixes two classes of audit selftest failures plus two minor bugs in the audit test helpers.
The main issue is that domain deallocation audit records are emitted asynchronously from kworker threads and can arrive after a previous test's socket has been closed. This causes two distinct failure modes:
-
audit_match_record()picks up a stale deallocation record from a previous test instead of the expected one, causing a domain ID mismatch. The audit.layers test (which reads 16 deallocation records in sequence) is particularly vulnerable because the large read window allows stale records to interleave. Patch 4 fixes this by filtering deallocation records by domain ID and skipping type-matching records with wrong content patterns.-
audit_count_records()counts stale deallocation records from a previous test, incrementing records.domain from the expected 0 to 1. Patch 3 fixes this by draining stale records ataudit_init()time and removing records.domain == 0 checks that are not preceded byaudit_match_record()calls (which would consume stale records).These races are more likely to manifest when additional instrumentation changes kworker timing in the deallocation path (e.g. with the upcoming Landlock tracepoints work).
The two minor fixes (patches 1-2) correct a snprintf truncation check off-by-one and socket file descriptor leaks on error paths in
audit_init(),audit_init_with_exe_filter(), andaudit_cleanup(). Patch 5 fixes a__u64format warning reported by the kbuild bot on powerpc64.Patch 1 is an exact subset of the v1 combined patch, which is why it carries the Reviewed-by tag. Patches 2 and 3 extend beyond what was in v1, so the Reviewed-by is not carried. Patches 4 and 5 are new.
Changes since v2:
https://lore.kernel.org/r/[email protected]
- Patches 4-5: fix__u64format warnings on powerpc64 (cast to unsigned long long for %llx). Patch 5 is new.Changes since v1:
https://lore.kernel.org/r/[email protected]
- Split the combined drain fix into four separate patches.
- Patch 2: extend fd leak fix toaudit_init_with_exe_filter()andaudit_cleanup().
- Patch 3: also remove domain checks from audit.trace and scoped_audit.connect_to_child, document constraint, explain why a longer drain timeout was rejected.
- Patch 4: new, add domain ID filtering and timeout management tomatches_log_domain_deallocated(), skip stale records inaudit_match_record().Mickaël Salaün (5):
selftests/landlock: Fix snprintf truncation checks in audit helpers selftests/landlock: Fix socket file descriptor leaks in audit helpers selftests/landlock: Drain stale audit records on init selftests/landlock: Skip stale records inaudit_match_record()selftests/landlock: Fix format warning for__u64innet_testQuoted 10 lines
tools/testing/selftests/landlock/audit.h | 133 ++++++++++++++---- tools/testing/selftests/landlock/audit_test.c | 36 ++--- tools/testing/selftests/landlock/net_test.c | 2 +- .../testing/selftests/landlock/ptrace_test.c | 1 - .../landlock/scoped_abstract_unix_test.c | 1 - 5 files changed, 119 insertions(+), 54 deletions(-)Signature · 1 line
2.53.0I am still getting flaky audit tests even with these patches, I am afraid. It differs which of these tests is flaking, some of them still do, for example:
# RUN audit_layout1.remove_dir ...
# fs_test.c:7281:remove_dir:Expected 0 (0) ==matches_log_fs(_metadata, self->audit_fd, "fs\\.remove_dir", dir_s1d2)(-11) #remove_dir: Test failed
# ❌ FAIL audit_layout1.remove_dir
not ok 191 audit_layout1.remove_dir
# RUN audit_layout1.read_dir ...
# ✅ OK audit_layout1.read_dir
ok 192 audit_layout1.read_dir
# RUN audit_layout1.read_file ...
Quoted 8 lines
# ✅ OK audit_layout1.read_file
ok 193 audit_layout1.read_file
# RUN audit_layout1.write_file ...
# fs_test.c:7221:write_file:Expected 0 (0) ==matches_log_fs(_metadata, self->audit_fd, "fs\\.write_file", file1_s1d1)(-11) # fs_test.c:7224:write_file:Expected 0 (0) == records.access (1)
#write_file: Test failed
# ❌ FAIL audit_layout1.write_file
not ok 194 audit_layout1.write_file
I never hit these issues and I cannot reproduce them. This patch fixes the async events (i.e. domain drops).
You can try to increase audit_tv_default.
Quoted 20 lines
My kernel config is this:
make defconfig make kvm_guest.config KCONFIG_CONFIG="${KBUILD_OUTPUT}/.config" ./scripts/kconfig/merge_config.sh "${KBUILD_OUTPUT}/.config" tools/testing/selftests/landlock/config make debug.config echo "CONFIG_RANDOMIZE_BASE=n" >> "${KBUILD_OUTPUT}/.config" make olddefconfigand then I run the selftests in Qemu with these flags:
qemu-system-x86_64 \ -nographic \ -m 4G \ -enable-kvm \ -append "console=ttyS0 lsm=landlock no_hash_pointers" \ -kernel "${KBUILD_OUTPUT}/arch/x86/boot/bzImage" \ -initrd "${INITRAMFS}"
Quoted 2 lines
This is using my own selftest runner scripts which builds an initramfs with the statically linked selftests.
Can you try with the check-linux.sh build kselftest (which also set a lot of debug options)? You can also try with qemu if you set ARCH=x86_64
Quoted 5 lines
Do you have a hunch what might be missing there? In the test run above, I have applied your V4 patch set on top of the current master,
5619b098e2fbf3a23bf13d91897056a1fe238c6d("Merge tag 'for-7.0-rc6-tag' ofgit://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux").
This is weird because this is related to FS events, and they should be (almost) synchronous events. Maybe the audit event pipeline is made very slow because of some audit options but still...
Anyway, this is not what this patch fixes, but we should fix your issues as well.