Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

landlock: Log file-related denials

Add audit support for path_mkdir, path_mknod, path_symlink, path_unlink,
path_rmdir, path_truncate, path_link, path_rename, and file_open hooks.

The dedicated blockers are:
- fs.execute
- fs.write_file
- fs.read_file
- fs.read_dir
- fs.remove_dir
- fs.remove_file
- fs.make_char
- fs.make_dir
- fs.make_reg
- fs.make_sock
- fs.make_fifo
- fs.make_block
- fs.make_sym
- fs.refer
- fs.truncate
- fs.ioctl_dev

Audit event sample for a denied link action:

type=LANDLOCK_DENY msg=audit(1729738800.349:44): domain=195ba459b blockers=fs.refer path="/usr/bin" dev="vda2" ino=351
type=LANDLOCK_DENY msg=audit(1729738800.349:44): domain=195ba459b blockers=fs.make_reg,fs.refer path="/usr/local" dev="vda2" ino=365

We could pack blocker names (e.g. "fs:make_reg,refer") but that would
increase complexity for the kernel and log parsers. Moreover, this
could not handle blockers of different classes (e.g. fs and net). Make
it simple and flexible instead.

Add KUnit tests to check the identification from a layer_mask_t array of
the first layer level denying such request.

Cc: Günther Noack <gnoack@google.com>
Depends-on: 058518c20920 ("landlock: Align partial refer access checks with final ones")
Depends-on: d617f0d72d80 ("landlock: Optimize file path walks and prepare for audit support")
Link: https://lore.kernel.org/r/20250320190717.2287696-13-mic@digikod.net
Signed-off-by: Mickaël Salaün <mic@digikod.net>

