@@ -162,6 +162,7 @@ TEST_F(audit, layers)
162162struct thread_data {
163163 pid_t parent_pid ;
164164 int ruleset_fd , pipe_child , pipe_parent ;
165+ bool mute_subdomains ;
165166};
166167
167168static void * thread_audit_test (void * arg )
@@ -367,6 +368,238 @@ TEST_F(audit, log_subdomains_off_fork)
367368 EXPECT_EQ (0 , close (ruleset_fd ));
368369}
369370
371+ /*
372+ * Thread function: runs two rounds of (create domain, trigger denial, signal
373+ * back), waiting for the main thread before each round. When mute_subdomains
374+ * is set, phase 1 also mutes subdomain logs via the fd=-1 path before creating
375+ * the domain. The ruleset_fd is kept open across both rounds so each
376+ * restrict_self call stacks a new domain layer.
377+ */
378+ static void * thread_sandbox_deny_twice (void * arg )
379+ {
380+ const struct thread_data * data = (struct thread_data * )arg ;
381+ uintptr_t err = 0 ;
382+ char buffer ;
383+
384+ /* Phase 1: optionally mutes, creates a domain, and triggers a denial. */
385+ if (read (data -> pipe_parent , & buffer , 1 ) != 1 ) {
386+ err = 1 ;
387+ goto out ;
388+ }
389+
390+ if (data -> mute_subdomains &&
391+ landlock_restrict_self (-1 ,
392+ LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF )) {
393+ err = 2 ;
394+ goto out ;
395+ }
396+
397+ if (landlock_restrict_self (data -> ruleset_fd , 0 )) {
398+ err = 3 ;
399+ goto out ;
400+ }
401+
402+ if (kill (data -> parent_pid , 0 ) != -1 || errno != EPERM ) {
403+ err = 4 ;
404+ goto out ;
405+ }
406+
407+ if (write (data -> pipe_child , "." , 1 ) != 1 ) {
408+ err = 5 ;
409+ goto out ;
410+ }
411+
412+ /* Phase 2: stacks another domain and triggers a denial. */
413+ if (read (data -> pipe_parent , & buffer , 1 ) != 1 ) {
414+ err = 6 ;
415+ goto out ;
416+ }
417+
418+ if (landlock_restrict_self (data -> ruleset_fd , 0 )) {
419+ err = 7 ;
420+ goto out ;
421+ }
422+
423+ if (kill (data -> parent_pid , 0 ) != -1 || errno != EPERM ) {
424+ err = 8 ;
425+ goto out ;
426+ }
427+
428+ if (write (data -> pipe_child , "." , 1 ) != 1 ) {
429+ err = 9 ;
430+ goto out ;
431+ }
432+
433+ out :
434+ close (data -> ruleset_fd );
435+ close (data -> pipe_child );
436+ close (data -> pipe_parent );
437+ return (void * )err ;
438+ }
439+
440+ /*
441+ * Verifies that LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF with
442+ * LANDLOCK_RESTRICT_SELF_TSYNC and ruleset_fd=-1 propagates log_subdomains_off
443+ * to a sibling thread, suppressing audit logging on domains it subsequently
444+ * creates.
445+ *
446+ * Phase 1 (before TSYNC) acts as an inline baseline: the sibling creates a
447+ * domain and triggers a denial that IS logged.
448+ *
449+ * Phase 2 (after TSYNC) verifies suppression: the sibling stacks another domain
450+ * and triggers a denial that is NOT logged.
451+ */
452+ TEST_F (audit , log_subdomains_off_tsync )
453+ {
454+ const struct landlock_ruleset_attr ruleset_attr = {
455+ .scoped = LANDLOCK_SCOPE_SIGNAL ,
456+ };
457+ struct audit_records records ;
458+ struct thread_data child_data = {};
459+ int pipe_child [2 ], pipe_parent [2 ];
460+ char buffer ;
461+ pthread_t thread ;
462+ void * thread_ret ;
463+
464+ child_data .parent_pid = getppid ();
465+ ASSERT_EQ (0 , pipe2 (pipe_child , O_CLOEXEC ));
466+ child_data .pipe_child = pipe_child [1 ];
467+ ASSERT_EQ (0 , pipe2 (pipe_parent , O_CLOEXEC ));
468+ child_data .pipe_parent = pipe_parent [0 ];
469+ child_data .ruleset_fd =
470+ landlock_create_ruleset (& ruleset_attr , sizeof (ruleset_attr ), 0 );
471+ ASSERT_LE (0 , child_data .ruleset_fd );
472+
473+ ASSERT_EQ (0 , prctl (PR_SET_NO_NEW_PRIVS , 1 , 0 , 0 , 0 ));
474+
475+ /* Creates the sibling thread. */
476+ ASSERT_EQ (0 , pthread_create (& thread , NULL , thread_sandbox_deny_twice ,
477+ & child_data ));
478+
479+ /*
480+ * Phase 1: the sibling creates a domain and triggers a denial before
481+ * any log muting. This proves the audit path works.
482+ */
483+ ASSERT_EQ (1 , write (pipe_parent [1 ], "." , 1 ));
484+ ASSERT_EQ (1 , read (pipe_child [0 ], & buffer , 1 ));
485+
486+ /* The denial must be logged. */
487+ EXPECT_EQ (0 , matches_log_signal (_metadata , self -> audit_fd ,
488+ child_data .parent_pid , NULL ));
489+
490+ /* Drains any remaining records (e.g. domain allocation). */
491+ EXPECT_EQ (0 , audit_count_records (self -> audit_fd , & records ));
492+
493+ /*
494+ * Mutes subdomain logs and propagates to the sibling thread via TSYNC,
495+ * without creating a domain.
496+ */
497+ ASSERT_EQ (0 , landlock_restrict_self (
498+ -1 , LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF |
499+ LANDLOCK_RESTRICT_SELF_TSYNC ));
500+
501+ /*
502+ * Phase 2: the sibling stacks another domain and triggers a denial.
503+ * Because log_subdomains_off was propagated via TSYNC, the new domain
504+ * has log_status=LANDLOCK_LOG_DISABLED.
505+ */
506+ ASSERT_EQ (1 , write (pipe_parent [1 ], "." , 1 ));
507+ ASSERT_EQ (1 , read (pipe_child [0 ], & buffer , 1 ));
508+
509+ /* No denial record should appear. */
510+ EXPECT_EQ (- EAGAIN , matches_log_signal (_metadata , self -> audit_fd ,
511+ child_data .parent_pid , NULL ));
512+
513+ EXPECT_EQ (0 , audit_count_records (self -> audit_fd , & records ));
514+ EXPECT_EQ (0 , records .access );
515+
516+ EXPECT_EQ (0 , close (pipe_child [0 ]));
517+ EXPECT_EQ (0 , close (pipe_parent [1 ]));
518+ ASSERT_EQ (0 , pthread_join (thread , & thread_ret ));
519+ EXPECT_EQ (NULL , thread_ret );
520+ }
521+
522+ /*
523+ * Verifies that LANDLOCK_RESTRICT_SELF_TSYNC without
524+ * LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF overrides a sibling thread's
525+ * log_subdomains_off, re-enabling audit logging on domains the sibling
526+ * subsequently creates.
527+ *
528+ * Phase 1: the sibling sets log_subdomains_off, creates a muted domain, and
529+ * triggers a denial that is NOT logged.
530+ *
531+ * Phase 2 (after TSYNC without LOG_SUBDOMAINS_OFF): the sibling stacks another
532+ * domain and triggers a denial that IS logged, proving the muting was
533+ * overridden.
534+ */
535+ TEST_F (audit , tsync_override_log_subdomains_off )
536+ {
537+ const struct landlock_ruleset_attr ruleset_attr = {
538+ .scoped = LANDLOCK_SCOPE_SIGNAL ,
539+ };
540+ struct audit_records records ;
541+ struct thread_data child_data = {};
542+ int pipe_child [2 ], pipe_parent [2 ];
543+ char buffer ;
544+ pthread_t thread ;
545+ void * thread_ret ;
546+
547+ child_data .parent_pid = getppid ();
548+ ASSERT_EQ (0 , pipe2 (pipe_child , O_CLOEXEC ));
549+ child_data .pipe_child = pipe_child [1 ];
550+ ASSERT_EQ (0 , pipe2 (pipe_parent , O_CLOEXEC ));
551+ child_data .pipe_parent = pipe_parent [0 ];
552+ child_data .ruleset_fd =
553+ landlock_create_ruleset (& ruleset_attr , sizeof (ruleset_attr ), 0 );
554+ ASSERT_LE (0 , child_data .ruleset_fd );
555+
556+ ASSERT_EQ (0 , prctl (PR_SET_NO_NEW_PRIVS , 1 , 0 , 0 , 0 ));
557+
558+ child_data .mute_subdomains = true;
559+
560+ /* Creates the sibling thread. */
561+ ASSERT_EQ (0 , pthread_create (& thread , NULL , thread_sandbox_deny_twice ,
562+ & child_data ));
563+
564+ /*
565+ * Phase 1: the sibling mutes subdomain logs, creates a domain, and
566+ * triggers a denial. The denial must not be logged.
567+ */
568+ ASSERT_EQ (1 , write (pipe_parent [1 ], "." , 1 ));
569+ ASSERT_EQ (1 , read (pipe_child [0 ], & buffer , 1 ));
570+
571+ EXPECT_EQ (- EAGAIN , matches_log_signal (_metadata , self -> audit_fd ,
572+ child_data .parent_pid , NULL ));
573+
574+ /* Drains any remaining records. */
575+ EXPECT_EQ (0 , audit_count_records (self -> audit_fd , & records ));
576+ EXPECT_EQ (0 , records .access );
577+
578+ /*
579+ * Overrides the sibling's log_subdomains_off by calling TSYNC without
580+ * LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF.
581+ */
582+ ASSERT_EQ (0 , landlock_restrict_self (child_data .ruleset_fd ,
583+ LANDLOCK_RESTRICT_SELF_TSYNC ));
584+
585+ /*
586+ * Phase 2: the sibling stacks another domain and triggers a denial.
587+ * Because TSYNC replaced its log_subdomains_off with 0, the new domain
588+ * has log_status=LANDLOCK_LOG_PENDING.
589+ */
590+ ASSERT_EQ (1 , write (pipe_parent [1 ], "." , 1 ));
591+ ASSERT_EQ (1 , read (pipe_child [0 ], & buffer , 1 ));
592+
593+ /* The denial must be logged. */
594+ EXPECT_EQ (0 , matches_log_signal (_metadata , self -> audit_fd ,
595+ child_data .parent_pid , NULL ));
596+
597+ EXPECT_EQ (0 , close (pipe_child [0 ]));
598+ EXPECT_EQ (0 , close (pipe_parent [1 ]));
599+ ASSERT_EQ (0 , pthread_join (thread , & thread_ret ));
600+ EXPECT_EQ (NULL , thread_ret );
601+ }
602+
370603FIXTURE (audit_flags )
371604{
372605 struct audit_filter audit_filter ;
0 commit comments