133e65b0dSMickaël Salaün // SPDX-License-Identifier: GPL-2.0-only 233e65b0dSMickaël Salaün /* 333e65b0dSMickaël Salaün * Landlock - Audit helpers 433e65b0dSMickaël Salaün * 533e65b0dSMickaël Salaün * Copyright © 2023-2025 Microsoft Corporation 633e65b0dSMickaël Salaün */ 733e65b0dSMickaël Salaün 833e65b0dSMickaël Salaün #include <kunit/test.h> 933e65b0dSMickaël Salaün #include <linux/audit.h> 102fc80c69SMickaël Salaün #include <linux/bitops.h> 1133e65b0dSMickaël Salaün #include <linux/lsm_audit.h> 121d636984SMickaël Salaün #include <linux/pid.h> 132fc80c69SMickaël Salaün #include <uapi/linux/landlock.h> 1433e65b0dSMickaël Salaün 1520fd2954SMickaël Salaün #include "access.h" 1633e65b0dSMickaël Salaün #include "audit.h" 172fc80c69SMickaël Salaün #include "common.h" 1833e65b0dSMickaël Salaün #include "cred.h" 1933e65b0dSMickaël Salaün #include "domain.h" 2033e65b0dSMickaël Salaün #include "limits.h" 2133e65b0dSMickaël Salaün #include "ruleset.h" 2233e65b0dSMickaël Salaün 232fc80c69SMickaël Salaün static const char *const fs_access_strings[] = { 242fc80c69SMickaël Salaün [BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = "fs.execute", 252fc80c69SMickaël Salaün [BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)] = "fs.write_file", 262fc80c69SMickaël Salaün [BIT_INDEX(LANDLOCK_ACCESS_FS_READ_FILE)] = "fs.read_file", 272fc80c69SMickaël Salaün [BIT_INDEX(LANDLOCK_ACCESS_FS_READ_DIR)] = "fs.read_dir", 282fc80c69SMickaël Salaün [BIT_INDEX(LANDLOCK_ACCESS_FS_REMOVE_DIR)] = "fs.remove_dir", 292fc80c69SMickaël Salaün [BIT_INDEX(LANDLOCK_ACCESS_FS_REMOVE_FILE)] = "fs.remove_file", 302fc80c69SMickaël Salaün [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_CHAR)] = "fs.make_char", 312fc80c69SMickaël Salaün [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_DIR)] = "fs.make_dir", 322fc80c69SMickaël Salaün [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_REG)] = "fs.make_reg", 332fc80c69SMickaël Salaün [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_SOCK)] = "fs.make_sock", 342fc80c69SMickaël Salaün [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_FIFO)] = "fs.make_fifo", 352fc80c69SMickaël Salaün [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_BLOCK)] = "fs.make_block", 362fc80c69SMickaël Salaün [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_SYM)] = "fs.make_sym", 372fc80c69SMickaël Salaün [BIT_INDEX(LANDLOCK_ACCESS_FS_REFER)] = "fs.refer", 382fc80c69SMickaël Salaün [BIT_INDEX(LANDLOCK_ACCESS_FS_TRUNCATE)] = "fs.truncate", 392fc80c69SMickaël Salaün [BIT_INDEX(LANDLOCK_ACCESS_FS_IOCTL_DEV)] = "fs.ioctl_dev", 402fc80c69SMickaël Salaün }; 412fc80c69SMickaël Salaün 422fc80c69SMickaël Salaün static_assert(ARRAY_SIZE(fs_access_strings) == LANDLOCK_NUM_ACCESS_FS); 432fc80c69SMickaël Salaün 449f74411aSMickaël Salaün static const char *const net_access_strings[] = { 459f74411aSMickaël Salaün [BIT_INDEX(LANDLOCK_ACCESS_NET_BIND_TCP)] = "net.bind_tcp", 469f74411aSMickaël Salaün [BIT_INDEX(LANDLOCK_ACCESS_NET_CONNECT_TCP)] = "net.connect_tcp", 479f74411aSMickaël Salaün }; 489f74411aSMickaël Salaün 499f74411aSMickaël Salaün static_assert(ARRAY_SIZE(net_access_strings) == LANDLOCK_NUM_ACCESS_NET); 509f74411aSMickaël Salaün 512fc80c69SMickaël Salaün static __attribute_const__ const char * 522fc80c69SMickaël Salaün get_blocker(const enum landlock_request_type type, 532fc80c69SMickaël Salaün const unsigned long access_bit) 5433e65b0dSMickaël Salaün { 5533e65b0dSMickaël Salaün switch (type) { 5633e65b0dSMickaël Salaün case LANDLOCK_REQUEST_PTRACE: 572fc80c69SMickaël Salaün WARN_ON_ONCE(access_bit != -1); 5833e65b0dSMickaël Salaün return "ptrace"; 59c56f6496SMickaël Salaün 60c56f6496SMickaël Salaün case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY: 612fc80c69SMickaël Salaün WARN_ON_ONCE(access_bit != -1); 62c56f6496SMickaël Salaün return "fs.change_topology"; 632fc80c69SMickaël Salaün 642fc80c69SMickaël Salaün case LANDLOCK_REQUEST_FS_ACCESS: 652fc80c69SMickaël Salaün if (WARN_ON_ONCE(access_bit >= ARRAY_SIZE(fs_access_strings))) 662fc80c69SMickaël Salaün return "unknown"; 672fc80c69SMickaël Salaün return fs_access_strings[access_bit]; 689f74411aSMickaël Salaün 699f74411aSMickaël Salaün case LANDLOCK_REQUEST_NET_ACCESS: 709f74411aSMickaël Salaün if (WARN_ON_ONCE(access_bit >= ARRAY_SIZE(net_access_strings))) 719f74411aSMickaël Salaün return "unknown"; 729f74411aSMickaël Salaün return net_access_strings[access_bit]; 731176a15bSMickaël Salaün 741176a15bSMickaël Salaün case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET: 751176a15bSMickaël Salaün WARN_ON_ONCE(access_bit != -1); 761176a15bSMickaël Salaün return "scope.abstract_unix_socket"; 771176a15bSMickaël Salaün 781176a15bSMickaël Salaün case LANDLOCK_REQUEST_SCOPE_SIGNAL: 791176a15bSMickaël Salaün WARN_ON_ONCE(access_bit != -1); 801176a15bSMickaël Salaün return "scope.signal"; 8133e65b0dSMickaël Salaün } 8233e65b0dSMickaël Salaün 8333e65b0dSMickaël Salaün WARN_ON_ONCE(1); 8433e65b0dSMickaël Salaün return "unknown"; 8533e65b0dSMickaël Salaün } 8633e65b0dSMickaël Salaün 8733e65b0dSMickaël Salaün static void log_blockers(struct audit_buffer *const ab, 882fc80c69SMickaël Salaün const enum landlock_request_type type, 892fc80c69SMickaël Salaün const access_mask_t access) 9033e65b0dSMickaël Salaün { 912fc80c69SMickaël Salaün const unsigned long access_mask = access; 922fc80c69SMickaël Salaün unsigned long access_bit; 932fc80c69SMickaël Salaün bool is_first = true; 942fc80c69SMickaël Salaün 952fc80c69SMickaël Salaün for_each_set_bit(access_bit, &access_mask, BITS_PER_TYPE(access)) { 962fc80c69SMickaël Salaün audit_log_format(ab, "%s%s", is_first ? "" : ",", 972fc80c69SMickaël Salaün get_blocker(type, access_bit)); 982fc80c69SMickaël Salaün is_first = false; 992fc80c69SMickaël Salaün } 1002fc80c69SMickaël Salaün if (is_first) 1012fc80c69SMickaël Salaün audit_log_format(ab, "%s", get_blocker(type, -1)); 10233e65b0dSMickaël Salaün } 10333e65b0dSMickaël Salaün 1041d636984SMickaël Salaün static void log_domain(struct landlock_hierarchy *const hierarchy) 1051d636984SMickaël Salaün { 1061d636984SMickaël Salaün struct audit_buffer *ab; 1071d636984SMickaël Salaün 1081d636984SMickaël Salaün /* Ignores already logged domains. */ 1091d636984SMickaël Salaün if (READ_ONCE(hierarchy->log_status) == LANDLOCK_LOG_RECORDED) 1101d636984SMickaël Salaün return; 1111d636984SMickaël Salaün 1121d636984SMickaël Salaün /* Uses consistent allocation flags wrt common_lsm_audit(). */ 1131d636984SMickaël Salaün ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN, 1141d636984SMickaël Salaün AUDIT_LANDLOCK_DOMAIN); 1151d636984SMickaël Salaün if (!ab) 1161d636984SMickaël Salaün return; 1171d636984SMickaël Salaün 1181d636984SMickaël Salaün WARN_ON_ONCE(hierarchy->id == 0); 1191d636984SMickaël Salaün audit_log_format( 1201d636984SMickaël Salaün ab, 1211d636984SMickaël Salaün "domain=%llx status=allocated mode=enforcing pid=%d uid=%u exe=", 1221d636984SMickaël Salaün hierarchy->id, pid_nr(hierarchy->details->pid), 1231d636984SMickaël Salaün hierarchy->details->uid); 1241d636984SMickaël Salaün audit_log_untrustedstring(ab, hierarchy->details->exe_path); 1251d636984SMickaël Salaün audit_log_format(ab, " comm="); 1261d636984SMickaël Salaün audit_log_untrustedstring(ab, hierarchy->details->comm); 1271d636984SMickaël Salaün audit_log_end(ab); 1281d636984SMickaël Salaün 1291d636984SMickaël Salaün /* 1301d636984SMickaël Salaün * There may be race condition leading to logging of the same domain 1311d636984SMickaël Salaün * several times but that is OK. 1321d636984SMickaël Salaün */ 1331d636984SMickaël Salaün WRITE_ONCE(hierarchy->log_status, LANDLOCK_LOG_RECORDED); 1341d636984SMickaël Salaün } 1351d636984SMickaël Salaün 13633e65b0dSMickaël Salaün static struct landlock_hierarchy * 13733e65b0dSMickaël Salaün get_hierarchy(const struct landlock_ruleset *const domain, const size_t layer) 13833e65b0dSMickaël Salaün { 13933e65b0dSMickaël Salaün struct landlock_hierarchy *hierarchy = domain->hierarchy; 14033e65b0dSMickaël Salaün ssize_t i; 14133e65b0dSMickaël Salaün 14233e65b0dSMickaël Salaün if (WARN_ON_ONCE(layer >= domain->num_layers)) 14333e65b0dSMickaël Salaün return hierarchy; 14433e65b0dSMickaël Salaün 14533e65b0dSMickaël Salaün for (i = domain->num_layers - 1; i > layer; i--) { 14633e65b0dSMickaël Salaün if (WARN_ON_ONCE(!hierarchy->parent)) 14733e65b0dSMickaël Salaün break; 14833e65b0dSMickaël Salaün 14933e65b0dSMickaël Salaün hierarchy = hierarchy->parent; 15033e65b0dSMickaël Salaün } 15133e65b0dSMickaël Salaün 15233e65b0dSMickaël Salaün return hierarchy; 15333e65b0dSMickaël Salaün } 15433e65b0dSMickaël Salaün 15533e65b0dSMickaël Salaün #ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST 15633e65b0dSMickaël Salaün 15733e65b0dSMickaël Salaün static void test_get_hierarchy(struct kunit *const test) 15833e65b0dSMickaël Salaün { 15933e65b0dSMickaël Salaün struct landlock_hierarchy dom0_hierarchy = { 16033e65b0dSMickaël Salaün .id = 10, 16133e65b0dSMickaël Salaün }; 16233e65b0dSMickaël Salaün struct landlock_hierarchy dom1_hierarchy = { 16333e65b0dSMickaël Salaün .parent = &dom0_hierarchy, 16433e65b0dSMickaël Salaün .id = 20, 16533e65b0dSMickaël Salaün }; 16633e65b0dSMickaël Salaün struct landlock_hierarchy dom2_hierarchy = { 16733e65b0dSMickaël Salaün .parent = &dom1_hierarchy, 16833e65b0dSMickaël Salaün .id = 30, 16933e65b0dSMickaël Salaün }; 17033e65b0dSMickaël Salaün struct landlock_ruleset dom2 = { 17133e65b0dSMickaël Salaün .hierarchy = &dom2_hierarchy, 17233e65b0dSMickaël Salaün .num_layers = 3, 17333e65b0dSMickaël Salaün }; 17433e65b0dSMickaël Salaün 17533e65b0dSMickaël Salaün KUNIT_EXPECT_EQ(test, 10, get_hierarchy(&dom2, 0)->id); 17633e65b0dSMickaël Salaün KUNIT_EXPECT_EQ(test, 20, get_hierarchy(&dom2, 1)->id); 17733e65b0dSMickaël Salaün KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, 2)->id); 17833e65b0dSMickaël Salaün KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, -1)->id); 17933e65b0dSMickaël Salaün } 18033e65b0dSMickaël Salaün 18133e65b0dSMickaël Salaün #endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ 18233e65b0dSMickaël Salaün 1832fc80c69SMickaël Salaün static size_t get_denied_layer(const struct landlock_ruleset *const domain, 1842fc80c69SMickaël Salaün access_mask_t *const access_request, 1852fc80c69SMickaël Salaün const layer_mask_t (*const layer_masks)[], 1862fc80c69SMickaël Salaün const size_t layer_masks_size) 1872fc80c69SMickaël Salaün { 1882fc80c69SMickaël Salaün const unsigned long access_req = *access_request; 1892fc80c69SMickaël Salaün unsigned long access_bit; 1902fc80c69SMickaël Salaün access_mask_t missing = 0; 1912fc80c69SMickaël Salaün long youngest_layer = -1; 1922fc80c69SMickaël Salaün 1932fc80c69SMickaël Salaün for_each_set_bit(access_bit, &access_req, layer_masks_size) { 1942fc80c69SMickaël Salaün const access_mask_t mask = (*layer_masks)[access_bit]; 1952fc80c69SMickaël Salaün long layer; 1962fc80c69SMickaël Salaün 1972fc80c69SMickaël Salaün if (!mask) 1982fc80c69SMickaël Salaün continue; 1992fc80c69SMickaël Salaün 2002fc80c69SMickaël Salaün /* __fls(1) == 0 */ 2012fc80c69SMickaël Salaün layer = __fls(mask); 2022fc80c69SMickaël Salaün if (layer > youngest_layer) { 2032fc80c69SMickaël Salaün youngest_layer = layer; 2042fc80c69SMickaël Salaün missing = BIT(access_bit); 2052fc80c69SMickaël Salaün } else if (layer == youngest_layer) { 2062fc80c69SMickaël Salaün missing |= BIT(access_bit); 2072fc80c69SMickaël Salaün } 2082fc80c69SMickaël Salaün } 2092fc80c69SMickaël Salaün 2102fc80c69SMickaël Salaün *access_request = missing; 2112fc80c69SMickaël Salaün if (youngest_layer == -1) 2122fc80c69SMickaël Salaün return domain->num_layers - 1; 2132fc80c69SMickaël Salaün 2142fc80c69SMickaël Salaün return youngest_layer; 2152fc80c69SMickaël Salaün } 2162fc80c69SMickaël Salaün 2172fc80c69SMickaël Salaün #ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST 2182fc80c69SMickaël Salaün 2192fc80c69SMickaël Salaün static void test_get_denied_layer(struct kunit *const test) 2202fc80c69SMickaël Salaün { 2212fc80c69SMickaël Salaün const struct landlock_ruleset dom = { 2222fc80c69SMickaël Salaün .num_layers = 5, 2232fc80c69SMickaël Salaün }; 2242fc80c69SMickaël Salaün const layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = { 2252fc80c69SMickaël Salaün [BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT(0), 2262fc80c69SMickaël Salaün [BIT_INDEX(LANDLOCK_ACCESS_FS_READ_FILE)] = BIT(1), 2272fc80c69SMickaël Salaün [BIT_INDEX(LANDLOCK_ACCESS_FS_READ_DIR)] = BIT(1) | BIT(0), 2282fc80c69SMickaël Salaün [BIT_INDEX(LANDLOCK_ACCESS_FS_REMOVE_DIR)] = BIT(2), 2292fc80c69SMickaël Salaün }; 2302fc80c69SMickaël Salaün access_mask_t access; 2312fc80c69SMickaël Salaün 2322fc80c69SMickaël Salaün access = LANDLOCK_ACCESS_FS_EXECUTE; 2332fc80c69SMickaël Salaün KUNIT_EXPECT_EQ(test, 0, 2342fc80c69SMickaël Salaün get_denied_layer(&dom, &access, &layer_masks, 2352fc80c69SMickaël Salaün sizeof(layer_masks))); 2362fc80c69SMickaël Salaün KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_EXECUTE); 2372fc80c69SMickaël Salaün 2382fc80c69SMickaël Salaün access = LANDLOCK_ACCESS_FS_READ_FILE; 2392fc80c69SMickaël Salaün KUNIT_EXPECT_EQ(test, 1, 2402fc80c69SMickaël Salaün get_denied_layer(&dom, &access, &layer_masks, 2412fc80c69SMickaël Salaün sizeof(layer_masks))); 2422fc80c69SMickaël Salaün KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_FILE); 2432fc80c69SMickaël Salaün 2442fc80c69SMickaël Salaün access = LANDLOCK_ACCESS_FS_READ_DIR; 2452fc80c69SMickaël Salaün KUNIT_EXPECT_EQ(test, 1, 2462fc80c69SMickaël Salaün get_denied_layer(&dom, &access, &layer_masks, 2472fc80c69SMickaël Salaün sizeof(layer_masks))); 2482fc80c69SMickaël Salaün KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR); 2492fc80c69SMickaël Salaün 2502fc80c69SMickaël Salaün access = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR; 2512fc80c69SMickaël Salaün KUNIT_EXPECT_EQ(test, 1, 2522fc80c69SMickaël Salaün get_denied_layer(&dom, &access, &layer_masks, 2532fc80c69SMickaël Salaün sizeof(layer_masks))); 2542fc80c69SMickaël Salaün KUNIT_EXPECT_EQ(test, access, 2552fc80c69SMickaël Salaün LANDLOCK_ACCESS_FS_READ_FILE | 2562fc80c69SMickaël Salaün LANDLOCK_ACCESS_FS_READ_DIR); 2572fc80c69SMickaël Salaün 2582fc80c69SMickaël Salaün access = LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_READ_DIR; 2592fc80c69SMickaël Salaün KUNIT_EXPECT_EQ(test, 1, 2602fc80c69SMickaël Salaün get_denied_layer(&dom, &access, &layer_masks, 2612fc80c69SMickaël Salaün sizeof(layer_masks))); 2622fc80c69SMickaël Salaün KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR); 2632fc80c69SMickaël Salaün 2642fc80c69SMickaël Salaün access = LANDLOCK_ACCESS_FS_WRITE_FILE; 2652fc80c69SMickaël Salaün KUNIT_EXPECT_EQ(test, 4, 2662fc80c69SMickaël Salaün get_denied_layer(&dom, &access, &layer_masks, 2672fc80c69SMickaël Salaün sizeof(layer_masks))); 2682fc80c69SMickaël Salaün KUNIT_EXPECT_EQ(test, access, 0); 2692fc80c69SMickaël Salaün } 2702fc80c69SMickaël Salaün 2712fc80c69SMickaël Salaün #endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ 2722fc80c69SMickaël Salaün 27320fd2954SMickaël Salaün static size_t 27420fd2954SMickaël Salaün get_layer_from_deny_masks(access_mask_t *const access_request, 27520fd2954SMickaël Salaün const access_mask_t all_existing_optional_access, 27620fd2954SMickaël Salaün const deny_masks_t deny_masks) 27720fd2954SMickaël Salaün { 27820fd2954SMickaël Salaün const unsigned long access_opt = all_existing_optional_access; 27920fd2954SMickaël Salaün const unsigned long access_req = *access_request; 28020fd2954SMickaël Salaün access_mask_t missing = 0; 28120fd2954SMickaël Salaün size_t youngest_layer = 0; 28220fd2954SMickaël Salaün size_t access_index = 0; 28320fd2954SMickaël Salaün unsigned long access_bit; 28420fd2954SMickaël Salaün 28520fd2954SMickaël Salaün /* This will require change with new object types. */ 28620fd2954SMickaël Salaün WARN_ON_ONCE(access_opt != _LANDLOCK_ACCESS_FS_OPTIONAL); 28720fd2954SMickaël Salaün 28820fd2954SMickaël Salaün for_each_set_bit(access_bit, &access_opt, 28920fd2954SMickaël Salaün BITS_PER_TYPE(access_mask_t)) { 29020fd2954SMickaël Salaün if (access_req & BIT(access_bit)) { 29120fd2954SMickaël Salaün const size_t layer = 29220fd2954SMickaël Salaün (deny_masks >> (access_index * 4)) & 29320fd2954SMickaël Salaün (LANDLOCK_MAX_NUM_LAYERS - 1); 29420fd2954SMickaël Salaün 29520fd2954SMickaël Salaün if (layer > youngest_layer) { 29620fd2954SMickaël Salaün youngest_layer = layer; 29720fd2954SMickaël Salaün missing = BIT(access_bit); 29820fd2954SMickaël Salaün } else if (layer == youngest_layer) { 29920fd2954SMickaël Salaün missing |= BIT(access_bit); 30020fd2954SMickaël Salaün } 30120fd2954SMickaël Salaün } 30220fd2954SMickaël Salaün access_index++; 30320fd2954SMickaël Salaün } 30420fd2954SMickaël Salaün 30520fd2954SMickaël Salaün *access_request = missing; 30620fd2954SMickaël Salaün return youngest_layer; 30720fd2954SMickaël Salaün } 30820fd2954SMickaël Salaün 30920fd2954SMickaël Salaün #ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST 31020fd2954SMickaël Salaün 31120fd2954SMickaël Salaün static void test_get_layer_from_deny_masks(struct kunit *const test) 31220fd2954SMickaël Salaün { 31320fd2954SMickaël Salaün deny_masks_t deny_mask; 31420fd2954SMickaël Salaün access_mask_t access; 31520fd2954SMickaël Salaün 31620fd2954SMickaël Salaün /* truncate:0 ioctl_dev:2 */ 31720fd2954SMickaël Salaün deny_mask = 0x20; 31820fd2954SMickaël Salaün 31920fd2954SMickaël Salaün access = LANDLOCK_ACCESS_FS_TRUNCATE; 32020fd2954SMickaël Salaün KUNIT_EXPECT_EQ(test, 0, 32120fd2954SMickaël Salaün get_layer_from_deny_masks(&access, 32220fd2954SMickaël Salaün _LANDLOCK_ACCESS_FS_OPTIONAL, 32320fd2954SMickaël Salaün deny_mask)); 32420fd2954SMickaël Salaün KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); 32520fd2954SMickaël Salaün 32620fd2954SMickaël Salaün access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV; 32720fd2954SMickaël Salaün KUNIT_EXPECT_EQ(test, 2, 32820fd2954SMickaël Salaün get_layer_from_deny_masks(&access, 32920fd2954SMickaël Salaün _LANDLOCK_ACCESS_FS_OPTIONAL, 33020fd2954SMickaël Salaün deny_mask)); 33120fd2954SMickaël Salaün KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV); 33220fd2954SMickaël Salaün 33320fd2954SMickaël Salaün /* truncate:15 ioctl_dev:15 */ 33420fd2954SMickaël Salaün deny_mask = 0xff; 33520fd2954SMickaël Salaün 33620fd2954SMickaël Salaün access = LANDLOCK_ACCESS_FS_TRUNCATE; 33720fd2954SMickaël Salaün KUNIT_EXPECT_EQ(test, 15, 33820fd2954SMickaël Salaün get_layer_from_deny_masks(&access, 33920fd2954SMickaël Salaün _LANDLOCK_ACCESS_FS_OPTIONAL, 34020fd2954SMickaël Salaün deny_mask)); 34120fd2954SMickaël Salaün KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); 34220fd2954SMickaël Salaün 34320fd2954SMickaël Salaün access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV; 34420fd2954SMickaël Salaün KUNIT_EXPECT_EQ(test, 15, 34520fd2954SMickaël Salaün get_layer_from_deny_masks(&access, 34620fd2954SMickaël Salaün _LANDLOCK_ACCESS_FS_OPTIONAL, 34720fd2954SMickaël Salaün deny_mask)); 34820fd2954SMickaël Salaün KUNIT_EXPECT_EQ(test, access, 34920fd2954SMickaël Salaün LANDLOCK_ACCESS_FS_TRUNCATE | 35020fd2954SMickaël Salaün LANDLOCK_ACCESS_FS_IOCTL_DEV); 35120fd2954SMickaël Salaün } 35220fd2954SMickaël Salaün 35320fd2954SMickaël Salaün #endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ 35420fd2954SMickaël Salaün 35533e65b0dSMickaël Salaün static bool is_valid_request(const struct landlock_request *const request) 35633e65b0dSMickaël Salaün { 35733e65b0dSMickaël Salaün if (WARN_ON_ONCE(request->layer_plus_one > LANDLOCK_MAX_NUM_LAYERS)) 35833e65b0dSMickaël Salaün return false; 35933e65b0dSMickaël Salaün 3602fc80c69SMickaël Salaün if (WARN_ON_ONCE(!(!!request->layer_plus_one ^ !!request->access))) 3612fc80c69SMickaël Salaün return false; 3622fc80c69SMickaël Salaün 3632fc80c69SMickaël Salaün if (request->access) { 36420fd2954SMickaël Salaün if (WARN_ON_ONCE(!(!!request->layer_masks ^ 36520fd2954SMickaël Salaün !!request->all_existing_optional_access))) 3662fc80c69SMickaël Salaün return false; 3672fc80c69SMickaël Salaün } else { 36820fd2954SMickaël Salaün if (WARN_ON_ONCE(request->layer_masks || 36920fd2954SMickaël Salaün request->all_existing_optional_access)) 3702fc80c69SMickaël Salaün return false; 3712fc80c69SMickaël Salaün } 3722fc80c69SMickaël Salaün 3732fc80c69SMickaël Salaün if (WARN_ON_ONCE(!!request->layer_masks ^ !!request->layer_masks_size)) 37433e65b0dSMickaël Salaün return false; 37533e65b0dSMickaël Salaün 37620fd2954SMickaël Salaün if (request->deny_masks) { 37720fd2954SMickaël Salaün if (WARN_ON_ONCE(!request->all_existing_optional_access)) 37820fd2954SMickaël Salaün return false; 37920fd2954SMickaël Salaün } 38020fd2954SMickaël Salaün 38133e65b0dSMickaël Salaün return true; 38233e65b0dSMickaël Salaün } 38333e65b0dSMickaël Salaün 38433e65b0dSMickaël Salaün /** 38533e65b0dSMickaël Salaün * landlock_log_denial - Create audit records related to a denial 38633e65b0dSMickaël Salaün * 38733e65b0dSMickaël Salaün * @subject: The Landlock subject's credential denying an action. 38833e65b0dSMickaël Salaün * @request: Detail of the user space request. 38933e65b0dSMickaël Salaün */ 39033e65b0dSMickaël Salaün void landlock_log_denial(const struct landlock_cred_security *const subject, 39133e65b0dSMickaël Salaün const struct landlock_request *const request) 39233e65b0dSMickaël Salaün { 39333e65b0dSMickaël Salaün struct audit_buffer *ab; 39433e65b0dSMickaël Salaün struct landlock_hierarchy *youngest_denied; 39533e65b0dSMickaël Salaün size_t youngest_layer; 3962fc80c69SMickaël Salaün access_mask_t missing; 39733e65b0dSMickaël Salaün 39833e65b0dSMickaël Salaün if (WARN_ON_ONCE(!subject || !subject->domain || 39933e65b0dSMickaël Salaün !subject->domain->hierarchy || !request)) 40033e65b0dSMickaël Salaün return; 40133e65b0dSMickaël Salaün 40233e65b0dSMickaël Salaün if (!is_valid_request(request)) 40333e65b0dSMickaël Salaün return; 40433e65b0dSMickaël Salaün 4052fc80c69SMickaël Salaün missing = request->access; 4062fc80c69SMickaël Salaün if (missing) { 4072fc80c69SMickaël Salaün /* Gets the nearest domain that denies the request. */ 4082fc80c69SMickaël Salaün if (request->layer_masks) { 4092fc80c69SMickaël Salaün youngest_layer = get_denied_layer( 4102fc80c69SMickaël Salaün subject->domain, &missing, request->layer_masks, 4112fc80c69SMickaël Salaün request->layer_masks_size); 4122fc80c69SMickaël Salaün } else { 41320fd2954SMickaël Salaün youngest_layer = get_layer_from_deny_masks( 41420fd2954SMickaël Salaün &missing, request->all_existing_optional_access, 41520fd2954SMickaël Salaün request->deny_masks); 4162fc80c69SMickaël Salaün } 4172fc80c69SMickaël Salaün youngest_denied = 4182fc80c69SMickaël Salaün get_hierarchy(subject->domain, youngest_layer); 4192fc80c69SMickaël Salaün } else { 42033e65b0dSMickaël Salaün youngest_layer = request->layer_plus_one - 1; 4212fc80c69SMickaël Salaün youngest_denied = 4222fc80c69SMickaël Salaün get_hierarchy(subject->domain, youngest_layer); 4232fc80c69SMickaël Salaün } 42433e65b0dSMickaël Salaün 425*12bfcda7SMickaël Salaün if (READ_ONCE(youngest_denied->log_status) == LANDLOCK_LOG_DISABLED) 426*12bfcda7SMickaël Salaün return; 427*12bfcda7SMickaël Salaün 4281d636984SMickaël Salaün /* 4291d636984SMickaël Salaün * Consistently keeps track of the number of denied access requests 4301d636984SMickaël Salaün * even if audit is currently disabled, or if audit rules currently 4311d636984SMickaël Salaün * exclude this record type, or if landlock_restrict_self(2)'s flags 4321d636984SMickaël Salaün * quiet logs. 4331d636984SMickaël Salaün */ 4341d636984SMickaël Salaün atomic64_inc(&youngest_denied->num_denials); 4351d636984SMickaël Salaün 4361d636984SMickaël Salaün if (!audit_enabled) 4371d636984SMickaël Salaün return; 4381d636984SMickaël Salaün 439*12bfcda7SMickaël Salaün /* Checks if the current exec was restricting itself. */ 440*12bfcda7SMickaël Salaün if (subject->domain_exec & (1 << youngest_layer)) { 441*12bfcda7SMickaël Salaün /* Ignores denials for the same execution. */ 442*12bfcda7SMickaël Salaün if (!youngest_denied->log_same_exec) 44333e65b0dSMickaël Salaün return; 444*12bfcda7SMickaël Salaün } else { 445*12bfcda7SMickaël Salaün /* Ignores denials after a new execution. */ 446*12bfcda7SMickaël Salaün if (!youngest_denied->log_new_exec) 447*12bfcda7SMickaël Salaün return; 448*12bfcda7SMickaël Salaün } 44933e65b0dSMickaël Salaün 45033e65b0dSMickaël Salaün /* Uses consistent allocation flags wrt common_lsm_audit(). */ 45133e65b0dSMickaël Salaün ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN, 45233e65b0dSMickaël Salaün AUDIT_LANDLOCK_ACCESS); 45333e65b0dSMickaël Salaün if (!ab) 45433e65b0dSMickaël Salaün return; 45533e65b0dSMickaël Salaün 45633e65b0dSMickaël Salaün audit_log_format(ab, "domain=%llx blockers=", youngest_denied->id); 4572fc80c69SMickaël Salaün log_blockers(ab, request->type, missing); 45833e65b0dSMickaël Salaün audit_log_lsm_data(ab, &request->audit); 45933e65b0dSMickaël Salaün audit_log_end(ab); 4601d636984SMickaël Salaün 4611d636984SMickaël Salaün /* Logs this domain the first time it shows in log. */ 4621d636984SMickaël Salaün log_domain(youngest_denied); 4631d636984SMickaël Salaün } 4641d636984SMickaël Salaün 4651d636984SMickaël Salaün /** 4661d636984SMickaël Salaün * landlock_log_drop_domain - Create an audit record on domain deallocation 4671d636984SMickaël Salaün * 4681d636984SMickaël Salaün * @hierarchy: The domain's hierarchy being deallocated. 4691d636984SMickaël Salaün * 4701d636984SMickaël Salaün * Only domains which previously appeared in the audit logs are logged again. 4711d636984SMickaël Salaün * This is useful to know when a domain will never show again in the audit log. 4721d636984SMickaël Salaün * 4731d636984SMickaël Salaün * Called in a work queue scheduled by landlock_put_ruleset_deferred() called 4741d636984SMickaël Salaün * by hook_cred_free(). 4751d636984SMickaël Salaün */ 4761d636984SMickaël Salaün void landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy) 4771d636984SMickaël Salaün { 4781d636984SMickaël Salaün struct audit_buffer *ab; 4791d636984SMickaël Salaün 4801d636984SMickaël Salaün if (WARN_ON_ONCE(!hierarchy)) 4811d636984SMickaël Salaün return; 4821d636984SMickaël Salaün 4831d636984SMickaël Salaün if (!audit_enabled) 4841d636984SMickaël Salaün return; 4851d636984SMickaël Salaün 4861d636984SMickaël Salaün /* Ignores domains that were not logged. */ 4871d636984SMickaël Salaün if (READ_ONCE(hierarchy->log_status) != LANDLOCK_LOG_RECORDED) 4881d636984SMickaël Salaün return; 4891d636984SMickaël Salaün 4901d636984SMickaël Salaün /* 4911d636984SMickaël Salaün * If logging of domain allocation succeeded, warns about failure to log 4921d636984SMickaël Salaün * domain deallocation to highlight unbalanced domain lifetime logs. 4931d636984SMickaël Salaün */ 4941d636984SMickaël Salaün ab = audit_log_start(audit_context(), GFP_KERNEL, 4951d636984SMickaël Salaün AUDIT_LANDLOCK_DOMAIN); 4961d636984SMickaël Salaün if (!ab) 4971d636984SMickaël Salaün return; 4981d636984SMickaël Salaün 4991d636984SMickaël Salaün audit_log_format(ab, "domain=%llx status=deallocated denials=%llu", 5001d636984SMickaël Salaün hierarchy->id, atomic64_read(&hierarchy->num_denials)); 5011d636984SMickaël Salaün audit_log_end(ab); 50233e65b0dSMickaël Salaün } 50333e65b0dSMickaël Salaün 50433e65b0dSMickaël Salaün #ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST 50533e65b0dSMickaël Salaün 50633e65b0dSMickaël Salaün static struct kunit_case test_cases[] = { 50733e65b0dSMickaël Salaün /* clang-format off */ 50833e65b0dSMickaël Salaün KUNIT_CASE(test_get_hierarchy), 5092fc80c69SMickaël Salaün KUNIT_CASE(test_get_denied_layer), 51020fd2954SMickaël Salaün KUNIT_CASE(test_get_layer_from_deny_masks), 51133e65b0dSMickaël Salaün {} 51233e65b0dSMickaël Salaün /* clang-format on */ 51333e65b0dSMickaël Salaün }; 51433e65b0dSMickaël Salaün 51533e65b0dSMickaël Salaün static struct kunit_suite test_suite = { 51633e65b0dSMickaël Salaün .name = "landlock_audit", 51733e65b0dSMickaël Salaün .test_cases = test_cases, 51833e65b0dSMickaël Salaün }; 51933e65b0dSMickaël Salaün 52033e65b0dSMickaël Salaün kunit_test_suite(test_suite); 52133e65b0dSMickaël Salaün 52233e65b0dSMickaël Salaün #endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ 523