12651712aSMickaël Salaün // SPDX-License-Identifier: GPL-2.0 22651712aSMickaël Salaün /* 32651712aSMickaël Salaün * Landlock tests - Tracepoints 42651712aSMickaël Salaün * 52651712aSMickaël Salaün * Copyright © 2026 Cloudflare, Inc. 62651712aSMickaël Salaün */ 72651712aSMickaël Salaün 82651712aSMickaël Salaün #define _GNU_SOURCE 92651712aSMickaël Salaün #include <errno.h> 102651712aSMickaël Salaün #include <fcntl.h> 112651712aSMickaël Salaün #include <linux/landlock.h> 12*ac6d193dSMickaël Salaün #include <pthread.h> 132651712aSMickaël Salaün #include <sched.h> 142651712aSMickaël Salaün #include <stdio.h> 152651712aSMickaël Salaün #include <string.h> 162651712aSMickaël Salaün #include <sys/mount.h> 172651712aSMickaël Salaün #include <sys/stat.h> 182651712aSMickaël Salaün #include <sys/types.h> 192651712aSMickaël Salaün #include <sys/wait.h> 202651712aSMickaël Salaün #include <unistd.h> 212651712aSMickaël Salaün 222651712aSMickaël Salaün #include "common.h" 232651712aSMickaël Salaün #include "trace.h" 242651712aSMickaël Salaün 252651712aSMickaël Salaün #define TRACE_TASK "trace_test" 262651712aSMickaël Salaün 272651712aSMickaël Salaün /* clang-format off */ 282651712aSMickaël Salaün FIXTURE(trace) { 292651712aSMickaël Salaün /* clang-format on */ 302651712aSMickaël Salaün int tracefs_ok; 312651712aSMickaël Salaün }; 322651712aSMickaël Salaün 332651712aSMickaël Salaün FIXTURE_SETUP(trace) 342651712aSMickaël Salaün { 352651712aSMickaël Salaün int ret; 362651712aSMickaël Salaün 372651712aSMickaël Salaün set_cap(_metadata, CAP_SYS_ADMIN); 382651712aSMickaël Salaün ASSERT_EQ(0, unshare(CLONE_NEWNS)); 392651712aSMickaël Salaün ASSERT_EQ(0, mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL)); 402651712aSMickaël Salaün 412651712aSMickaël Salaün ret = tracefs_fixture_setup(); 422651712aSMickaël Salaün if (ret) { 432651712aSMickaël Salaün clear_cap(_metadata, CAP_SYS_ADMIN); 442651712aSMickaël Salaün self->tracefs_ok = 0; 452651712aSMickaël Salaün SKIP(return, "tracefs not available"); 462651712aSMickaël Salaün } 472651712aSMickaël Salaün self->tracefs_ok = 1; 482651712aSMickaël Salaün 492651712aSMickaël Salaün ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, true)); 502651712aSMickaël Salaün ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, true)); 51*ac6d193dSMickaël Salaün ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, true)); 522651712aSMickaël Salaün ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, true)); 532651712aSMickaël Salaün ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_NET_ENABLE, true)); 542651712aSMickaël Salaün ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, true)); 552651712aSMickaël Salaün ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, true)); 562651712aSMickaël Salaün ASSERT_EQ(0, tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, true)); 572651712aSMickaël Salaün ASSERT_EQ(0, 582651712aSMickaël Salaün tracefs_enable_event(TRACEFS_DENY_ACCESS_NET_ENABLE, true)); 592651712aSMickaël Salaün ASSERT_EQ(0, tracefs_enable_event(TRACEFS_FREE_DOMAIN_ENABLE, true)); 602651712aSMickaël Salaün ASSERT_EQ(0, tracefs_enable_event(TRACEFS_FREE_RULESET_ENABLE, true)); 612651712aSMickaël Salaün ASSERT_EQ(0, tracefs_clear()); 622651712aSMickaël Salaün clear_cap(_metadata, CAP_SYS_ADMIN); 632651712aSMickaël Salaün } 642651712aSMickaël Salaün 652651712aSMickaël Salaün FIXTURE_TEARDOWN(trace) 662651712aSMickaël Salaün { 672651712aSMickaël Salaün if (!self->tracefs_ok) 682651712aSMickaël Salaün return; 692651712aSMickaël Salaün 702651712aSMickaël Salaün /* Disables landlock events and clears PID filter. */ 712651712aSMickaël Salaün set_cap(_metadata, CAP_SYS_ADMIN); 722651712aSMickaël Salaün tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, false); 732651712aSMickaël Salaün tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, false); 74*ac6d193dSMickaël Salaün tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, false); 752651712aSMickaël Salaün tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, false); 762651712aSMickaël Salaün tracefs_enable_event(TRACEFS_ADD_RULE_NET_ENABLE, false); 772651712aSMickaël Salaün tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, false); 782651712aSMickaël Salaün tracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, false); 792651712aSMickaël Salaün tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, false); 802651712aSMickaël Salaün tracefs_enable_event(TRACEFS_DENY_ACCESS_NET_ENABLE, false); 812651712aSMickaël Salaün tracefs_enable_event(TRACEFS_FREE_DOMAIN_ENABLE, false); 822651712aSMickaël Salaün tracefs_enable_event(TRACEFS_FREE_RULESET_ENABLE, false); 832651712aSMickaël Salaün tracefs_clear_pid_filter(); 842651712aSMickaël Salaün clear_cap(_metadata, CAP_SYS_ADMIN); 852651712aSMickaël Salaün 862651712aSMickaël Salaün /* 872651712aSMickaël Salaün * The mount namespace is cleaned up automatically when the test process 882651712aSMickaël Salaün * (harness child) exits. 892651712aSMickaël Salaün */ 902651712aSMickaël Salaün } 912651712aSMickaël Salaün 922651712aSMickaël Salaün /* 932651712aSMickaël Salaün * Verifies that no trace events are emitted when the tracepoints are disabled. 942651712aSMickaël Salaün */ 952651712aSMickaël Salaün TEST_F(trace, no_trace_when_disabled) 962651712aSMickaël Salaün { 972651712aSMickaël Salaün char *buf; 982651712aSMickaël Salaün 992651712aSMickaël Salaün /* Disable all landlock events. */ 1002651712aSMickaël Salaün set_cap(_metadata, CAP_SYS_ADMIN); 1012651712aSMickaël Salaün ASSERT_EQ(0, 1022651712aSMickaël Salaün tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, false)); 1032651712aSMickaël Salaün ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, false)); 104*ac6d193dSMickaël Salaün ASSERT_EQ(0, 105*ac6d193dSMickaël Salaün tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, false)); 1062651712aSMickaël Salaün ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_FS_ENABLE, false)); 1072651712aSMickaël Salaün ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_NET_ENABLE, false)); 1082651712aSMickaël Salaün ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CHECK_RULE_FS_ENABLE, false)); 1092651712aSMickaël Salaün ASSERT_EQ(0, 1102651712aSMickaël Salaün tracefs_enable_event(TRACEFS_CHECK_RULE_NET_ENABLE, false)); 1112651712aSMickaël Salaün ASSERT_EQ(0, 1122651712aSMickaël Salaün tracefs_enable_event(TRACEFS_DENY_ACCESS_FS_ENABLE, false)); 1132651712aSMickaël Salaün ASSERT_EQ(0, 1142651712aSMickaël Salaün tracefs_enable_event(TRACEFS_DENY_ACCESS_NET_ENABLE, false)); 1152651712aSMickaël Salaün ASSERT_EQ(0, tracefs_enable_event(TRACEFS_DENY_PTRACE_ENABLE, false)); 1162651712aSMickaël Salaün ASSERT_EQ(0, tracefs_enable_event(TRACEFS_DENY_SCOPE_SIGNAL_ENABLE, 1172651712aSMickaël Salaün false)); 1182651712aSMickaël Salaün ASSERT_EQ(0, tracefs_enable_event( 1192651712aSMickaël Salaün TRACEFS_DENY_SCOPE_ABSTRACT_UNIX_SOCKET_ENABLE, 1202651712aSMickaël Salaün false)); 1212651712aSMickaël Salaün ASSERT_EQ(0, tracefs_enable_event(TRACEFS_FREE_DOMAIN_ENABLE, false)); 1222651712aSMickaël Salaün ASSERT_EQ(0, tracefs_enable_event(TRACEFS_FREE_RULESET_ENABLE, false)); 1232651712aSMickaël Salaün ASSERT_EQ(0, tracefs_clear()); 1242651712aSMickaël Salaün clear_cap(_metadata, CAP_SYS_ADMIN); 1252651712aSMickaël Salaün 1262651712aSMickaël Salaün /* 1272651712aSMickaël Salaün * Trigger both allowed and denied accesses to verify neither check_rule 1282651712aSMickaël Salaün * nor check_access events fire when disabled. 1292651712aSMickaël Salaün */ 1302651712aSMickaël Salaün sandbox_child_fs_access(_metadata, "/usr", LANDLOCK_ACCESS_FS_READ_DIR, 1312651712aSMickaël Salaün LANDLOCK_ACCESS_FS_READ_DIR, "/tmp"); 1322651712aSMickaël Salaün 1332651712aSMickaël Salaün /* Read trace buffer and verify no landlock events at all. */ 1342651712aSMickaël Salaün buf = tracefs_read_buf(); 1352651712aSMickaël Salaün ASSERT_NE(NULL, buf); 1362651712aSMickaël Salaün 1372651712aSMickaël Salaün EXPECT_EQ(0, tracefs_count_matches(buf, "landlock_")) 1382651712aSMickaël Salaün { 1392651712aSMickaël Salaün TH_LOG("Expected 0 landlock events when disabled\n%s", buf); 1402651712aSMickaël Salaün } 1412651712aSMickaël Salaün 1422651712aSMickaël Salaün free(buf); 1432651712aSMickaël Salaün } 1442651712aSMickaël Salaün 1452651712aSMickaël Salaün /* 1462651712aSMickaël Salaün * Verifies that landlock_create_ruleset emits a trace event with the correct 1472651712aSMickaël Salaün * handled access masks. 1482651712aSMickaël Salaün */ 1492651712aSMickaël Salaün TEST_F(trace, create_ruleset) 1502651712aSMickaël Salaün { 1512651712aSMickaël Salaün struct landlock_ruleset_attr ruleset_attr = { 1522651712aSMickaël Salaün .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, 1532651712aSMickaël Salaün .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP, 1542651712aSMickaël Salaün }; 1552651712aSMickaël Salaün int ruleset_fd; 1562651712aSMickaël Salaün char *buf, *dot; 1572651712aSMickaël Salaün char field[64]; 1582651712aSMickaël Salaün 1592651712aSMickaël Salaün ruleset_fd = 1602651712aSMickaël Salaün landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); 1612651712aSMickaël Salaün ASSERT_LE(0, ruleset_fd); 1622651712aSMickaël Salaün ASSERT_EQ(0, close(ruleset_fd)); 1632651712aSMickaël Salaün 1642651712aSMickaël Salaün buf = tracefs_read_buf(); 1652651712aSMickaël Salaün ASSERT_NE(NULL, buf); 1662651712aSMickaël Salaün 1672651712aSMickaël Salaün EXPECT_EQ(1, 1682651712aSMickaël Salaün tracefs_count_matches(buf, REGEX_CREATE_RULESET(TRACE_TASK))) 1692651712aSMickaël Salaün { 1702651712aSMickaël Salaün TH_LOG("Expected 1 create_ruleset event\n%s", buf); 1712651712aSMickaël Salaün } 1722651712aSMickaël Salaün 1732651712aSMickaël Salaün /* Verify handled_fs matches what we requested. */ 1742651712aSMickaël Salaün EXPECT_EQ(0, 1752651712aSMickaël Salaün tracefs_extract_field(buf, REGEX_CREATE_RULESET(TRACE_TASK), 1762651712aSMickaël Salaün "handled_fs", field, sizeof(field))); 1772651712aSMickaël Salaün EXPECT_STREQ("read_file", field); 1782651712aSMickaël Salaün 1792651712aSMickaël Salaün /* Verify handled_net matches. */ 1802651712aSMickaël Salaün EXPECT_EQ(0, 1812651712aSMickaël Salaün tracefs_extract_field(buf, REGEX_CREATE_RULESET(TRACE_TASK), 1822651712aSMickaël Salaün "handled_net", field, sizeof(field))); 1832651712aSMickaël Salaün EXPECT_STREQ("bind_tcp", field); 1842651712aSMickaël Salaün 1852651712aSMickaël Salaün /* Verify version is 0 at creation (no rules added yet). */ 1862651712aSMickaël Salaün EXPECT_EQ(0, 1872651712aSMickaël Salaün tracefs_extract_field(buf, REGEX_CREATE_RULESET(TRACE_TASK), 1882651712aSMickaël Salaün "ruleset", field, sizeof(field))); 1892651712aSMickaël Salaün /* Format is <hex>.<dec>; version is after the dot. */ 1902651712aSMickaël Salaün dot = strchr(field, '.'); 1912651712aSMickaël Salaün ASSERT_NE(0, !!dot); 1922651712aSMickaël Salaün EXPECT_STREQ("0", dot + 1); 1932651712aSMickaël Salaün 1942651712aSMickaël Salaün free(buf); 1952651712aSMickaël Salaün } 1962651712aSMickaël Salaün 1972651712aSMickaël Salaün /* 1982651712aSMickaël Salaün * Verifies that the ruleset version increments with each add_rule call and that 1992651712aSMickaël Salaün * create_domain records the correct version. 2002651712aSMickaël Salaün */ 2012651712aSMickaël Salaün TEST_F(trace, ruleset_version) 2022651712aSMickaël Salaün { 2032651712aSMickaël Salaün pid_t pid; 2042651712aSMickaël Salaün int status; 2052651712aSMickaël Salaün char *buf; 2062651712aSMickaël Salaün const char *dot; 2072651712aSMickaël Salaün char field[64]; 2082651712aSMickaël Salaün 2092651712aSMickaël Salaün ASSERT_EQ(0, tracefs_clear_buf()); 2102651712aSMickaël Salaün 2112651712aSMickaël Salaün pid = fork(); 2122651712aSMickaël Salaün ASSERT_LE(0, pid); 2132651712aSMickaël Salaün 2142651712aSMickaël Salaün if (pid == 0) { 2152651712aSMickaël Salaün struct landlock_ruleset_attr ruleset_attr = { 2162651712aSMickaël Salaün .handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR, 2172651712aSMickaël Salaün }; 2182651712aSMickaël Salaün struct landlock_path_beneath_attr path_beneath = { 2192651712aSMickaël Salaün .allowed_access = LANDLOCK_ACCESS_FS_READ_DIR, 2202651712aSMickaël Salaün }; 2212651712aSMickaël Salaün int ruleset_fd; 2222651712aSMickaël Salaün 2232651712aSMickaël Salaün ruleset_fd = landlock_create_ruleset(&ruleset_attr, 2242651712aSMickaël Salaün sizeof(ruleset_attr), 0); 2252651712aSMickaël Salaün if (ruleset_fd < 0) 2262651712aSMickaël Salaün _exit(1); 2272651712aSMickaël Salaün 2282651712aSMickaël Salaün /* First rule: version becomes 1. */ 2292651712aSMickaël Salaün path_beneath.parent_fd = 2302651712aSMickaël Salaün open("/usr", O_PATH | O_DIRECTORY | O_CLOEXEC); 2312651712aSMickaël Salaün if (path_beneath.parent_fd < 0) 2322651712aSMickaël Salaün _exit(1); 2332651712aSMickaël Salaün landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, 2342651712aSMickaël Salaün &path_beneath, 0); 2352651712aSMickaël Salaün close(path_beneath.parent_fd); 2362651712aSMickaël Salaün 2372651712aSMickaël Salaün /* Second rule: version becomes 2. */ 2382651712aSMickaël Salaün path_beneath.parent_fd = 2392651712aSMickaël Salaün open("/tmp", O_PATH | O_DIRECTORY | O_CLOEXEC); 2402651712aSMickaël Salaün if (path_beneath.parent_fd < 0) 2412651712aSMickaël Salaün _exit(1); 2422651712aSMickaël Salaün landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, 2432651712aSMickaël Salaün &path_beneath, 0); 2442651712aSMickaël Salaün close(path_beneath.parent_fd); 2452651712aSMickaël Salaün 2462651712aSMickaël Salaün prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); 2472651712aSMickaël Salaün if (landlock_restrict_self(ruleset_fd, 0)) 2482651712aSMickaël Salaün _exit(1); 2492651712aSMickaël Salaün close(ruleset_fd); 2502651712aSMickaël Salaün _exit(0); 2512651712aSMickaël Salaün } 2522651712aSMickaël Salaün 2532651712aSMickaël Salaün ASSERT_EQ(pid, waitpid(pid, &status, 0)); 2542651712aSMickaël Salaün ASSERT_TRUE(WIFEXITED(status)); 2552651712aSMickaël Salaün EXPECT_EQ(0, WEXITSTATUS(status)); 2562651712aSMickaël Salaün 2572651712aSMickaël Salaün buf = tracefs_read_buf(); 2582651712aSMickaël Salaün ASSERT_NE(NULL, buf); 2592651712aSMickaël Salaün 2602651712aSMickaël Salaün /* Verify create_ruleset has version=0. */ 2612651712aSMickaël Salaün ASSERT_EQ(0, 2622651712aSMickaël Salaün tracefs_extract_field(buf, REGEX_CREATE_RULESET(TRACE_TASK), 2632651712aSMickaël Salaün "ruleset", field, sizeof(field))); 2642651712aSMickaël Salaün dot = strchr(field, '.'); 2652651712aSMickaël Salaün ASSERT_NE(0, !!dot); 2662651712aSMickaël Salaün EXPECT_STREQ("0", dot + 1); 2672651712aSMickaël Salaün 2682651712aSMickaël Salaün /* Verify 2 add_rule_fs events were emitted. */ 2692651712aSMickaël Salaün EXPECT_EQ(2, tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK))) 2702651712aSMickaël Salaün { 2712651712aSMickaël Salaün TH_LOG("Expected 2 add_rule_fs events\n%s", buf); 2722651712aSMickaël Salaün } 2732651712aSMickaël Salaün 2742651712aSMickaël Salaün /* 2752651712aSMickaël Salaün * Verify create_domain records version=2 (after 2 add_rule calls). The 2762651712aSMickaël Salaün * ruleset field format is <hex_id>.<dec_version>. 2772651712aSMickaël Salaün */ 2782651712aSMickaël Salaün ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_CREATE_DOMAIN(TRACE_TASK), 2792651712aSMickaël Salaün "ruleset", field, sizeof(field))); 2802651712aSMickaël Salaün dot = strchr(field, '.'); 2812651712aSMickaël Salaün ASSERT_NE(0, !!dot); 2822651712aSMickaël Salaün EXPECT_STREQ("2", dot + 1); 2832651712aSMickaël Salaün 2842651712aSMickaël Salaün free(buf); 2852651712aSMickaël Salaün } 2862651712aSMickaël Salaün 2872651712aSMickaël Salaün /* 2882651712aSMickaël Salaün * Verifies that landlock_create_domain emits a trace event linking the ruleset 2892651712aSMickaël Salaün * ID to the new domain ID. 2902651712aSMickaël Salaün */ 2912651712aSMickaël Salaün TEST_F(trace, create_domain) 2922651712aSMickaël Salaün { 2932651712aSMickaël Salaün pid_t pid; 2942651712aSMickaël Salaün int status, check_count; 2952651712aSMickaël Salaün char *buf; 2962651712aSMickaël Salaün char parent_id[64], domain_id[64], check_domain[64]; 2972651712aSMickaël Salaün 2982651712aSMickaël Salaün /* Clear before the sandboxed child. */ 2992651712aSMickaël Salaün ASSERT_EQ(0, tracefs_clear_buf()); 3002651712aSMickaël Salaün 3012651712aSMickaël Salaün pid = fork(); 3022651712aSMickaël Salaün ASSERT_LE(0, pid); 3032651712aSMickaël Salaün 3042651712aSMickaël Salaün if (pid == 0) { 3052651712aSMickaël Salaün struct landlock_ruleset_attr ruleset_attr = { 3062651712aSMickaël Salaün .handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR, 3072651712aSMickaël Salaün }; 3082651712aSMickaël Salaün struct landlock_path_beneath_attr path_beneath = { 3092651712aSMickaël Salaün .allowed_access = LANDLOCK_ACCESS_FS_READ_DIR, 3102651712aSMickaël Salaün }; 3112651712aSMickaël Salaün int ruleset_fd, fd; 3122651712aSMickaël Salaün 3132651712aSMickaël Salaün ruleset_fd = landlock_create_ruleset(&ruleset_attr, 3142651712aSMickaël Salaün sizeof(ruleset_attr), 0); 3152651712aSMickaël Salaün if (ruleset_fd < 0) 3162651712aSMickaël Salaün _exit(1); 3172651712aSMickaël Salaün 3182651712aSMickaël Salaün path_beneath.parent_fd = 3192651712aSMickaël Salaün open("/usr", O_PATH | O_DIRECTORY | O_CLOEXEC); 3202651712aSMickaël Salaün if (path_beneath.parent_fd < 0) 3212651712aSMickaël Salaün _exit(1); 3222651712aSMickaël Salaün 3232651712aSMickaël Salaün landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, 3242651712aSMickaël Salaün &path_beneath, 0); 3252651712aSMickaël Salaün close(path_beneath.parent_fd); 3262651712aSMickaël Salaün 3272651712aSMickaël Salaün prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); 3282651712aSMickaël Salaün if (landlock_restrict_self(ruleset_fd, 0)) 3292651712aSMickaël Salaün _exit(1); 3302651712aSMickaël Salaün close(ruleset_fd); 3312651712aSMickaël Salaün 3322651712aSMickaël Salaün /* Trigger a check_rule to verify domain_id correlation. */ 3332651712aSMickaël Salaün fd = open("/usr", O_RDONLY | O_DIRECTORY | O_CLOEXEC); 3342651712aSMickaël Salaün if (fd >= 0) 3352651712aSMickaël Salaün close(fd); 3362651712aSMickaël Salaün 3372651712aSMickaël Salaün _exit(0); 3382651712aSMickaël Salaün } 3392651712aSMickaël Salaün 3402651712aSMickaël Salaün ASSERT_EQ(pid, waitpid(pid, &status, 0)); 3412651712aSMickaël Salaün ASSERT_TRUE(WIFEXITED(status)); 3422651712aSMickaël Salaün EXPECT_EQ(0, WEXITSTATUS(status)); 3432651712aSMickaël Salaün 3442651712aSMickaël Salaün buf = tracefs_read_buf(); 3452651712aSMickaël Salaün ASSERT_NE(NULL, buf); 3462651712aSMickaël Salaün 3472651712aSMickaël Salaün /* Verify create_domain event exists. */ 3482651712aSMickaël Salaün EXPECT_EQ(1, 3492651712aSMickaël Salaün tracefs_count_matches(buf, REGEX_CREATE_DOMAIN(TRACE_TASK))) 3502651712aSMickaël Salaün { 3512651712aSMickaël Salaün TH_LOG("Expected 1 create_domain event\n%s", buf); 3522651712aSMickaël Salaün } 3532651712aSMickaël Salaün 3542651712aSMickaël Salaün /* Extract the domain ID from create_domain. */ 3552651712aSMickaël Salaün EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_CREATE_DOMAIN(TRACE_TASK), 3562651712aSMickaël Salaün "domain", domain_id, 3572651712aSMickaël Salaün sizeof(domain_id))); 3582651712aSMickaël Salaün 3592651712aSMickaël Salaün /* Verify domain ID is non-zero. */ 3602651712aSMickaël Salaün EXPECT_NE(0, strcmp(domain_id, "0")); 3612651712aSMickaël Salaün 3622651712aSMickaël Salaün /* Verify parent=0 (first restriction, no prior domain). */ 3632651712aSMickaël Salaün EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_CREATE_DOMAIN(TRACE_TASK), 3642651712aSMickaël Salaün "parent", parent_id, 3652651712aSMickaël Salaün sizeof(parent_id))); 3662651712aSMickaël Salaün EXPECT_STREQ("0", parent_id); 3672651712aSMickaël Salaün 3682651712aSMickaël Salaün /* 3692651712aSMickaël Salaün * Verify the same domain ID appears in the check_rule event, confirming 3702651712aSMickaël Salaün * end-to-end correlation. 3712651712aSMickaël Salaün */ 3722651712aSMickaël Salaün check_count = 3732651712aSMickaël Salaün tracefs_count_matches(buf, REGEX_CHECK_RULE_FS(TRACE_TASK)); 3742651712aSMickaël Salaün ASSERT_LE(1, check_count) 3752651712aSMickaël Salaün { 3762651712aSMickaël Salaün TH_LOG("Expected check_rule_fs events\n%s", buf); 3772651712aSMickaël Salaün } 3782651712aSMickaël Salaün 3792651712aSMickaël Salaün EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_CHECK_RULE_FS(TRACE_TASK), 3802651712aSMickaël Salaün "domain", check_domain, 3812651712aSMickaël Salaün sizeof(check_domain))); 3822651712aSMickaël Salaün EXPECT_STREQ(domain_id, check_domain); 3832651712aSMickaël Salaün 3842651712aSMickaël Salaün free(buf); 3852651712aSMickaël Salaün } 3862651712aSMickaël Salaün 3872651712aSMickaël Salaün /* Builds a rule-less scope-based ruleset; returns the fd or -1. */ 3882651712aSMickaël Salaün static int build_enforce_ruleset(void) 3892651712aSMickaël Salaün { 3902651712aSMickaël Salaün const struct landlock_ruleset_attr attr = { 3912651712aSMickaël Salaün .scoped = LANDLOCK_SCOPE_SIGNAL, 3922651712aSMickaël Salaün }; 3932651712aSMickaël Salaün 3942651712aSMickaël Salaün return landlock_create_ruleset(&attr, sizeof(attr), 0); 3952651712aSMickaël Salaün } 3962651712aSMickaël Salaün 3972651712aSMickaël Salaün /* 3982651712aSMickaël Salaün * Verifies that nested landlock_restrict_self calls produce trace events with 3992651712aSMickaël Salaün * correct parent domain IDs: the second create_domain's parent should be the 4002651712aSMickaël Salaün * first domain's ID. 4012651712aSMickaël Salaün */ 4022651712aSMickaël Salaün TEST_F(trace, create_domain_nested) 4032651712aSMickaël Salaün { 4042651712aSMickaël Salaün pid_t pid; 4052651712aSMickaël Salaün int status; 4062651712aSMickaël Salaün char *buf; 4072651712aSMickaël Salaün const char *after_first; 4082651712aSMickaël Salaün char first_domain[64], first_parent[64], second_parent[64]; 4092651712aSMickaël Salaün 4102651712aSMickaël Salaün ASSERT_EQ(0, tracefs_clear_buf()); 4112651712aSMickaël Salaün 4122651712aSMickaël Salaün pid = fork(); 4132651712aSMickaël Salaün ASSERT_LE(0, pid); 4142651712aSMickaël Salaün 4152651712aSMickaël Salaün if (pid == 0) { 4162651712aSMickaël Salaün int ruleset_fd; 4172651712aSMickaël Salaün 4182651712aSMickaël Salaün /* First restriction. */ 4192651712aSMickaël Salaün ruleset_fd = build_enforce_ruleset(); 4202651712aSMickaël Salaün if (ruleset_fd < 0) 4212651712aSMickaël Salaün _exit(1); 4222651712aSMickaël Salaün prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); 4232651712aSMickaël Salaün if (landlock_restrict_self(ruleset_fd, 0)) 4242651712aSMickaël Salaün _exit(1); 4252651712aSMickaël Salaün close(ruleset_fd); 4262651712aSMickaël Salaün 4272651712aSMickaël Salaün /* Second restriction (nested). */ 4282651712aSMickaël Salaün ruleset_fd = build_enforce_ruleset(); 4292651712aSMickaël Salaün if (ruleset_fd < 0) 4302651712aSMickaël Salaün _exit(1); 4312651712aSMickaël Salaün if (landlock_restrict_self(ruleset_fd, 0)) 4322651712aSMickaël Salaün _exit(1); 4332651712aSMickaël Salaün close(ruleset_fd); 4342651712aSMickaël Salaün 4352651712aSMickaël Salaün _exit(0); 4362651712aSMickaël Salaün } 4372651712aSMickaël Salaün 4382651712aSMickaël Salaün ASSERT_EQ(pid, waitpid(pid, &status, 0)); 4392651712aSMickaël Salaün ASSERT_TRUE(WIFEXITED(status)); 4402651712aSMickaël Salaün EXPECT_EQ(0, WEXITSTATUS(status)); 4412651712aSMickaël Salaün 4422651712aSMickaël Salaün buf = tracefs_read_buf(); 4432651712aSMickaël Salaün ASSERT_NE(NULL, buf); 4442651712aSMickaël Salaün 4452651712aSMickaël Salaün /* Should have 2 create_domain events. */ 4462651712aSMickaël Salaün EXPECT_EQ(2, 4472651712aSMickaël Salaün tracefs_count_matches(buf, REGEX_CREATE_DOMAIN(TRACE_TASK))) 4482651712aSMickaël Salaün { 4492651712aSMickaël Salaün TH_LOG("Expected 2 create_domain events\n%s", buf); 4502651712aSMickaël Salaün } 4512651712aSMickaël Salaün 4522651712aSMickaël Salaün /* 4532651712aSMickaël Salaün * Extract domain and parent from each create_domain event. The first 4542651712aSMickaël Salaün * event (parent=0) is the outer domain; the second (parent!=0) is the 4552651712aSMickaël Salaün * nested domain whose parent should match the first domain's ID. 4562651712aSMickaël Salaün */ 4572651712aSMickaël Salaün ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_CREATE_DOMAIN(TRACE_TASK), 4582651712aSMickaël Salaün "domain", first_domain, 4592651712aSMickaël Salaün sizeof(first_domain))); 4602651712aSMickaël Salaün ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_CREATE_DOMAIN(TRACE_TASK), 4612651712aSMickaël Salaün "parent", first_parent, 4622651712aSMickaël Salaün sizeof(first_parent))); 4632651712aSMickaël Salaün EXPECT_STREQ("0", first_parent); 4642651712aSMickaël Salaün 4652651712aSMickaël Salaün /* 4662651712aSMickaël Salaün * Find the second create_domain by scanning past the first. 4672651712aSMickaël Salaün * tracefs_extract_field returns the first match, so search in the 4682651712aSMickaël Salaün * buffer after the first event. 4692651712aSMickaël Salaün * 4702651712aSMickaël Salaün * Skip past the first create_domain line. tracefs_extract_field matches 4712651712aSMickaël Salaün * the first line that matches the regex, so passing the buffer after 4722651712aSMickaël Salaün * the first matching line gives us the second event. 4732651712aSMickaël Salaün */ 4742651712aSMickaël Salaün after_first = strstr(buf, "landlock_create_domain:"); 4752651712aSMickaël Salaün ASSERT_NE(NULL, after_first); 4762651712aSMickaël Salaün after_first = strchr(after_first, '\n'); 4772651712aSMickaël Salaün ASSERT_NE(NULL, after_first); 4782651712aSMickaël Salaün 4792651712aSMickaël Salaün ASSERT_EQ(0, tracefs_extract_field( 4802651712aSMickaël Salaün after_first + 1, REGEX_CREATE_DOMAIN(TRACE_TASK), 4812651712aSMickaël Salaün "parent", second_parent, sizeof(second_parent))); 4822651712aSMickaël Salaün 4832651712aSMickaël Salaün /* The second domain's parent should be the first domain's ID. */ 4842651712aSMickaël Salaün EXPECT_STREQ(first_domain, second_parent); 4852651712aSMickaël Salaün 4862651712aSMickaël Salaün free(buf); 4872651712aSMickaël Salaün } 4882651712aSMickaël Salaün 4892651712aSMickaël Salaün /* 4902651712aSMickaël Salaün * Verifies that landlock_add_rule does not emit a trace event when the syscall 4912651712aSMickaël Salaün * fails (e.g., invalid ruleset fd). 4922651712aSMickaël Salaün */ 4932651712aSMickaël Salaün TEST_F(trace, add_rule_invalid_fd) 4942651712aSMickaël Salaün { 4952651712aSMickaël Salaün struct landlock_path_beneath_attr path_beneath = { 4962651712aSMickaël Salaün .allowed_access = LANDLOCK_ACCESS_FS_READ_FILE, 4972651712aSMickaël Salaün }; 4982651712aSMickaël Salaün char *buf; 4992651712aSMickaël Salaün 5002651712aSMickaël Salaün path_beneath.parent_fd = open("/usr", O_PATH | O_DIRECTORY | O_CLOEXEC); 5012651712aSMickaël Salaün ASSERT_LE(0, path_beneath.parent_fd); 5022651712aSMickaël Salaün 5032651712aSMickaël Salaün /* Invalid ruleset fd (-1). */ 5042651712aSMickaël Salaün ASSERT_EQ(-1, landlock_add_rule(-1, LANDLOCK_RULE_PATH_BENEATH, 5052651712aSMickaël Salaün &path_beneath, 0)); 5062651712aSMickaël Salaün ASSERT_EQ(0, close(path_beneath.parent_fd)); 5072651712aSMickaël Salaün 5082651712aSMickaël Salaün buf = tracefs_read_buf(); 5092651712aSMickaël Salaün ASSERT_NE(NULL, buf); 5102651712aSMickaël Salaün 5112651712aSMickaël Salaün EXPECT_EQ(0, tracefs_count_matches(buf, REGEX_ADD_RULE_FS(TRACE_TASK))) 5122651712aSMickaël Salaün { 5132651712aSMickaël Salaün TH_LOG("No add_rule_fs event expected on invalid fd\n%s", buf); 5142651712aSMickaël Salaün } 5152651712aSMickaël Salaün 5162651712aSMickaël Salaün free(buf); 5172651712aSMickaël Salaün } 5182651712aSMickaël Salaün 5192651712aSMickaël Salaün /* 5202651712aSMickaël Salaün * Verifies that landlock_create_domain does not emit a trace event when the 5212651712aSMickaël Salaün * syscall fails (e.g., invalid ruleset fd or unknown flags). 5222651712aSMickaël Salaün */ 5232651712aSMickaël Salaün TEST_F(trace, create_domain_invalid) 5242651712aSMickaël Salaün { 5252651712aSMickaël Salaün int ruleset_fd; 5262651712aSMickaël Salaün char *buf; 5272651712aSMickaël Salaün 5282651712aSMickaël Salaün ruleset_fd = build_enforce_ruleset(); 5292651712aSMickaël Salaün ASSERT_LE(0, ruleset_fd); 5302651712aSMickaël Salaün 5312651712aSMickaël Salaün /* Clear the trace buffer after create_ruleset event. */ 5322651712aSMickaël Salaün ASSERT_EQ(0, tracefs_clear_buf()); 5332651712aSMickaël Salaün 5342651712aSMickaël Salaün /* Invalid fd. */ 5352651712aSMickaël Salaün ASSERT_EQ(-1, landlock_restrict_self(-1, 0)); 5362651712aSMickaël Salaün 5372651712aSMickaël Salaün /* Unknown flags. */ 5382651712aSMickaël Salaün ASSERT_EQ(-1, landlock_restrict_self(ruleset_fd, -1)); 5392651712aSMickaël Salaün 5402651712aSMickaël Salaün ASSERT_EQ(0, close(ruleset_fd)); 5412651712aSMickaël Salaün 5422651712aSMickaël Salaün buf = tracefs_read_buf(); 5432651712aSMickaël Salaün ASSERT_NE(NULL, buf); 5442651712aSMickaël Salaün 5452651712aSMickaël Salaün EXPECT_EQ(0, 5462651712aSMickaël Salaün tracefs_count_matches(buf, REGEX_CREATE_DOMAIN(TRACE_TASK))) 5472651712aSMickaël Salaün { 5482651712aSMickaël Salaün TH_LOG("No create_domain event expected on error\n%s", buf); 5492651712aSMickaël Salaün } 5502651712aSMickaël Salaün 5512651712aSMickaël Salaün free(buf); 5522651712aSMickaël Salaün } 5532651712aSMickaël Salaün 5542651712aSMickaël Salaün /* 5552651712aSMickaël Salaün * Verifies that trace_landlock_free_domain fires when a domain is deallocated, 5562651712aSMickaël Salaün * with the correct denials count. 5572651712aSMickaël Salaün */ 5582651712aSMickaël Salaün TEST_F(trace, free_domain) 5592651712aSMickaël Salaün { 5602651712aSMickaël Salaün char *buf; 5612651712aSMickaël Salaün int count; 5622651712aSMickaël Salaün char denials_field[32]; 5632651712aSMickaël Salaün 5642651712aSMickaël Salaün ASSERT_EQ(0, tracefs_clear_buf()); 5652651712aSMickaël Salaün 5662651712aSMickaël Salaün /* 5672651712aSMickaël Salaün * The domain is freed via a work queue (kworker), so the free_domain 5682651712aSMickaël Salaün * trace event is emitted from a different PID. Clear the PID filter 5692651712aSMickaël Salaün * BEFORE the child exits, so the kworker event passes the filter when 5702651712aSMickaël Salaün * it fires. 5712651712aSMickaël Salaün */ 5722651712aSMickaël Salaün set_cap(_metadata, CAP_SYS_ADMIN); 5732651712aSMickaël Salaün tracefs_clear_pid_filter(); 5742651712aSMickaël Salaün clear_cap(_metadata, CAP_SYS_ADMIN); 5752651712aSMickaël Salaün 5762651712aSMickaël Salaün sandbox_child_fs_access(_metadata, "/usr", LANDLOCK_ACCESS_FS_READ_DIR, 5772651712aSMickaël Salaün LANDLOCK_ACCESS_FS_READ_DIR, "/tmp"); 5782651712aSMickaël Salaün 5792651712aSMickaël Salaün /* 5802651712aSMickaël Salaün * Wait for the deferred deallocation work to run. The domain is freed 5812651712aSMickaël Salaün * asynchronously from a kworker; poll until the event appears or a 5822651712aSMickaël Salaün * timeout is reached. 5832651712aSMickaël Salaün */ 5842651712aSMickaël Salaün for (int retry = 0; retry < 10; retry++) { 5852651712aSMickaël Salaün usleep(100000); 5862651712aSMickaël Salaün 5872651712aSMickaël Salaün set_cap(_metadata, CAP_SYS_ADMIN); 5882651712aSMickaël Salaün buf = tracefs_read_trace(); 5892651712aSMickaël Salaün clear_cap(_metadata, CAP_SYS_ADMIN); 5902651712aSMickaël Salaün ASSERT_NE(NULL, buf); 5912651712aSMickaël Salaün 5922651712aSMickaël Salaün count = tracefs_count_matches(buf, 5932651712aSMickaël Salaün REGEX_FREE_DOMAIN(KWORKER_TASK)); 5942651712aSMickaël Salaün if (count >= 1) 5952651712aSMickaël Salaün break; 5962651712aSMickaël Salaün free(buf); 5972651712aSMickaël Salaün buf = NULL; 5982651712aSMickaël Salaün } 5992651712aSMickaël Salaün 6002651712aSMickaël Salaün set_cap(_metadata, CAP_SYS_ADMIN); 6012651712aSMickaël Salaün ASSERT_EQ(0, tracefs_set_pid_filter(getpid())); 6022651712aSMickaël Salaün clear_cap(_metadata, CAP_SYS_ADMIN); 6032651712aSMickaël Salaün 6042651712aSMickaël Salaün ASSERT_NE(NULL, buf); 6052651712aSMickaël Salaün EXPECT_LE(1, count) 6062651712aSMickaël Salaün { 6072651712aSMickaël Salaün TH_LOG("Expected free_domain event, got %d\n%s", count, buf); 6082651712aSMickaël Salaün } 6092651712aSMickaël Salaün 6102651712aSMickaël Salaün /* Verify denials count matches the single denial we triggered. */ 6112651712aSMickaël Salaün EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_FREE_DOMAIN(KWORKER_TASK), 6122651712aSMickaël Salaün "denials", denials_field, 6132651712aSMickaël Salaün sizeof(denials_field))); 6142651712aSMickaël Salaün EXPECT_STREQ("1", denials_field); 6152651712aSMickaël Salaün 6162651712aSMickaël Salaün free(buf); 6172651712aSMickaël Salaün } 6182651712aSMickaël Salaün 6192651712aSMickaël Salaün /* 6202651712aSMickaël Salaün * Verifies that deny_access_fs includes the enriched fields: same_exec and 6212651712aSMickaël Salaün * logged. 6222651712aSMickaël Salaün */ 6232651712aSMickaël Salaün TEST_F(trace, deny_access_fs_fields) 6242651712aSMickaël Salaün { 6252651712aSMickaël Salaün char *buf; 6262651712aSMickaël Salaün char field_buf[64]; 6272651712aSMickaël Salaün 6282651712aSMickaël Salaün ASSERT_EQ(0, tracefs_clear_buf()); 6292651712aSMickaël Salaün 6302651712aSMickaël Salaün /* Trigger a denial: rule for /usr, access /tmp. */ 6312651712aSMickaël Salaün sandbox_child_fs_access(_metadata, "/usr", LANDLOCK_ACCESS_FS_READ_DIR, 6322651712aSMickaël Salaün LANDLOCK_ACCESS_FS_READ_DIR, "/tmp"); 6332651712aSMickaël Salaün 6342651712aSMickaël Salaün buf = tracefs_read_buf(); 6352651712aSMickaël Salaün ASSERT_NE(NULL, buf); 6362651712aSMickaël Salaün 6372651712aSMickaël Salaün /* Verify the enriched fields are present and have valid values. */ 6382651712aSMickaël Salaün ASSERT_EQ(0, tracefs_extract_field( 6392651712aSMickaël Salaün buf, REGEX_DENY_ACCESS_FS(TRACE_TASK), "same_exec", 6402651712aSMickaël Salaün field_buf, sizeof(field_buf))); 6412651712aSMickaël Salaün /* Child is the same exec that restricted itself. */ 6422651712aSMickaël Salaün EXPECT_STREQ("1", field_buf); 6432651712aSMickaël Salaün 6442651712aSMickaël Salaün /* Same exec with default flags: audit would log this denial. */ 6452651712aSMickaël Salaün ASSERT_EQ(0, tracefs_extract_field( 6462651712aSMickaël Salaün buf, REGEX_DENY_ACCESS_FS(TRACE_TASK), "logged", 6472651712aSMickaël Salaün field_buf, sizeof(field_buf))); 6482651712aSMickaël Salaün EXPECT_STREQ("1", field_buf); 6492651712aSMickaël Salaün 6502651712aSMickaël Salaün free(buf); 6512651712aSMickaël Salaün } 6522651712aSMickaël Salaün 6532651712aSMickaël Salaün /* 6542651712aSMickaël Salaün * Verifies that same_exec is 1 (true) for denials from the same executable that 6552651712aSMickaël Salaün * called landlock_restrict_self(). 6562651712aSMickaël Salaün */ 6572651712aSMickaël Salaün TEST_F(trace, same_exec_before_exec) 6582651712aSMickaël Salaün { 6592651712aSMickaël Salaün pid_t pid; 6602651712aSMickaël Salaün int status; 6612651712aSMickaël Salaün char *buf; 6622651712aSMickaël Salaün char field[64]; 6632651712aSMickaël Salaün 6642651712aSMickaël Salaün ASSERT_EQ(0, tracefs_clear_buf()); 6652651712aSMickaël Salaün 6662651712aSMickaël Salaün pid = fork(); 6672651712aSMickaël Salaün ASSERT_LE(0, pid); 6682651712aSMickaël Salaün 6692651712aSMickaël Salaün if (pid == 0) { 6702651712aSMickaël Salaün struct landlock_ruleset_attr attr = { 6712651712aSMickaël Salaün .handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR, 6722651712aSMickaël Salaün }; 6732651712aSMickaël Salaün int ruleset_fd, dir_fd; 6742651712aSMickaël Salaün 6752651712aSMickaël Salaün ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0); 6762651712aSMickaël Salaün if (ruleset_fd < 0) 6772651712aSMickaël Salaün _exit(1); 6782651712aSMickaël Salaün 6792651712aSMickaël Salaün /* No rules: all read_dir access is denied. */ 6802651712aSMickaël Salaün prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); 6812651712aSMickaël Salaün if (landlock_restrict_self(ruleset_fd, 0)) 6822651712aSMickaël Salaün _exit(1); 6832651712aSMickaël Salaün close(ruleset_fd); 6842651712aSMickaël Salaün 6852651712aSMickaël Salaün /* Trigger denial without exec (same executable). */ 6862651712aSMickaël Salaün dir_fd = open(".", O_RDONLY | O_DIRECTORY | O_CLOEXEC); 6872651712aSMickaël Salaün if (dir_fd >= 0) 6882651712aSMickaël Salaün close(dir_fd); 6892651712aSMickaël Salaün _exit(0); 6902651712aSMickaël Salaün } 6912651712aSMickaël Salaün 6922651712aSMickaël Salaün ASSERT_EQ(pid, waitpid(pid, &status, 0)); 6932651712aSMickaël Salaün ASSERT_TRUE(WIFEXITED(status)); 6942651712aSMickaël Salaün EXPECT_EQ(0, WEXITSTATUS(status)); 6952651712aSMickaël Salaün 6962651712aSMickaël Salaün buf = tracefs_read_buf(); 6972651712aSMickaël Salaün ASSERT_NE(NULL, buf); 6982651712aSMickaël Salaün 6992651712aSMickaël Salaün /* Should have at least one deny_access_fs denial. */ 7002651712aSMickaël Salaün EXPECT_LE(1, 7012651712aSMickaël Salaün tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK))); 7022651712aSMickaël Salaün 7032651712aSMickaël Salaün /* Verify same_exec=1 (same executable, no exec). */ 7042651712aSMickaël Salaün ASSERT_EQ(0, 7052651712aSMickaël Salaün tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK), 7062651712aSMickaël Salaün "same_exec", field, sizeof(field))); 7072651712aSMickaël Salaün EXPECT_STREQ("1", field); 7082651712aSMickaël Salaün 7092651712aSMickaël Salaün /* Same exec with default flags: audit would log this denial. */ 7102651712aSMickaël Salaün ASSERT_EQ(0, 7112651712aSMickaël Salaün tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK), 7122651712aSMickaël Salaün "logged", field, sizeof(field))); 7132651712aSMickaël Salaün EXPECT_STREQ("1", field); 7142651712aSMickaël Salaün 7152651712aSMickaël Salaün free(buf); 7162651712aSMickaël Salaün } 7172651712aSMickaël Salaün 7182651712aSMickaël Salaün /* 7192651712aSMickaël Salaün * Verifies that same_exec is 0 (false) for denials from a process that has 7202651712aSMickaël Salaün * exec'd a new binary after landlock_restrict_self(). The sandboxed child 7212651712aSMickaël Salaün * exec's true which opens "." and triggers a read_dir denial. Covers the 7222651712aSMickaël Salaün * "trace-only" visibility condition: with same_exec=0 and the default 7232651712aSMickaël Salaün * log_new_exec=0, audit suppresses the denial (logged=0) but the trace event 7242651712aSMickaël Salaün * still fires. 7252651712aSMickaël Salaün */ 7262651712aSMickaël Salaün TEST_F(trace, same_exec_after_exec) 7272651712aSMickaël Salaün { 7282651712aSMickaël Salaün char *buf; 7292651712aSMickaël Salaün char field[64]; 7302651712aSMickaël Salaün 7312651712aSMickaël Salaün ASSERT_EQ(0, tracefs_clear_buf()); 7322651712aSMickaël Salaün 7332651712aSMickaël Salaün sandbox_child_exec_true(_metadata, 0); 7342651712aSMickaël Salaün 7352651712aSMickaël Salaün buf = tracefs_read_buf(); 7362651712aSMickaël Salaün ASSERT_NE(NULL, buf); 7372651712aSMickaël Salaün 7382651712aSMickaël Salaün EXPECT_LE(1, tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS("true"))); 7392651712aSMickaël Salaün 7402651712aSMickaël Salaün /* Verify same_exec=0 (different executable after exec). */ 7412651712aSMickaël Salaün ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS("true"), 7422651712aSMickaël Salaün "same_exec", field, sizeof(field))); 7432651712aSMickaël Salaün EXPECT_STREQ("0", field); 7442651712aSMickaël Salaün 7452651712aSMickaël Salaün /* 7462651712aSMickaël Salaün * same_exec=0 with default log_new_exec=0: audit suppresses (logged=0). 7472651712aSMickaël Salaün */ 7482651712aSMickaël Salaün ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS("true"), 7492651712aSMickaël Salaün "logged", field, sizeof(field))); 7502651712aSMickaël Salaün EXPECT_STREQ("0", field); 7512651712aSMickaël Salaün 7522651712aSMickaël Salaün free(buf); 7532651712aSMickaël Salaün } 7542651712aSMickaël Salaün 7552651712aSMickaël Salaün /* 7562651712aSMickaël Salaün * Verifies that LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF suppresses logging 7572651712aSMickaël Salaün * (logged=0) for a denial from the same executable. 7582651712aSMickaël Salaün */ 7592651712aSMickaël Salaün TEST_F(trace, log_flags_same_exec_off) 7602651712aSMickaël Salaün { 7612651712aSMickaël Salaün pid_t pid; 7622651712aSMickaël Salaün int status; 7632651712aSMickaël Salaün char *buf; 7642651712aSMickaël Salaün char field[64]; 7652651712aSMickaël Salaün 7662651712aSMickaël Salaün ASSERT_EQ(0, tracefs_clear_buf()); 7672651712aSMickaël Salaün 7682651712aSMickaël Salaün pid = fork(); 7692651712aSMickaël Salaün ASSERT_LE(0, pid); 7702651712aSMickaël Salaün 7712651712aSMickaël Salaün if (pid == 0) { 7722651712aSMickaël Salaün struct landlock_ruleset_attr attr = { 7732651712aSMickaël Salaün .handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR, 7742651712aSMickaël Salaün }; 7752651712aSMickaël Salaün int ruleset_fd, dir_fd; 7762651712aSMickaël Salaün 7772651712aSMickaël Salaün ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0); 7782651712aSMickaël Salaün if (ruleset_fd < 0) 7792651712aSMickaël Salaün _exit(1); 7802651712aSMickaël Salaün 7812651712aSMickaël Salaün prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); 7822651712aSMickaël Salaün if (landlock_restrict_self( 7832651712aSMickaël Salaün ruleset_fd, 7842651712aSMickaël Salaün LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF)) 7852651712aSMickaël Salaün _exit(1); 7862651712aSMickaël Salaün close(ruleset_fd); 7872651712aSMickaël Salaün 7882651712aSMickaël Salaün dir_fd = open(".", O_RDONLY | O_DIRECTORY | O_CLOEXEC); 7892651712aSMickaël Salaün if (dir_fd >= 0) 7902651712aSMickaël Salaün close(dir_fd); 7912651712aSMickaël Salaün _exit(0); 7922651712aSMickaël Salaün } 7932651712aSMickaël Salaün 7942651712aSMickaël Salaün ASSERT_EQ(pid, waitpid(pid, &status, 0)); 7952651712aSMickaël Salaün ASSERT_TRUE(WIFEXITED(status)); 7962651712aSMickaël Salaün EXPECT_EQ(0, WEXITSTATUS(status)); 7972651712aSMickaël Salaün 7982651712aSMickaël Salaün buf = tracefs_read_buf(); 7992651712aSMickaël Salaün ASSERT_NE(NULL, buf); 8002651712aSMickaël Salaün 8012651712aSMickaël Salaün EXPECT_LE(1, 8022651712aSMickaël Salaün tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK))); 8032651712aSMickaël Salaün 8042651712aSMickaël Salaün /* Same-exec denial with LOG_SAME_EXEC_OFF: audit suppresses it. */ 8052651712aSMickaël Salaün ASSERT_EQ(0, 8062651712aSMickaël Salaün tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK), 8072651712aSMickaël Salaün "logged", field, sizeof(field))); 8082651712aSMickaël Salaün EXPECT_STREQ("0", field); 8092651712aSMickaël Salaün 8102651712aSMickaël Salaün free(buf); 8112651712aSMickaël Salaün } 8122651712aSMickaël Salaün 8132651712aSMickaël Salaün /* 8142651712aSMickaël Salaün * Verifies that LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON causes a post-exec 8152651712aSMickaël Salaün * denial to be logged (logged=1). The child exec's true so that the denial 8162651712aSMickaël Salaün * comes from a new executable (same_exec=0). 8172651712aSMickaël Salaün */ 8182651712aSMickaël Salaün TEST_F(trace, log_flags_new_exec_on) 8192651712aSMickaël Salaün { 8202651712aSMickaël Salaün char *buf; 8212651712aSMickaël Salaün char field[64]; 8222651712aSMickaël Salaün 8232651712aSMickaël Salaün ASSERT_EQ(0, tracefs_clear_buf()); 8242651712aSMickaël Salaün 8252651712aSMickaël Salaün sandbox_child_exec_true(_metadata, 8262651712aSMickaël Salaün LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON); 8272651712aSMickaël Salaün 8282651712aSMickaël Salaün buf = tracefs_read_buf(); 8292651712aSMickaël Salaün ASSERT_NE(NULL, buf); 8302651712aSMickaël Salaün 8312651712aSMickaël Salaün EXPECT_LE(1, tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS("true"))); 8322651712aSMickaël Salaün 8332651712aSMickaël Salaün ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS("true"), 8342651712aSMickaël Salaün "same_exec", field, sizeof(field))); 8352651712aSMickaël Salaün EXPECT_STREQ("0", field); 8362651712aSMickaël Salaün 8372651712aSMickaël Salaün /* LOG_NEW_EXEC_ON: the post-exec denial (same_exec=0) is logged. */ 8382651712aSMickaël Salaün ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS("true"), 8392651712aSMickaël Salaün "logged", field, sizeof(field))); 8402651712aSMickaël Salaün EXPECT_STREQ("1", field); 8412651712aSMickaël Salaün 8422651712aSMickaël Salaün free(buf); 8432651712aSMickaël Salaün } 8442651712aSMickaël Salaün 8452651712aSMickaël Salaün /* 8462651712aSMickaël Salaün * Verifies that denials suppressed by audit log flags are still counted in 8472651712aSMickaël Salaün * num_denials. The child restricts itself with default flags (log_same_exec=1, 8482651712aSMickaël Salaün * log_new_exec=0), then execs true which attempts to read a denied directory. 8492651712aSMickaël Salaün * After exec, same_exec=0 and log_new_exec=0, so audit suppresses the denial. 8502651712aSMickaël Salaün * But the trace event fires unconditionally and free_domain must report the 8512651712aSMickaël Salaün * correct denials count. 8522651712aSMickaël Salaün */ 8532651712aSMickaël Salaün TEST_F(trace, non_audit_visible_denial_counting) 8542651712aSMickaël Salaün { 8552651712aSMickaël Salaün char *buf = NULL; 8562651712aSMickaël Salaün char denials_field[32]; 8572651712aSMickaël Salaün int count; 8582651712aSMickaël Salaün 8592651712aSMickaël Salaün set_cap(_metadata, CAP_SYS_ADMIN); 8602651712aSMickaël Salaün ASSERT_EQ(0, tracefs_clear()); 8612651712aSMickaël Salaün tracefs_clear_pid_filter(); 8622651712aSMickaël Salaün clear_cap(_metadata, CAP_SYS_ADMIN); 8632651712aSMickaël Salaün 8642651712aSMickaël Salaün sandbox_child_exec_true(_metadata, 0); 8652651712aSMickaël Salaün 8662651712aSMickaël Salaün /* Wait for free_domain event with retry. */ 8672651712aSMickaël Salaün for (int retry = 0; retry < 10; retry++) { 8682651712aSMickaël Salaün usleep(100000); 8692651712aSMickaël Salaün 8702651712aSMickaël Salaün set_cap(_metadata, CAP_SYS_ADMIN); 8712651712aSMickaël Salaün buf = tracefs_read_trace(); 8722651712aSMickaël Salaün clear_cap(_metadata, CAP_SYS_ADMIN); 8732651712aSMickaël Salaün if (!buf) 8742651712aSMickaël Salaün break; 8752651712aSMickaël Salaün 8762651712aSMickaël Salaün count = tracefs_count_matches(buf, 8772651712aSMickaël Salaün REGEX_FREE_DOMAIN(KWORKER_TASK)); 8782651712aSMickaël Salaün if (count >= 1) 8792651712aSMickaël Salaün break; 8802651712aSMickaël Salaün free(buf); 8812651712aSMickaël Salaün buf = NULL; 8822651712aSMickaël Salaün } 8832651712aSMickaël Salaün 8842651712aSMickaël Salaün set_cap(_metadata, CAP_SYS_ADMIN); 8852651712aSMickaël Salaün ASSERT_EQ(0, tracefs_set_pid_filter(getpid())); 8862651712aSMickaël Salaün clear_cap(_metadata, CAP_SYS_ADMIN); 8872651712aSMickaël Salaün 8882651712aSMickaël Salaün /* 8892651712aSMickaël Salaün * The denial happened after exec (same_exec=0), so audit would suppress 8902651712aSMickaël Salaün * it. But num_denials counts all denials regardless. 8912651712aSMickaël Salaün */ 8922651712aSMickaël Salaün ASSERT_NE(NULL, buf) 8932651712aSMickaël Salaün { 8942651712aSMickaël Salaün TH_LOG("free_domain event not found after 10 retries"); 8952651712aSMickaël Salaün } 8962651712aSMickaël Salaün EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_FREE_DOMAIN(KWORKER_TASK), 8972651712aSMickaël Salaün "denials", denials_field, 8982651712aSMickaël Salaün sizeof(denials_field))); 8992651712aSMickaël Salaün EXPECT_STREQ("1", denials_field); 9002651712aSMickaël Salaün 9012651712aSMickaël Salaün free(buf); 9022651712aSMickaël Salaün } 9032651712aSMickaël Salaün 9042651712aSMickaël Salaün /* 9052651712aSMickaël Salaün * Verifies that landlock_add_rule_net emits a trace event with the correct port 9062651712aSMickaël Salaün * and allowed access mask fields. 9072651712aSMickaël Salaün */ 9082651712aSMickaël Salaün TEST_F(trace, add_rule_net_fields) 9092651712aSMickaël Salaün { 9102651712aSMickaël Salaün struct landlock_ruleset_attr ruleset_attr = { 9112651712aSMickaël Salaün .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP, 9122651712aSMickaël Salaün }; 9132651712aSMickaël Salaün struct landlock_net_port_attr net_port = { 9142651712aSMickaël Salaün .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP, 9152651712aSMickaël Salaün .port = 8080, 9162651712aSMickaël Salaün }; 9172651712aSMickaël Salaün int ruleset_fd; 9182651712aSMickaël Salaün char *buf; 9192651712aSMickaël Salaün char field[64]; 9202651712aSMickaël Salaün 9212651712aSMickaël Salaün ruleset_fd = 9222651712aSMickaël Salaün landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); 9232651712aSMickaël Salaün ASSERT_LE(0, ruleset_fd); 9242651712aSMickaël Salaün 9252651712aSMickaël Salaün ASSERT_EQ(0, tracefs_clear_buf()); 9262651712aSMickaël Salaün 9272651712aSMickaël Salaün ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT, 9282651712aSMickaël Salaün &net_port, 0)); 9292651712aSMickaël Salaün close(ruleset_fd); 9302651712aSMickaël Salaün 9312651712aSMickaël Salaün buf = tracefs_read_buf(); 9322651712aSMickaël Salaün ASSERT_NE(NULL, buf); 9332651712aSMickaël Salaün 9342651712aSMickaël Salaün EXPECT_EQ(1, tracefs_count_matches(buf, REGEX_ADD_RULE_NET(TRACE_TASK))) 9352651712aSMickaël Salaün { 9362651712aSMickaël Salaün TH_LOG("Expected 1 add_rule_net event\n%s", buf); 9372651712aSMickaël Salaün } 9382651712aSMickaël Salaün 9392651712aSMickaël Salaün /* 9402651712aSMickaël Salaün * Verify the port is in host endianness, matching the UAPI convention 9412651712aSMickaël Salaün * (landlock_net_port_attr.port). On little-endian, htons(8080) is 9422651712aSMickaël Salaün * 36895, so this comparison catches byte-order bugs. 9432651712aSMickaël Salaün */ 9442651712aSMickaël Salaün EXPECT_EQ(0, tracefs_extract_field(buf, REGEX_ADD_RULE_NET(TRACE_TASK), 9452651712aSMickaël Salaün "port", field, sizeof(field))); 9462651712aSMickaël Salaün EXPECT_STREQ("8080", field); 9472651712aSMickaël Salaün /* 9482651712aSMickaël Salaün * The allowed mask is the absolute value after transformation: the 9492651712aSMickaël Salaün * user-requested BIND_TCP plus all unhandled access rights (the other 9502651712aSMickaël Salaün * net access bits are unhandled because the ruleset only handles 9512651712aSMickaël Salaün * BIND_TCP). 9522651712aSMickaël Salaün */ 9532651712aSMickaël Salaün EXPECT_EQ(0, 9542651712aSMickaël Salaün tracefs_extract_field(buf, REGEX_ADD_RULE_NET(TRACE_TASK), 9552651712aSMickaël Salaün "access_rights", field, sizeof(field))); 9562651712aSMickaël Salaün EXPECT_STREQ("bind_tcp|connect_tcp|bind_udp|connect_send_udp", field); 9572651712aSMickaël Salaün 9582651712aSMickaël Salaün free(buf); 9592651712aSMickaël Salaün } 9602651712aSMickaël Salaün 9612651712aSMickaël Salaün /* 9622651712aSMickaël Salaün * Verifies that LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF suppresses audit 9632651712aSMickaël Salaün * logging for child domains (logged=0) even though the child's own 9642651712aSMickaël Salaün * per-execution flags are the defaults, while the trace event still fires 9652651712aSMickaël Salaün * (tracing is unconditional). The parent creates a domain with 9662651712aSMickaël Salaün * LOG_SUBDOMAINS_OFF, then the child creates a sub-domain and triggers a 9672651712aSMickaël Salaün * denial. 9682651712aSMickaël Salaün */ 9692651712aSMickaël Salaün TEST_F(trace, log_flags_subdomains_off) 9702651712aSMickaël Salaün { 9712651712aSMickaël Salaün pid_t pid; 9722651712aSMickaël Salaün int status; 9732651712aSMickaël Salaün char *buf; 9742651712aSMickaël Salaün char field[64]; 9752651712aSMickaël Salaün 9762651712aSMickaël Salaün ASSERT_EQ(0, tracefs_clear_buf()); 9772651712aSMickaël Salaün 9782651712aSMickaël Salaün pid = fork(); 9792651712aSMickaël Salaün ASSERT_LE(0, pid); 9802651712aSMickaël Salaün 9812651712aSMickaël Salaün if (pid == 0) { 9822651712aSMickaël Salaün struct landlock_ruleset_attr attr = { 9832651712aSMickaël Salaün .handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR, 9842651712aSMickaël Salaün }; 9852651712aSMickaël Salaün int parent_fd, child_fd, dir_fd; 9862651712aSMickaël Salaün 9872651712aSMickaël Salaün /* Parent domain with LOG_SUBDOMAINS_OFF. */ 9882651712aSMickaël Salaün parent_fd = landlock_create_ruleset(&attr, sizeof(attr), 0); 9892651712aSMickaël Salaün if (parent_fd < 0) 9902651712aSMickaël Salaün _exit(1); 9912651712aSMickaël Salaün 9922651712aSMickaël Salaün prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); 9932651712aSMickaël Salaün if (landlock_restrict_self( 9942651712aSMickaël Salaün parent_fd, 9952651712aSMickaël Salaün LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF)) 9962651712aSMickaël Salaün _exit(1); 9972651712aSMickaël Salaün close(parent_fd); 9982651712aSMickaël Salaün 9992651712aSMickaël Salaün /* Child sub-domain with default flags. */ 10002651712aSMickaël Salaün child_fd = landlock_create_ruleset(&attr, sizeof(attr), 0); 10012651712aSMickaël Salaün if (child_fd < 0) 10022651712aSMickaël Salaün _exit(1); 10032651712aSMickaël Salaün 10042651712aSMickaël Salaün if (landlock_restrict_self(child_fd, 0)) 10052651712aSMickaël Salaün _exit(1); 10062651712aSMickaël Salaün close(child_fd); 10072651712aSMickaël Salaün 10082651712aSMickaël Salaün /* Trigger a denial from the child domain. */ 10092651712aSMickaël Salaün dir_fd = open(".", O_RDONLY | O_DIRECTORY | O_CLOEXEC); 10102651712aSMickaël Salaün if (dir_fd >= 0) 10112651712aSMickaël Salaün close(dir_fd); 10122651712aSMickaël Salaün _exit(0); 10132651712aSMickaël Salaün } 10142651712aSMickaël Salaün 10152651712aSMickaël Salaün ASSERT_EQ(pid, waitpid(pid, &status, 0)); 10162651712aSMickaël Salaün ASSERT_TRUE(WIFEXITED(status)); 10172651712aSMickaël Salaün EXPECT_EQ(0, WEXITSTATUS(status)); 10182651712aSMickaël Salaün 10192651712aSMickaël Salaün buf = tracefs_read_buf(); 10202651712aSMickaël Salaün ASSERT_NE(NULL, buf); 10212651712aSMickaël Salaün 10222651712aSMickaël Salaün /* 10232651712aSMickaël Salaün * Trace fires unconditionally even though audit is disabled for the 10242651712aSMickaël Salaün * child domain (parent had LOG_SUBDOMAINS_OFF). 10252651712aSMickaël Salaün */ 10262651712aSMickaël Salaün EXPECT_LE(1, 10272651712aSMickaël Salaün tracefs_count_matches(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK))) 10282651712aSMickaël Salaün { 10292651712aSMickaël Salaün TH_LOG("Expected deny_access_fs event despite " 10302651712aSMickaël Salaün "LOG_SUBDOMAINS_OFF\n%s", 10312651712aSMickaël Salaün buf); 10322651712aSMickaël Salaün } 10332651712aSMickaël Salaün 10342651712aSMickaël Salaün /* 10352651712aSMickaël Salaün * The child's per-execution flags default to logging, but the 10362651712aSMickaël Salaün * ancestor's LOG_SUBDOMAINS_OFF disables it, so audit suppresses this 10372651712aSMickaël Salaün * denial (logged=0). This is exactly the case the single logged field 10382651712aSMickaël Salaün * captures and the raw per-execution flags could not. 10392651712aSMickaël Salaün */ 10402651712aSMickaël Salaün ASSERT_EQ(0, 10412651712aSMickaël Salaün tracefs_extract_field(buf, REGEX_DENY_ACCESS_FS(TRACE_TASK), 10422651712aSMickaël Salaün "logged", field, sizeof(field))); 10432651712aSMickaël Salaün EXPECT_STREQ("0", field); 10442651712aSMickaël Salaün 10452651712aSMickaël Salaün free(buf); 10462651712aSMickaël Salaün } 10472651712aSMickaël Salaün 10482651712aSMickaël Salaün /* Verifies that landlock_free_ruleset fires when a ruleset FD is closed. */ 10492651712aSMickaël Salaün TEST_F(trace, free_ruleset_on_close) 10502651712aSMickaël Salaün { 10512651712aSMickaël Salaün struct landlock_ruleset_attr ruleset_attr = { 10522651712aSMickaël Salaün .handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR, 10532651712aSMickaël Salaün }; 10542651712aSMickaël Salaün int ruleset_fd; 10552651712aSMickaël Salaün char *buf; 10562651712aSMickaël Salaün 10572651712aSMickaël Salaün ruleset_fd = 10582651712aSMickaël Salaün landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); 10592651712aSMickaël Salaün ASSERT_LE(0, ruleset_fd); 10602651712aSMickaël Salaün 10612651712aSMickaël Salaün ASSERT_EQ(0, tracefs_clear_buf()); 10622651712aSMickaël Salaün 10632651712aSMickaël Salaün /* Closing the FD should trigger free_ruleset. */ 10642651712aSMickaël Salaün close(ruleset_fd); 10652651712aSMickaël Salaün 10662651712aSMickaël Salaün buf = tracefs_read_buf(); 10672651712aSMickaël Salaün ASSERT_NE(NULL, buf); 10682651712aSMickaël Salaün 10692651712aSMickaël Salaün EXPECT_EQ(1, tracefs_count_matches(buf, REGEX_FREE_RULESET(TRACE_TASK))) 10702651712aSMickaël Salaün { 10712651712aSMickaël Salaün TH_LOG("Expected 1 free_ruleset event\n%s", buf); 10722651712aSMickaël Salaün } 10732651712aSMickaël Salaün 10742651712aSMickaël Salaün free(buf); 10752651712aSMickaël Salaün } 10762651712aSMickaël Salaün 10772651712aSMickaël Salaün /* 1078*ac6d193dSMickaël Salaün * Counts landlock_enforce_domain lines, filtered by @domain (NULL matches any), 1079*ac6d193dSMickaël Salaün * @complete and @process_wide (a negative value matches any). Builds the 1080*ac6d193dSMickaël Salaün * anchored regex dynamically so a single helper covers every field assertion. 1081*ac6d193dSMickaël Salaün */ 1082*ac6d193dSMickaël Salaün static int count_enforce_matches(const char *buf, const char *domain, 1083*ac6d193dSMickaël Salaün int complete, int process_wide, 1084*ac6d193dSMickaël Salaün int no_new_privs) 1085*ac6d193dSMickaël Salaün { 1086*ac6d193dSMickaël Salaün char pattern[512], dom[80], comp[8], pw[8], nnp[8]; 1087*ac6d193dSMickaël Salaün 1088*ac6d193dSMickaël Salaün if (domain) 1089*ac6d193dSMickaël Salaün snprintf(dom, sizeof(dom), "%s", domain); 1090*ac6d193dSMickaël Salaün else 1091*ac6d193dSMickaël Salaün snprintf(dom, sizeof(dom), "[0-9a-f]\\+"); 1092*ac6d193dSMickaël Salaün if (complete < 0) 1093*ac6d193dSMickaël Salaün snprintf(comp, sizeof(comp), "[01]"); 1094*ac6d193dSMickaël Salaün else 1095*ac6d193dSMickaël Salaün snprintf(comp, sizeof(comp), "%d", complete); 1096*ac6d193dSMickaël Salaün if (process_wide < 0) 1097*ac6d193dSMickaël Salaün snprintf(pw, sizeof(pw), "[01]"); 1098*ac6d193dSMickaël Salaün else 1099*ac6d193dSMickaël Salaün snprintf(pw, sizeof(pw), "%d", process_wide); 1100*ac6d193dSMickaël Salaün if (no_new_privs < 0) 1101*ac6d193dSMickaël Salaün snprintf(nnp, sizeof(nnp), "[01]"); 1102*ac6d193dSMickaël Salaün else 1103*ac6d193dSMickaël Salaün snprintf(nnp, sizeof(nnp), "%d", no_new_privs); 1104*ac6d193dSMickaël Salaün 1105*ac6d193dSMickaël Salaün snprintf(pattern, sizeof(pattern), 1106*ac6d193dSMickaël Salaün TRACE_PREFIX(TRACE_TASK) "landlock_enforce_domain: " 1107*ac6d193dSMickaël Salaün "domain=%s " 1108*ac6d193dSMickaël Salaün "complete=%s process_wide=%s " 1109*ac6d193dSMickaël Salaün "no_new_privs=%s$", 1110*ac6d193dSMickaël Salaün dom, comp, pw, nnp); 1111*ac6d193dSMickaël Salaün return tracefs_count_matches(buf, pattern); 1112*ac6d193dSMickaël Salaün } 1113*ac6d193dSMickaël Salaün 1114*ac6d193dSMickaël Salaün /* Idle sibling: waits on the barrier so it is a live thread, then sleeps. */ 1115*ac6d193dSMickaël Salaün static void *enforce_idle(void *arg) 1116*ac6d193dSMickaël Salaün { 1117*ac6d193dSMickaël Salaün pthread_barrier_t *barrier = arg; 1118*ac6d193dSMickaël Salaün 1119*ac6d193dSMickaël Salaün pthread_barrier_wait(barrier); 1120*ac6d193dSMickaël Salaün while (true) 1121*ac6d193dSMickaël Salaün sleep(1); 1122*ac6d193dSMickaël Salaün return NULL; 1123*ac6d193dSMickaël Salaün } 1124*ac6d193dSMickaël Salaün 1125*ac6d193dSMickaël Salaün /* 1126*ac6d193dSMickaël Salaün * Child body: spawns @nthreads idle siblings (barrier-synchronized so they are 1127*ac6d193dSMickaël Salaün * live when the syscall runs), then enforces a domain with @flags. Returns 0 1128*ac6d193dSMickaël Salaün * on success; the process exits afterwards, reaping the siblings. 1129*ac6d193dSMickaël Salaün */ 1130*ac6d193dSMickaël Salaün static int child_enforce(int nthreads, __u32 flags) 1131*ac6d193dSMickaël Salaün { 1132*ac6d193dSMickaël Salaün pthread_t threads[8]; 1133*ac6d193dSMickaël Salaün pthread_barrier_t barrier; 1134*ac6d193dSMickaël Salaün int ruleset_fd, i; 1135*ac6d193dSMickaël Salaün 1136*ac6d193dSMickaël Salaün if (nthreads > 0) { 1137*ac6d193dSMickaël Salaün if (pthread_barrier_init(&barrier, NULL, nthreads + 1)) 1138*ac6d193dSMickaël Salaün return 1; 1139*ac6d193dSMickaël Salaün for (i = 0; i < nthreads; i++) 1140*ac6d193dSMickaël Salaün if (pthread_create(&threads[i], NULL, enforce_idle, 1141*ac6d193dSMickaël Salaün &barrier)) 1142*ac6d193dSMickaël Salaün return 1; 1143*ac6d193dSMickaël Salaün pthread_barrier_wait(&barrier); 1144*ac6d193dSMickaël Salaün } 1145*ac6d193dSMickaël Salaün 1146*ac6d193dSMickaël Salaün ruleset_fd = build_enforce_ruleset(); 1147*ac6d193dSMickaël Salaün if (ruleset_fd < 0) 1148*ac6d193dSMickaël Salaün return 1; 1149*ac6d193dSMickaël Salaün 1150*ac6d193dSMickaël Salaün /* 1151*ac6d193dSMickaël Salaün * LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS sets no_new_privs itself, so skip 1152*ac6d193dSMickaël Salaün * the prctl() to exercise that path; otherwise Landlock requires 1153*ac6d193dSMickaël Salaün * no_new_privs up front. 1154*ac6d193dSMickaël Salaün */ 1155*ac6d193dSMickaël Salaün if (!(flags & LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS)) 1156*ac6d193dSMickaël Salaün prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); 1157*ac6d193dSMickaël Salaün if (landlock_restrict_self(ruleset_fd, flags)) 1158*ac6d193dSMickaël Salaün return 1; 1159*ac6d193dSMickaël Salaün close(ruleset_fd); 1160*ac6d193dSMickaël Salaün return 0; 1161*ac6d193dSMickaël Salaün } 1162*ac6d193dSMickaël Salaün 1163*ac6d193dSMickaël Salaün /* 1164*ac6d193dSMickaël Salaün * Runs in a spawned thread after the group leader called pthread_exit(). The 1165*ac6d193dSMickaël Salaün * leader lingers as an un-reaped zombie, so get_nr_threads() still counts it 1166*ac6d193dSMickaël Salaün * and this non-leader is not the only thread; enforcing here therefore reports 1167*ac6d193dSMickaël Salaün * process_wide=0. 1168*ac6d193dSMickaël Salaün */ 1169*ac6d193dSMickaël Salaün static void *enforce_nonleader(void *arg) 1170*ac6d193dSMickaël Salaün { 1171*ac6d193dSMickaël Salaün int ruleset_fd; 1172*ac6d193dSMickaël Salaün 1173*ac6d193dSMickaël Salaün ruleset_fd = build_enforce_ruleset(); 1174*ac6d193dSMickaël Salaün if (ruleset_fd < 0) 1175*ac6d193dSMickaël Salaün _exit(1); 1176*ac6d193dSMickaël Salaün prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); 1177*ac6d193dSMickaël Salaün if (landlock_restrict_self(ruleset_fd, 0)) 1178*ac6d193dSMickaël Salaün _exit(1); 1179*ac6d193dSMickaël Salaün _exit(0); 1180*ac6d193dSMickaël Salaün } 1181*ac6d193dSMickaël Salaün 1182*ac6d193dSMickaël Salaün /* 1183*ac6d193dSMickaël Salaün * Collapses the enforce_domain field cases into one parametrized test. Each 1184*ac6d193dSMickaël Salaün * variant runs child_enforce(nthreads, flags) and checks the resulting 1185*ac6d193dSMickaël Salaün * enforce_domain events. The flags column also selects how no_new_privs is 1186*ac6d193dSMickaël Salaün * set: with LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS child_enforce() skips the 1187*ac6d193dSMickaël Salaün * prctl() so the flag sets it (and, with TSYNC, propagates to the siblings); 1188*ac6d193dSMickaël Salaün * otherwise a prior prctl() sets it on the caller (and TSYNC propagates that). 1189*ac6d193dSMickaël Salaün */ 1190*ac6d193dSMickaël Salaün 1191*ac6d193dSMickaël Salaün /* clang-format off */ 1192*ac6d193dSMickaël Salaün FIXTURE(trace_enforce) { 1193*ac6d193dSMickaël Salaün /* clang-format on */ 1194*ac6d193dSMickaël Salaün int tracefs_ok; 1195*ac6d193dSMickaël Salaün }; 1196*ac6d193dSMickaël Salaün 1197*ac6d193dSMickaël Salaün FIXTURE_SETUP(trace_enforce) 1198*ac6d193dSMickaël Salaün { 1199*ac6d193dSMickaël Salaün int ret; 1200*ac6d193dSMickaël Salaün 1201*ac6d193dSMickaël Salaün set_cap(_metadata, CAP_SYS_ADMIN); 1202*ac6d193dSMickaël Salaün ASSERT_EQ(0, unshare(CLONE_NEWNS)); 1203*ac6d193dSMickaël Salaün ASSERT_EQ(0, mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL)); 1204*ac6d193dSMickaël Salaün 1205*ac6d193dSMickaël Salaün ret = tracefs_fixture_setup(); 1206*ac6d193dSMickaël Salaün if (ret) { 1207*ac6d193dSMickaël Salaün clear_cap(_metadata, CAP_SYS_ADMIN); 1208*ac6d193dSMickaël Salaün self->tracefs_ok = 0; 1209*ac6d193dSMickaël Salaün SKIP(return, "tracefs not available"); 1210*ac6d193dSMickaël Salaün } 1211*ac6d193dSMickaël Salaün self->tracefs_ok = 1; 1212*ac6d193dSMickaël Salaün 1213*ac6d193dSMickaël Salaün ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, true)); 1214*ac6d193dSMickaël Salaün ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, true)); 1215*ac6d193dSMickaël Salaün ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, true)); 1216*ac6d193dSMickaël Salaün ASSERT_EQ(0, tracefs_clear()); 1217*ac6d193dSMickaël Salaün clear_cap(_metadata, CAP_SYS_ADMIN); 1218*ac6d193dSMickaël Salaün } 1219*ac6d193dSMickaël Salaün 1220*ac6d193dSMickaël Salaün FIXTURE_TEARDOWN(trace_enforce) 1221*ac6d193dSMickaël Salaün { 1222*ac6d193dSMickaël Salaün if (!self->tracefs_ok) 1223*ac6d193dSMickaël Salaün return; 1224*ac6d193dSMickaël Salaün 1225*ac6d193dSMickaël Salaün set_cap(_metadata, CAP_SYS_ADMIN); 1226*ac6d193dSMickaël Salaün tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, false); 1227*ac6d193dSMickaël Salaün tracefs_enable_event(TRACEFS_CREATE_DOMAIN_ENABLE, false); 1228*ac6d193dSMickaël Salaün tracefs_enable_event(TRACEFS_ENFORCE_DOMAIN_ENABLE, false); 1229*ac6d193dSMickaël Salaün tracefs_fixture_teardown(); 1230*ac6d193dSMickaël Salaün clear_cap(_metadata, CAP_SYS_ADMIN); 1231*ac6d193dSMickaël Salaün } 1232*ac6d193dSMickaël Salaün 1233*ac6d193dSMickaël Salaün /* clang-format off */ 1234*ac6d193dSMickaël Salaün FIXTURE_VARIANT(trace_enforce) { 1235*ac6d193dSMickaël Salaün /* clang-format on */ 1236*ac6d193dSMickaël Salaün /* Inputs to child_enforce(). */ 1237*ac6d193dSMickaël Salaün int nthreads; 1238*ac6d193dSMickaël Salaün __u32 flags; 1239*ac6d193dSMickaël Salaün /* Expected enforce_domain event counts. */ 1240*ac6d193dSMickaël Salaün int total; 1241*ac6d193dSMickaël Salaün int complete; 1242*ac6d193dSMickaël Salaün int process_wide; 1243*ac6d193dSMickaël Salaün int no_new_privs; 1244*ac6d193dSMickaël Salaün }; 1245*ac6d193dSMickaël Salaün 1246*ac6d193dSMickaël Salaün /* clang-format off */ 1247*ac6d193dSMickaël Salaün 1248*ac6d193dSMickaël Salaün /* Single thread, no flags: prctl-backed no_new_privs. */ 1249*ac6d193dSMickaël Salaün FIXTURE_VARIANT_ADD(trace_enforce, single) { 1250*ac6d193dSMickaël Salaün .nthreads = 0, .flags = 0, 1251*ac6d193dSMickaël Salaün .total = 1, .complete = 1, .process_wide = 1, .no_new_privs = 1, 1252*ac6d193dSMickaël Salaün }; 1253*ac6d193dSMickaël Salaün 1254*ac6d193dSMickaël Salaün /* Single thread: the NO_NEW_PRIVS flag sets no_new_privs (no prctl). */ 1255*ac6d193dSMickaël Salaün FIXTURE_VARIANT_ADD(trace_enforce, no_new_privs) { 1256*ac6d193dSMickaël Salaün .nthreads = 0, .flags = LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS, 1257*ac6d193dSMickaël Salaün .total = 1, .complete = 1, .process_wide = 1, .no_new_privs = 1, 1258*ac6d193dSMickaël Salaün }; 1259*ac6d193dSMickaël Salaün 1260*ac6d193dSMickaël Salaün /* TSYNC on a lone thread still concludes, process-wide. */ 1261*ac6d193dSMickaël Salaün FIXTURE_VARIANT_ADD(trace_enforce, tsync_single) { 1262*ac6d193dSMickaël Salaün .nthreads = 0, .flags = LANDLOCK_RESTRICT_SELF_TSYNC, 1263*ac6d193dSMickaël Salaün .total = 1, .complete = 1, .process_wide = 1, .no_new_privs = 1, 1264*ac6d193dSMickaël Salaün }; 1265*ac6d193dSMickaël Salaün 1266*ac6d193dSMickaël Salaün /* TSYNC sweeps N siblings; the caller's prctl-backed nnp propagates to all. */ 1267*ac6d193dSMickaël Salaün FIXTURE_VARIANT_ADD(trace_enforce, tsync_multithread) { 1268*ac6d193dSMickaël Salaün .nthreads = 3, .flags = LANDLOCK_RESTRICT_SELF_TSYNC, 1269*ac6d193dSMickaël Salaün .total = 4, .complete = 1, .process_wide = 4, .no_new_privs = 4, 1270*ac6d193dSMickaël Salaün }; 1271*ac6d193dSMickaël Salaün 1272*ac6d193dSMickaël Salaün /* TSYNC + NO_NEW_PRIVS flag sets nnp on the caller and every swept sibling. */ 1273*ac6d193dSMickaël Salaün FIXTURE_VARIANT_ADD(trace_enforce, tsync_no_new_privs) { 1274*ac6d193dSMickaël Salaün .nthreads = 3, 1275*ac6d193dSMickaël Salaün .flags = LANDLOCK_RESTRICT_SELF_TSYNC | LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS, 1276*ac6d193dSMickaël Salaün .total = 4, .complete = 1, .process_wide = 4, .no_new_privs = 4, 1277*ac6d193dSMickaël Salaün }; 1278*ac6d193dSMickaël Salaün 1279*ac6d193dSMickaël Salaün /* Non-TSYNC on a multi-threaded process enforces only the caller. */ 1280*ac6d193dSMickaël Salaün FIXTURE_VARIANT_ADD(trace_enforce, multithread_non_tsync) { 1281*ac6d193dSMickaël Salaün .nthreads = 3, .flags = 0, 1282*ac6d193dSMickaël Salaün .total = 1, .complete = 1, .process_wide = 0, .no_new_privs = 1, 1283*ac6d193dSMickaël Salaün }; 1284*ac6d193dSMickaël Salaün 1285*ac6d193dSMickaël Salaün /* clang-format on */ 1286*ac6d193dSMickaël Salaün 1287*ac6d193dSMickaël Salaün /* 1288*ac6d193dSMickaël Salaün * One create_domain and variant->total enforce_domain events sharing that 1289*ac6d193dSMickaël Salaün * domain ID; complete=1 marks the single concluding event, and the process_wide 1290*ac6d193dSMickaël Salaün * / no_new_privs counts match the variant. Counts are order-independent, 1291*ac6d193dSMickaël Salaün * evaluated after the syscall returns. 1292*ac6d193dSMickaël Salaün */ 1293*ac6d193dSMickaël Salaün TEST_F(trace_enforce, enforce) 1294*ac6d193dSMickaël Salaün { 1295*ac6d193dSMickaël Salaün pid_t pid; 1296*ac6d193dSMickaël Salaün int status; 1297*ac6d193dSMickaël Salaün char *buf; 1298*ac6d193dSMickaël Salaün char domain[64]; 1299*ac6d193dSMickaël Salaün 1300*ac6d193dSMickaël Salaün ASSERT_EQ(0, tracefs_clear_buf()); 1301*ac6d193dSMickaël Salaün 1302*ac6d193dSMickaël Salaün pid = fork(); 1303*ac6d193dSMickaël Salaün ASSERT_LE(0, pid); 1304*ac6d193dSMickaël Salaün if (pid == 0) 1305*ac6d193dSMickaël Salaün _exit(child_enforce(variant->nthreads, variant->flags)); 1306*ac6d193dSMickaël Salaün 1307*ac6d193dSMickaël Salaün ASSERT_EQ(pid, waitpid(pid, &status, 0)); 1308*ac6d193dSMickaël Salaün ASSERT_TRUE(WIFEXITED(status)); 1309*ac6d193dSMickaël Salaün EXPECT_EQ(0, WEXITSTATUS(status)); 1310*ac6d193dSMickaël Salaün 1311*ac6d193dSMickaël Salaün buf = tracefs_read_buf(); 1312*ac6d193dSMickaël Salaün ASSERT_NE(NULL, buf); 1313*ac6d193dSMickaël Salaün 1314*ac6d193dSMickaël Salaün EXPECT_EQ(1, 1315*ac6d193dSMickaël Salaün tracefs_count_matches(buf, REGEX_CREATE_DOMAIN(TRACE_TASK))); 1316*ac6d193dSMickaël Salaün EXPECT_EQ(variant->total, count_enforce_matches(buf, NULL, -1, -1, -1)) 1317*ac6d193dSMickaël Salaün { 1318*ac6d193dSMickaël Salaün TH_LOG("Expected %d enforce_domain events\n%s", variant->total, 1319*ac6d193dSMickaël Salaün buf); 1320*ac6d193dSMickaël Salaün } 1321*ac6d193dSMickaël Salaün EXPECT_EQ(variant->complete, 1322*ac6d193dSMickaël Salaün count_enforce_matches(buf, NULL, 1, -1, -1)); 1323*ac6d193dSMickaël Salaün EXPECT_EQ(variant->total - variant->complete, 1324*ac6d193dSMickaël Salaün count_enforce_matches(buf, NULL, 0, -1, -1)); 1325*ac6d193dSMickaël Salaün EXPECT_EQ(variant->process_wide, 1326*ac6d193dSMickaël Salaün count_enforce_matches(buf, NULL, -1, 1, -1)); 1327*ac6d193dSMickaël Salaün EXPECT_EQ(variant->total - variant->process_wide, 1328*ac6d193dSMickaël Salaün count_enforce_matches(buf, NULL, -1, 0, -1)); 1329*ac6d193dSMickaël Salaün EXPECT_EQ(variant->no_new_privs, 1330*ac6d193dSMickaël Salaün count_enforce_matches(buf, NULL, -1, -1, 1)); 1331*ac6d193dSMickaël Salaün 1332*ac6d193dSMickaël Salaün ASSERT_EQ(0, tracefs_extract_field(buf, REGEX_CREATE_DOMAIN(TRACE_TASK), 1333*ac6d193dSMickaël Salaün "domain", domain, sizeof(domain))); 1334*ac6d193dSMickaël Salaün EXPECT_EQ(variant->total, 1335*ac6d193dSMickaël Salaün count_enforce_matches(buf, domain, -1, -1, -1)); 1336*ac6d193dSMickaël Salaün 1337*ac6d193dSMickaël Salaün free(buf); 1338*ac6d193dSMickaël Salaün } 1339*ac6d193dSMickaël Salaün 1340*ac6d193dSMickaël Salaün /* 1341*ac6d193dSMickaël Salaün * A non-leader thread enforcing a domain while the group leader lingers as an 1342*ac6d193dSMickaël Salaün * un-reaped zombie reports process_wide=0: get_nr_threads() counts the zombie 1343*ac6d193dSMickaël Salaün * leader, so the group is not single-threaded. This is the reachable half of 1344*ac6d193dSMickaël Salaün * the caveat that process_wide==0 never proves the process is multi-threaded 1345*ac6d193dSMickaël Salaün * (get_nr_threads(), unlike the leader-relative thread_group_empty(), counts 1346*ac6d193dSMickaël Salaün * the zombie leader). 1347*ac6d193dSMickaël Salaün */ 1348*ac6d193dSMickaël Salaün TEST_F(trace, enforce_single_non_leader) 1349*ac6d193dSMickaël Salaün { 1350*ac6d193dSMickaël Salaün pid_t pid; 1351*ac6d193dSMickaël Salaün int status; 1352*ac6d193dSMickaël Salaün char *buf; 1353*ac6d193dSMickaël Salaün 1354*ac6d193dSMickaël Salaün ASSERT_EQ(0, tracefs_clear_buf()); 1355*ac6d193dSMickaël Salaün 1356*ac6d193dSMickaël Salaün pid = fork(); 1357*ac6d193dSMickaël Salaün ASSERT_LE(0, pid); 1358*ac6d193dSMickaël Salaün if (pid == 0) { 1359*ac6d193dSMickaël Salaün pthread_t worker; 1360*ac6d193dSMickaël Salaün 1361*ac6d193dSMickaël Salaün if (pthread_create(&worker, NULL, enforce_nonleader, NULL)) 1362*ac6d193dSMickaël Salaün _exit(1); 1363*ac6d193dSMickaël Salaün /* Leader leaves; the worker enforces as a non-leader. */ 1364*ac6d193dSMickaël Salaün pthread_exit(NULL); 1365*ac6d193dSMickaël Salaün } 1366*ac6d193dSMickaël Salaün 1367*ac6d193dSMickaël Salaün ASSERT_EQ(pid, waitpid(pid, &status, 0)); 1368*ac6d193dSMickaël Salaün ASSERT_TRUE(WIFEXITED(status)); 1369*ac6d193dSMickaël Salaün EXPECT_EQ(0, WEXITSTATUS(status)); 1370*ac6d193dSMickaël Salaün 1371*ac6d193dSMickaël Salaün buf = tracefs_read_buf(); 1372*ac6d193dSMickaël Salaün ASSERT_NE(NULL, buf); 1373*ac6d193dSMickaël Salaün 1374*ac6d193dSMickaël Salaün EXPECT_EQ(1, 1375*ac6d193dSMickaël Salaün tracefs_count_matches(buf, REGEX_CREATE_DOMAIN(TRACE_TASK))); 1376*ac6d193dSMickaël Salaün EXPECT_EQ(1, count_enforce_matches(buf, NULL, 1, 0, -1)) 1377*ac6d193dSMickaël Salaün { 1378*ac6d193dSMickaël Salaün TH_LOG("Expected complete=1 process_wide=0 for non-leader\n%s", 1379*ac6d193dSMickaël Salaün buf); 1380*ac6d193dSMickaël Salaün } 1381*ac6d193dSMickaël Salaün 1382*ac6d193dSMickaël Salaün free(buf); 1383*ac6d193dSMickaël Salaün } 1384*ac6d193dSMickaël Salaün 1385*ac6d193dSMickaël Salaün /* 1386*ac6d193dSMickaël Salaün * Verifies the flags-only path (ruleset_fd == -1) creates no domain and emits 1387*ac6d193dSMickaël Salaün * neither create_domain nor enforce_domain, with and without TSYNC. 1388*ac6d193dSMickaël Salaün */ 1389*ac6d193dSMickaël Salaün TEST_F(trace, enforce_flags_only) 1390*ac6d193dSMickaël Salaün { 1391*ac6d193dSMickaël Salaün pid_t pid; 1392*ac6d193dSMickaël Salaün int status; 1393*ac6d193dSMickaël Salaün char *buf; 1394*ac6d193dSMickaël Salaün 1395*ac6d193dSMickaël Salaün ASSERT_EQ(0, tracefs_clear_buf()); 1396*ac6d193dSMickaël Salaün 1397*ac6d193dSMickaël Salaün pid = fork(); 1398*ac6d193dSMickaël Salaün ASSERT_LE(0, pid); 1399*ac6d193dSMickaël Salaün if (pid == 0) { 1400*ac6d193dSMickaël Salaün prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); 1401*ac6d193dSMickaël Salaün if (landlock_restrict_self( 1402*ac6d193dSMickaël Salaün -1, LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF)) 1403*ac6d193dSMickaël Salaün _exit(1); 1404*ac6d193dSMickaël Salaün if (landlock_restrict_self( 1405*ac6d193dSMickaël Salaün -1, LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF | 1406*ac6d193dSMickaël Salaün LANDLOCK_RESTRICT_SELF_TSYNC)) 1407*ac6d193dSMickaël Salaün _exit(1); 1408*ac6d193dSMickaël Salaün _exit(0); 1409*ac6d193dSMickaël Salaün } 1410*ac6d193dSMickaël Salaün 1411*ac6d193dSMickaël Salaün ASSERT_EQ(pid, waitpid(pid, &status, 0)); 1412*ac6d193dSMickaël Salaün ASSERT_TRUE(WIFEXITED(status)); 1413*ac6d193dSMickaël Salaün EXPECT_EQ(0, WEXITSTATUS(status)); 1414*ac6d193dSMickaël Salaün 1415*ac6d193dSMickaël Salaün buf = tracefs_read_buf(); 1416*ac6d193dSMickaël Salaün ASSERT_NE(NULL, buf); 1417*ac6d193dSMickaël Salaün 1418*ac6d193dSMickaël Salaün EXPECT_EQ(0, 1419*ac6d193dSMickaël Salaün tracefs_count_matches(buf, REGEX_CREATE_DOMAIN(TRACE_TASK))); 1420*ac6d193dSMickaël Salaün EXPECT_EQ(0, count_enforce_matches(buf, NULL, -1, -1, -1)) 1421*ac6d193dSMickaël Salaün { 1422*ac6d193dSMickaël Salaün TH_LOG("No enforce_domain expected on flags-only path\n%s", 1423*ac6d193dSMickaël Salaün buf); 1424*ac6d193dSMickaël Salaün } 1425*ac6d193dSMickaël Salaün 1426*ac6d193dSMickaël Salaün free(buf); 1427*ac6d193dSMickaël Salaün } 1428*ac6d193dSMickaël Salaün 1429*ac6d193dSMickaël Salaün static void enforce_nop_handler(int sig) 1430*ac6d193dSMickaël Salaün { 1431*ac6d193dSMickaël Salaün } 1432*ac6d193dSMickaël Salaün 1433*ac6d193dSMickaël Salaün struct abort_signaler_data { 1434*ac6d193dSMickaël Salaün pthread_t target; 1435*ac6d193dSMickaël Salaün volatile bool stop; 1436*ac6d193dSMickaël Salaün }; 1437*ac6d193dSMickaël Salaün 1438*ac6d193dSMickaël Salaün /* 1439*ac6d193dSMickaël Salaün * Hammers the target thread with SIGUSR1 to interrupt the TSYNC prepare wait. 1440*ac6d193dSMickaël Salaün */ 1441*ac6d193dSMickaël Salaün static void *abort_signaler(void *arg) 1442*ac6d193dSMickaël Salaün { 1443*ac6d193dSMickaël Salaün struct abort_signaler_data *data = arg; 1444*ac6d193dSMickaël Salaün 1445*ac6d193dSMickaël Salaün while (!data->stop) 1446*ac6d193dSMickaël Salaün pthread_kill(data->target, SIGUSR1); 1447*ac6d193dSMickaël Salaün return NULL; 1448*ac6d193dSMickaël Salaün } 1449*ac6d193dSMickaël Salaün 1450*ac6d193dSMickaël Salaün /* 1451*ac6d193dSMickaël Salaün * Child body for the abort test: with idle siblings and a signaler interrupting 1452*ac6d193dSMickaël Salaün * it, repeatedly enforces under TSYNC. An interrupted attempt aborts its 1453*ac6d193dSMickaël Salaün * just-created domain (create_domain + free_domain, zero enforce_domain) while 1454*ac6d193dSMickaël Salaün * -ERESTARTNOINTR transparently restarts the syscall, so a successful retry may 1455*ac6d193dSMickaël Salaün * add its own full lifecycle. 1456*ac6d193dSMickaël Salaün */ 1457*ac6d193dSMickaël Salaün static int child_abort(int nsiblings, int attempts) 1458*ac6d193dSMickaël Salaün { 1459*ac6d193dSMickaël Salaün pthread_t threads[200]; 1460*ac6d193dSMickaël Salaün pthread_t signaler; 1461*ac6d193dSMickaël Salaün pthread_barrier_t barrier; 1462*ac6d193dSMickaël Salaün struct abort_signaler_data data = {}; 1463*ac6d193dSMickaël Salaün struct sigaction sa = {}; 1464*ac6d193dSMickaël Salaün int i; 1465*ac6d193dSMickaël Salaün 1466*ac6d193dSMickaël Salaün sa.sa_handler = enforce_nop_handler; 1467*ac6d193dSMickaël Salaün if (sigaction(SIGUSR1, &sa, NULL)) 1468*ac6d193dSMickaël Salaün return 1; 1469*ac6d193dSMickaël Salaün 1470*ac6d193dSMickaël Salaün if (pthread_barrier_init(&barrier, NULL, nsiblings + 1)) 1471*ac6d193dSMickaël Salaün return 1; 1472*ac6d193dSMickaël Salaün for (i = 0; i < nsiblings; i++) 1473*ac6d193dSMickaël Salaün if (pthread_create(&threads[i], NULL, enforce_idle, &barrier)) 1474*ac6d193dSMickaël Salaün return 1; 1475*ac6d193dSMickaël Salaün pthread_barrier_wait(&barrier); 1476*ac6d193dSMickaël Salaün 1477*ac6d193dSMickaël Salaün data.target = pthread_self(); 1478*ac6d193dSMickaël Salaün if (pthread_create(&signaler, NULL, abort_signaler, &data)) 1479*ac6d193dSMickaël Salaün return 1; 1480*ac6d193dSMickaël Salaün 1481*ac6d193dSMickaël Salaün prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); 1482*ac6d193dSMickaël Salaün for (i = 0; i < attempts; i++) { 1483*ac6d193dSMickaël Salaün int ruleset_fd = build_enforce_ruleset(); 1484*ac6d193dSMickaël Salaün 1485*ac6d193dSMickaël Salaün if (ruleset_fd < 0) 1486*ac6d193dSMickaël Salaün break; 1487*ac6d193dSMickaël Salaün /* 1488*ac6d193dSMickaël Salaün * Ignore the result: an abort returns an error, that is fine. 1489*ac6d193dSMickaël Salaün */ 1490*ac6d193dSMickaël Salaün landlock_restrict_self(ruleset_fd, 1491*ac6d193dSMickaël Salaün LANDLOCK_RESTRICT_SELF_TSYNC); 1492*ac6d193dSMickaël Salaün close(ruleset_fd); 1493*ac6d193dSMickaël Salaün } 1494*ac6d193dSMickaël Salaün 1495*ac6d193dSMickaël Salaün data.stop = true; 1496*ac6d193dSMickaël Salaün pthread_join(signaler, NULL); 1497*ac6d193dSMickaël Salaün return 0; 1498*ac6d193dSMickaël Salaün } 1499*ac6d193dSMickaël Salaün 1500*ac6d193dSMickaël Salaün /* 1501*ac6d193dSMickaël Salaün * Verifies the abort contract: a domain aborted by a thread-sync failure emits 1502*ac6d193dSMickaël Salaün * create_domain and free_domain but zero enforce_domain. The signal race is 1503*ac6d193dSMickaël Salaün * probabilistic and -ERESTARTNOINTR may add a successful retry's lifecycle, so 1504*ac6d193dSMickaël Salaün * events are grouped by domain ID and the test SKIPs if no abort occurred. 1505*ac6d193dSMickaël Salaün */ 1506*ac6d193dSMickaël Salaün TEST_F(trace, enforce_abort) 1507*ac6d193dSMickaël Salaün { 1508*ac6d193dSMickaël Salaün pid_t pid; 1509*ac6d193dSMickaël Salaün int status, retry; 1510*ac6d193dSMickaël Salaün char *buf = NULL; 1511*ac6d193dSMickaël Salaün const char *cursor; 1512*ac6d193dSMickaël Salaün char domain[64]; 1513*ac6d193dSMickaël Salaün bool abort_found = false; 1514*ac6d193dSMickaël Salaün 1515*ac6d193dSMickaël Salaün ASSERT_EQ(0, tracefs_clear_buf()); 1516*ac6d193dSMickaël Salaün 1517*ac6d193dSMickaël Salaün /* free_domain fires from a kworker, so widen the filter first. */ 1518*ac6d193dSMickaël Salaün set_cap(_metadata, CAP_SYS_ADMIN); 1519*ac6d193dSMickaël Salaün tracefs_clear_pid_filter(); 1520*ac6d193dSMickaël Salaün clear_cap(_metadata, CAP_SYS_ADMIN); 1521*ac6d193dSMickaël Salaün 1522*ac6d193dSMickaël Salaün pid = fork(); 1523*ac6d193dSMickaël Salaün ASSERT_LE(0, pid); 1524*ac6d193dSMickaël Salaün if (pid == 0) 1525*ac6d193dSMickaël Salaün /* 1526*ac6d193dSMickaël Salaün * Match tsync_test's NUM_IDLE_THREADS: enough siblings that 1527*ac6d193dSMickaël Salaün * credential preparation runs in several serialized waves, 1528*ac6d193dSMickaël Salaün * giving the signaler a window to interrupt the thread-sync 1529*ac6d193dSMickaël Salaün * wait and abort the operation. A handful of threads finishes 1530*ac6d193dSMickaël Salaün * in a single wave, leaving no window (the abort never fires). 1531*ac6d193dSMickaël Salaün */ 1532*ac6d193dSMickaël Salaün _exit(child_abort(200, 8)); 1533*ac6d193dSMickaël Salaün 1534*ac6d193dSMickaël Salaün ASSERT_EQ(pid, waitpid(pid, &status, 0)); 1535*ac6d193dSMickaël Salaün ASSERT_TRUE(WIFEXITED(status)); 1536*ac6d193dSMickaël Salaün EXPECT_EQ(0, WEXITSTATUS(status)); 1537*ac6d193dSMickaël Salaün 1538*ac6d193dSMickaël Salaün /* Poll for the asynchronous free_domain events. */ 1539*ac6d193dSMickaël Salaün for (retry = 0; retry < 10; retry++) { 1540*ac6d193dSMickaël Salaün usleep(100000); 1541*ac6d193dSMickaël Salaün set_cap(_metadata, CAP_SYS_ADMIN); 1542*ac6d193dSMickaël Salaün free(buf); 1543*ac6d193dSMickaël Salaün buf = tracefs_read_trace(); 1544*ac6d193dSMickaël Salaün clear_cap(_metadata, CAP_SYS_ADMIN); 1545*ac6d193dSMickaël Salaün ASSERT_NE(NULL, buf); 1546*ac6d193dSMickaël Salaün } 1547*ac6d193dSMickaël Salaün 1548*ac6d193dSMickaël Salaün set_cap(_metadata, CAP_SYS_ADMIN); 1549*ac6d193dSMickaël Salaün ASSERT_EQ(0, tracefs_set_pid_filter(getpid())); 1550*ac6d193dSMickaël Salaün clear_cap(_metadata, CAP_SYS_ADMIN); 1551*ac6d193dSMickaël Salaün 1552*ac6d193dSMickaël Salaün /* 1553*ac6d193dSMickaël Salaün * Walk every create_domain and look for one whose domain ID has zero 1554*ac6d193dSMickaël Salaün * enforce_domain events but a matching free_domain: that is an aborted 1555*ac6d193dSMickaël Salaün * domain (created, never enforced, freed). 1556*ac6d193dSMickaël Salaün */ 1557*ac6d193dSMickaël Salaün cursor = buf; 1558*ac6d193dSMickaël Salaün while (tracefs_extract_field(cursor, REGEX_CREATE_DOMAIN(TRACE_TASK), 1559*ac6d193dSMickaël Salaün "domain", domain, sizeof(domain)) == 0) { 1560*ac6d193dSMickaël Salaün const char *cd, *nl; 1561*ac6d193dSMickaël Salaün char free_pattern[256]; 1562*ac6d193dSMickaël Salaün 1563*ac6d193dSMickaël Salaün if (count_enforce_matches(buf, domain, -1, -1, -1) == 0) { 1564*ac6d193dSMickaël Salaün snprintf( 1565*ac6d193dSMickaël Salaün free_pattern, sizeof(free_pattern), 1566*ac6d193dSMickaël Salaün TRACE_PREFIX( 1567*ac6d193dSMickaël Salaün KWORKER_TASK) "landlock_free_domain: " 1568*ac6d193dSMickaël Salaün "domain=%s denials=[0-9]\\+$", 1569*ac6d193dSMickaël Salaün domain); 1570*ac6d193dSMickaël Salaün if (tracefs_count_matches(buf, free_pattern) >= 1) 1571*ac6d193dSMickaël Salaün abort_found = true; 1572*ac6d193dSMickaël Salaün } 1573*ac6d193dSMickaël Salaün 1574*ac6d193dSMickaël Salaün cd = strstr(cursor, "landlock_create_domain:"); 1575*ac6d193dSMickaël Salaün if (!cd) 1576*ac6d193dSMickaël Salaün break; 1577*ac6d193dSMickaël Salaün nl = strchr(cd, '\n'); 1578*ac6d193dSMickaël Salaün if (!nl) 1579*ac6d193dSMickaël Salaün break; 1580*ac6d193dSMickaël Salaün cursor = nl + 1; 1581*ac6d193dSMickaël Salaün } 1582*ac6d193dSMickaël Salaün 1583*ac6d193dSMickaël Salaün if (!abort_found) { 1584*ac6d193dSMickaël Salaün free(buf); 1585*ac6d193dSMickaël Salaün SKIP(return, "signal race did not produce a thread-sync abort"); 1586*ac6d193dSMickaël Salaün } 1587*ac6d193dSMickaël Salaün 1588*ac6d193dSMickaël Salaün free(buf); 1589*ac6d193dSMickaël Salaün } 1590*ac6d193dSMickaël Salaün 1591*ac6d193dSMickaël Salaün /* 15922651712aSMickaël Salaün * The following tests are intentionally elided because the underlying kernel 15932651712aSMickaël Salaün * mechanisms are already validated by audit tests: 15942651712aSMickaël Salaün * 15952651712aSMickaël Salaün * - Domain ID monotonicity: validated by audit_test.c:layers. The same 15962651712aSMickaël Salaün * landlock_get_id_range() function serves both audit and trace. 15972651712aSMickaël Salaün * 15982651712aSMickaël Salaün * - Domain deallocation order (LIFO): validated by audit_test.c:layers. Trace 15992651712aSMickaël Salaün * events fire from the same free_domain_work() code path. 16002651712aSMickaël Salaün * 16012651712aSMickaël Salaün * - Max-layer stacking (16 domains): validated by audit_test.c:layers. 16022651712aSMickaël Salaün * 16032651712aSMickaël Salaün * - IPv6 network tests: IPv6 hook dispatch uses the same 16042651712aSMickaël Salaün * current_check_access_socket() as IPv4, validated by net_test.c:audit tests. 16052651712aSMickaël Salaün * 16062651712aSMickaël Salaün * - Per-access-right full matrix (all 16 FS rights): hook dispatch is validated 16072651712aSMickaël Salaün * by fs_test.c:audit tests. Trace tests verify representative samples to 16082651712aSMickaël Salaün * ensure bitmask encoding is correct. 16092651712aSMickaël Salaün * 16102651712aSMickaël Salaün * - Combined log flag variants (e.g., LOG_SUBDOMAINS_OFF + LOG_NEW_EXEC_ON): 16112651712aSMickaël Salaün * individual flag tests above cover each flag's effect on trace fields. Flag 16122651712aSMickaël Salaün * combination logic is validated by audit_test.c:audit_flags tests. 16132651712aSMickaël Salaün * 16142651712aSMickaël Salaün * - fs.refer multi-record denials and fs.change_topology (mount): 16152651712aSMickaël Salaün * trace_denial() uses the same code path for all FS request types. The 16162651712aSMickaël Salaün * DENTRY union member is validated by the deny_access_fs_fields 16172651712aSMickaël Salaün * test. Audit tests in fs_test.c cover refer and mount denial specifics. 16182651712aSMickaël Salaün */ 16192651712aSMickaël Salaün 16202651712aSMickaël Salaün TEST_HARNESS_MAIN 1621