+233 -16
+171 -7
security/landlock/audit.c
··· 7 7 8 8 #include <kunit/test.h> 9 9 #include <linux/audit.h> 10 + #include <linux/bitops.h> 10 11 #include <linux/lsm_audit.h> 11 12 #include <linux/pid.h> 13 + #include <uapi/linux/landlock.h> 12 14 13 15 #include "audit.h" 16 + #include "common.h" 14 17 #include "cred.h" 15 18 #include "domain.h" 16 19 #include "limits.h" 17 20 #include "ruleset.h" 18 21 19 - static const char *get_blocker(const enum landlock_request_type type) 22 + static const char *const fs_access_strings[] = { 23 + [BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = "fs.execute", 24 + [BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)] = "fs.write_file", 25 + [BIT_INDEX(LANDLOCK_ACCESS_FS_READ_FILE)] = "fs.read_file", 26 + [BIT_INDEX(LANDLOCK_ACCESS_FS_READ_DIR)] = "fs.read_dir", 27 + [BIT_INDEX(LANDLOCK_ACCESS_FS_REMOVE_DIR)] = "fs.remove_dir", 28 + [BIT_INDEX(LANDLOCK_ACCESS_FS_REMOVE_FILE)] = "fs.remove_file", 29 + [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_CHAR)] = "fs.make_char", 30 + [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_DIR)] = "fs.make_dir", 31 + [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_REG)] = "fs.make_reg", 32 + [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_SOCK)] = "fs.make_sock", 33 + [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_FIFO)] = "fs.make_fifo", 34 + [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_BLOCK)] = "fs.make_block", 35 + [BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_SYM)] = "fs.make_sym", 36 + [BIT_INDEX(LANDLOCK_ACCESS_FS_REFER)] = "fs.refer", 37 + [BIT_INDEX(LANDLOCK_ACCESS_FS_TRUNCATE)] = "fs.truncate", 38 + [BIT_INDEX(LANDLOCK_ACCESS_FS_IOCTL_DEV)] = "fs.ioctl_dev", 39 + }; 40 + 41 + static_assert(ARRAY_SIZE(fs_access_strings) == LANDLOCK_NUM_ACCESS_FS); 42 + 43 + static __attribute_const__ const char * 44 + get_blocker(const enum landlock_request_type type, 45 + const unsigned long access_bit) 20 46 { 21 47 switch (type) { 22 48 case LANDLOCK_REQUEST_PTRACE: 49 + WARN_ON_ONCE(access_bit != -1); 23 50 return "ptrace"; 24 51 25 52 case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY: 53 + WARN_ON_ONCE(access_bit != -1); 26 54 return "fs.change_topology"; 55 + 56 + case LANDLOCK_REQUEST_FS_ACCESS: 57 + if (WARN_ON_ONCE(access_bit >= ARRAY_SIZE(fs_access_strings))) 58 + return "unknown"; 59 + return fs_access_strings[access_bit]; 27 60 } 28 61 29 62 WARN_ON_ONCE(1); ··· 64 31 } 65 32 66 33 static void log_blockers(struct audit_buffer *const ab, 67 - const enum landlock_request_type type) 34 + const enum landlock_request_type type, 35 + const access_mask_t access) 68 36 { 69 - audit_log_format(ab, "%s", get_blocker(type)); 37 + const unsigned long access_mask = access; 38 + unsigned long access_bit; 39 + bool is_first = true; 40 + 41 + for_each_set_bit(access_bit, &access_mask, BITS_PER_TYPE(access)) { 42 + audit_log_format(ab, "%s%s", is_first ? "" : ",", 43 + get_blocker(type, access_bit)); 44 + is_first = false; 45 + } 46 + if (is_first) 47 + audit_log_format(ab, "%s", get_blocker(type, -1)); 70 48 } 71 49 72 50 static void log_domain(struct landlock_hierarchy *const hierarchy) ··· 159 115 160 116 #endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ 161 117 118 + static size_t get_denied_layer(const struct landlock_ruleset *const domain, 119 + access_mask_t *const access_request, 120 + const layer_mask_t (*const layer_masks)[], 121 + const size_t layer_masks_size) 122 + { 123 + const unsigned long access_req = *access_request; 124 + unsigned long access_bit; 125 + access_mask_t missing = 0; 126 + long youngest_layer = -1; 127 + 128 + for_each_set_bit(access_bit, &access_req, layer_masks_size) { 129 + const access_mask_t mask = (*layer_masks)[access_bit]; 130 + long layer; 131 + 132 + if (!mask) 133 + continue; 134 + 135 + /* __fls(1) == 0 */ 136 + layer = __fls(mask); 137 + if (layer > youngest_layer) { 138 + youngest_layer = layer; 139 + missing = BIT(access_bit); 140 + } else if (layer == youngest_layer) { 141 + missing |= BIT(access_bit); 142 + } 143 + } 144 + 145 + *access_request = missing; 146 + if (youngest_layer == -1) 147 + return domain->num_layers - 1; 148 + 149 + return youngest_layer; 150 + } 151 + 152 + #ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST 153 + 154 + static void test_get_denied_layer(struct kunit *const test) 155 + { 156 + const struct landlock_ruleset dom = { 157 + .num_layers = 5, 158 + }; 159 + const layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = { 160 + [BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT(0), 161 + [BIT_INDEX(LANDLOCK_ACCESS_FS_READ_FILE)] = BIT(1), 162 + [BIT_INDEX(LANDLOCK_ACCESS_FS_READ_DIR)] = BIT(1) | BIT(0), 163 + [BIT_INDEX(LANDLOCK_ACCESS_FS_REMOVE_DIR)] = BIT(2), 164 + }; 165 + access_mask_t access; 166 + 167 + access = LANDLOCK_ACCESS_FS_EXECUTE; 168 + KUNIT_EXPECT_EQ(test, 0, 169 + get_denied_layer(&dom, &access, &layer_masks, 170 + sizeof(layer_masks))); 171 + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_EXECUTE); 172 + 173 + access = LANDLOCK_ACCESS_FS_READ_FILE; 174 + KUNIT_EXPECT_EQ(test, 1, 175 + get_denied_layer(&dom, &access, &layer_masks, 176 + sizeof(layer_masks))); 177 + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_FILE); 178 + 179 + access = LANDLOCK_ACCESS_FS_READ_DIR; 180 + KUNIT_EXPECT_EQ(test, 1, 181 + get_denied_layer(&dom, &access, &layer_masks, 182 + sizeof(layer_masks))); 183 + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR); 184 + 185 + access = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR; 186 + KUNIT_EXPECT_EQ(test, 1, 187 + get_denied_layer(&dom, &access, &layer_masks, 188 + sizeof(layer_masks))); 189 + KUNIT_EXPECT_EQ(test, access, 190 + LANDLOCK_ACCESS_FS_READ_FILE | 191 + LANDLOCK_ACCESS_FS_READ_DIR); 192 + 193 + access = LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_READ_DIR; 194 + KUNIT_EXPECT_EQ(test, 1, 195 + get_denied_layer(&dom, &access, &layer_masks, 196 + sizeof(layer_masks))); 197 + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR); 198 + 199 + access = LANDLOCK_ACCESS_FS_WRITE_FILE; 200 + KUNIT_EXPECT_EQ(test, 4, 201 + get_denied_layer(&dom, &access, &layer_masks, 202 + sizeof(layer_masks))); 203 + KUNIT_EXPECT_EQ(test, access, 0); 204 + } 205 + 206 + #endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ 207 + 162 208 static bool is_valid_request(const struct landlock_request *const request) 163 209 { 164 210 if (WARN_ON_ONCE(request->layer_plus_one > LANDLOCK_MAX_NUM_LAYERS)) 165 211 return false; 166 212 167 - if (WARN_ON_ONCE(!request->layer_plus_one)) 213 + if (WARN_ON_ONCE(!(!!request->layer_plus_one ^ !!request->access))) 214 + return false; 215 + 216 + if (request->access) { 217 + if (WARN_ON_ONCE(!request->layer_masks)) 218 + return false; 219 + } else { 220 + if (WARN_ON_ONCE(request->layer_masks)) 221 + return false; 222 + } 223 + 224 + if (WARN_ON_ONCE(!!request->layer_masks ^ !!request->layer_masks_size)) 168 225 return false; 169 226 170 227 return true; ··· 283 138 struct audit_buffer *ab; 284 139 struct landlock_hierarchy *youngest_denied; 285 140 size_t youngest_layer; 141 + access_mask_t missing; 286 142 287 143 if (WARN_ON_ONCE(!subject || !subject->domain || 288 144 !subject->domain->hierarchy || !request)) ··· 292 146 if (!is_valid_request(request)) 293 147 return; 294 148 295 - youngest_layer = request->layer_plus_one - 1; 296 - youngest_denied = get_hierarchy(subject->domain, youngest_layer); 149 + missing = request->access; 150 + if (missing) { 151 + /* Gets the nearest domain that denies the request. */ 152 + if (request->layer_masks) { 153 + youngest_layer = get_denied_layer( 154 + subject->domain, &missing, request->layer_masks, 155 + request->layer_masks_size); 156 + } else { 157 + /* This will change with the next commit. */ 158 + WARN_ON_ONCE(1); 159 + youngest_layer = subject->domain->num_layers; 160 + } 161 + youngest_denied = 162 + get_hierarchy(subject->domain, youngest_layer); 163 + } else { 164 + youngest_layer = request->layer_plus_one - 1; 165 + youngest_denied = 166 + get_hierarchy(subject->domain, youngest_layer); 167 + } 297 168 298 169 /* 299 170 * Consistently keeps track of the number of denied access requests ··· 334 171 return; 335 172 336 173 audit_log_format(ab, "domain=%llx blockers=", youngest_denied->id); 337 - log_blockers(ab, request->type); 174 + log_blockers(ab, request->type, missing); 338 175 audit_log_lsm_data(ab, &request->audit); 339 176 audit_log_end(ab); 340 177 ··· 386 223 static struct kunit_case test_cases[] = { 387 224 /* clang-format off */ 388 225 KUNIT_CASE(test_get_hierarchy), 226 + KUNIT_CASE(test_get_denied_layer), 389 227 {} 390 228 /* clang-format on */ 391 229 };
+9
security/landlock/audit.h
··· 11 11 #include <linux/audit.h> 12 12 #include <linux/lsm_audit.h> 13 13 14 + #include "access.h" 14 15 #include "cred.h" 15 16 16 17 enum landlock_request_type { 17 18 LANDLOCK_REQUEST_PTRACE = 1, 18 19 LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY, 20 + LANDLOCK_REQUEST_FS_ACCESS, 19 21 }; 20 22 21 23 /* ··· 35 33 * extra one is useful to detect uninitialized field. 36 34 */ 37 35 size_t layer_plus_one; 36 + 37 + /* Required field for configurable access control. */ 38 + access_mask_t access; 39 + 40 + /* Required fields for requests with layer masks. */ 41 + const layer_mask_t (*layer_masks)[]; 42 + size_t layer_masks_size; 38 43 }; 39 44 40 45 #ifdef CONFIG_AUDIT
+53 -9
security/landlock/fs.c
··· 726 726 * those identified by @access_request_parent1). This matrix can 727 727 * initially refer to domain layer masks and, when the accesses for the 728 728 * destination and source are the same, to requested layer masks. 729 + * @log_request_parent1: Audit request to fill if the related access is denied. 729 730 * @dentry_child1: Dentry to the initial child of the parent1 path. This 730 731 * pointer must be NULL for non-refer actions (i.e. not link nor rename). 731 732 * @access_request_parent2: Similar to @access_request_parent1 but for a ··· 735 734 * the source. Must be set to 0 when using a simple path request. 736 735 * @layer_masks_parent2: Similar to @layer_masks_parent1 but for a refer 737 736 * action. This must be NULL otherwise. 737 + * @log_request_parent2: Audit request to fill if the related access is denied. 738 738 * @dentry_child2: Dentry to the initial child of the parent2 path. This 739 739 * pointer is only set for RENAME_EXCHANGE actions and must be NULL 740 740 * otherwise. ··· 755 753 const struct path *const path, 756 754 const access_mask_t access_request_parent1, 757 755 layer_mask_t (*const layer_masks_parent1)[LANDLOCK_NUM_ACCESS_FS], 758 - const struct dentry *const dentry_child1, 756 + struct landlock_request *const log_request_parent1, 757 + struct dentry *const dentry_child1, 759 758 const access_mask_t access_request_parent2, 760 759 layer_mask_t (*const layer_masks_parent2)[LANDLOCK_NUM_ACCESS_FS], 761 - const struct dentry *const dentry_child2) 760 + struct landlock_request *const log_request_parent2, 761 + struct dentry *const dentry_child2) 762 762 { 763 763 bool allowed_parent1 = false, allowed_parent2 = false, is_dom_check, 764 764 child1_is_directory = true, child2_is_directory = true; ··· 925 921 } 926 922 path_put(&walker_path); 927 923 924 + if (!allowed_parent1) { 925 + log_request_parent1->type = LANDLOCK_REQUEST_FS_ACCESS; 926 + log_request_parent1->audit.type = LSM_AUDIT_DATA_PATH; 927 + log_request_parent1->audit.u.path = *path; 928 + log_request_parent1->access = access_masked_parent1; 929 + log_request_parent1->layer_masks = layer_masks_parent1; 930 + log_request_parent1->layer_masks_size = 931 + ARRAY_SIZE(*layer_masks_parent1); 932 + } 933 + 934 + if (!allowed_parent2) { 935 + log_request_parent2->type = LANDLOCK_REQUEST_FS_ACCESS; 936 + log_request_parent2->audit.type = LSM_AUDIT_DATA_PATH; 937 + log_request_parent2->audit.u.path = *path; 938 + log_request_parent2->access = access_masked_parent2; 939 + log_request_parent2->layer_masks = layer_masks_parent2; 940 + log_request_parent2->layer_masks_size = 941 + ARRAY_SIZE(*layer_masks_parent2); 942 + } 928 943 return allowed_parent1 && allowed_parent2; 929 944 } 930 945 ··· 956 933 const struct landlock_cred_security *const subject = 957 934 landlock_get_applicable_subject(current_cred(), masks, NULL); 958 935 layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {}; 936 + struct landlock_request request = {}; 959 937 960 938 if (!subject) 961 939 return 0; ··· 965 941 access_request, &layer_masks, 966 942 LANDLOCK_KEY_INODE); 967 943 if (is_access_to_paths_allowed(subject->domain, path, access_request, 968 - &layer_masks, NULL, 0, NULL, NULL)) 944 + &layer_masks, &request, NULL, 0, NULL, 945 + NULL, NULL)) 969 946 return 0; 970 947 948 + landlock_log_denial(subject, &request); 971 949 return -EACCES; 972 950 } 973 951 ··· 1138 1112 struct dentry *old_parent; 1139 1113 layer_mask_t layer_masks_parent1[LANDLOCK_NUM_ACCESS_FS] = {}, 1140 1114 layer_masks_parent2[LANDLOCK_NUM_ACCESS_FS] = {}; 1115 + struct landlock_request request1 = {}, request2 = {}; 1141 1116 1142 1117 if (!subject) 1143 1118 return 0; ··· 1170 1143 subject->domain, 1171 1144 access_request_parent1 | access_request_parent2, 1172 1145 &layer_masks_parent1, LANDLOCK_KEY_INODE); 1173 - if (is_access_to_paths_allowed( 1174 - subject->domain, new_dir, access_request_parent1, 1175 - &layer_masks_parent1, NULL, 0, NULL, NULL)) 1146 + if (is_access_to_paths_allowed(subject->domain, new_dir, 1147 + access_request_parent1, 1148 + &layer_masks_parent1, &request1, 1149 + NULL, 0, NULL, NULL, NULL)) 1176 1150 return 0; 1151 + 1152 + landlock_log_denial(subject, &request1); 1177 1153 return -EACCES; 1178 1154 } 1179 1155 ··· 1215 1185 */ 1216 1186 if (is_access_to_paths_allowed( 1217 1187 subject->domain, &mnt_dir, access_request_parent1, 1218 - &layer_masks_parent1, old_dentry, access_request_parent2, 1219 - &layer_masks_parent2, exchange ? new_dentry : NULL)) 1188 + &layer_masks_parent1, &request1, old_dentry, 1189 + access_request_parent2, &layer_masks_parent2, &request2, 1190 + exchange ? new_dentry : NULL)) 1220 1191 return 0; 1192 + 1193 + if (request1.access) { 1194 + request1.audit.u.path.dentry = old_parent; 1195 + landlock_log_denial(subject, &request1); 1196 + } 1197 + if (request2.access) { 1198 + request2.audit.u.path.dentry = new_dir->dentry; 1199 + landlock_log_denial(subject, &request2); 1200 + } 1221 1201 1222 1202 /* 1223 1203 * This prioritizes EACCES over EXDEV for all actions, including ··· 1618 1578 optional_access; 1619 1579 const struct landlock_cred_security *const subject = 1620 1580 landlock_get_applicable_subject(file->f_cred, any_fs, NULL); 1581 + struct landlock_request request = {}; 1621 1582 1622 1583 if (!subject) 1623 1584 return 0; ··· 1645 1604 landlock_init_layer_masks(subject->domain, 1646 1605 full_access_request, &layer_masks, 1647 1606 LANDLOCK_KEY_INODE), 1648 - &layer_masks, NULL, 0, NULL, NULL)) { 1607 + &layer_masks, &request, NULL, 0, NULL, NULL, NULL)) { 1649 1608 allowed_access = full_access_request; 1650 1609 } else { 1651 1610 unsigned long access_bit; ··· 1675 1634 if ((open_access_request & allowed_access) == open_access_request) 1676 1635 return 0; 1677 1636 1637 + /* Sets access to reflect the actual request. */ 1638 + request.access = open_access_request; 1639 + landlock_log_denial(subject, &request); 1678 1640 return -EACCES; 1679 1641 } 1680 1642