diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-06-19 12:20:25 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-06-19 12:20:25 -0700 |
| commit | 5e2e14749c3d969e263a879db104db6e9f0eb484 (patch) | |
| tree | 065f3f60b48f249fca91931fa261b9cf93172c50 /security | |
| parent | e2c0595b56e9526e67ddd228fc35fa9ff20724ec (diff) | |
| parent | 1c236e7fe740a009ad8dd40a5ee0602ec402fffe (diff) | |
| download | ath-5e2e14749c3d969e263a879db104db6e9f0eb484.tar.gz | |
Merge tag 'landlock-7.2-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/mic/linux
Pull landlock updates from Mickaël Salaün:
"This adds new Landlock access rights to control UDP bind and
connect/send operations, and a new "quiet" feature to mute specific
specific audit logs (and other future observability events).
A few commits also fix Landlock issues"
* tag 'landlock-7.2-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/mic/linux: (24 commits)
selftests/landlock: Add tests for invalid use of quiet flag
selftests/landlock: Add tests for quiet flag with scope
selftests/landlock: Add tests for quiet flag with net rules
selftests/landlock: Add tests for quiet flag with fs rules
selftests/landlock: Replace hard-coded 16 with a constant
samples/landlock: Add quiet flag support to sandboxer
landlock: Suppress logging when quiet flag is present
landlock: Add API support and docs for the quiet flags
landlock: Add a place for flags to layer rules
landlock: Add documentation for UDP support
samples/landlock: Add sandboxer UDP access control
selftests/landlock: Add tests for UDP send
selftests/landlock: Add tests for UDP bind/connect
landlock: Add UDP send+connect access control
landlock: Add UDP bind() access control
landlock: Fix unmarked concurrent access to socket family
selftests/landlock: Explicitly disable audit in teardowns
selftests/landlock: Test SCOPE_SIGNAL on the SIGIO/fowner pgid path
landlock: Fix LANDLOCK_SCOPE_SIGNAL bypass on the SIGIO path
landlock: Demonstrate best-effort allowed_access filtering
...
Diffstat (limited to 'security')
| -rw-r--r-- | security/landlock/access.h | 44 | ||||
| -rw-r--r-- | security/landlock/audit.c | 292 | ||||
| -rw-r--r-- | security/landlock/audit.h | 3 | ||||
| -rw-r--r-- | security/landlock/domain.c | 66 | ||||
| -rw-r--r-- | security/landlock/domain.h | 16 | ||||
| -rw-r--r-- | security/landlock/fs.c | 171 | ||||
| -rw-r--r-- | security/landlock/fs.h | 29 | ||||
| -rw-r--r-- | security/landlock/limits.h | 5 | ||||
| -rw-r--r-- | security/landlock/net.c | 185 | ||||
| -rw-r--r-- | security/landlock/net.h | 5 | ||||
| -rw-r--r-- | security/landlock/ruleset.c | 49 | ||||
| -rw-r--r-- | security/landlock/ruleset.h | 29 | ||||
| -rw-r--r-- | security/landlock/syscalls.c | 73 | ||||
| -rw-r--r-- | security/landlock/task.c | 11 |
14 files changed, 788 insertions, 190 deletions
diff --git a/security/landlock/access.h b/security/landlock/access.h index c19d5bc139443..d926078bf0a5f 100644 --- a/security/landlock/access.h +++ b/security/landlock/access.h @@ -62,18 +62,41 @@ static_assert(sizeof(typeof_member(union access_masks_all, masks)) == sizeof(typeof_member(union access_masks_all, all))); /** - * struct layer_access_masks - A boolean matrix of layers and access rights + * struct layer_mask - The access rights and rule flags for a layer. * - * This has a bit for each combination of layer numbers and access rights. - * During access checks, it is used to represent the access rights for each - * layer which still need to be fulfilled. When all bits are 0, the access - * request is considered to be fulfilled. + * This has a bit for each access rights and rule flags. During access checks, + * it is used to represent the access rights for each layer which still need to + * be fulfilled. When all bits are 0, the access request is considered to be + * fulfilled. */ -struct layer_access_masks { +struct layer_mask { /** - * @access: The unfulfilled access rights for each layer. + * @access: The unfulfilled access rights for this layer. */ - access_mask_t access[LANDLOCK_MAX_NUM_LAYERS]; + access_mask_t access : LANDLOCK_NUM_ACCESS_MAX; +#ifdef CONFIG_AUDIT + /** + * @quiet: Whether we have encountered a rule with the quiet flag for + * this layer. Used to control logging. + */ + access_mask_t quiet : 1; +#endif /* CONFIG_AUDIT */ +} __packed __aligned(sizeof(access_mask_t)); + +/* + * Make sure that we don't increase the size of struct layer_mask when storing + * rule flags. + */ +static_assert(sizeof(struct layer_mask) == sizeof(access_mask_t)); + +/** + * struct layer_masks - An array of struct layer_mask, one per layer. + */ +struct layer_masks { + /** + * @layers: The unfulfilled access rights for each layer. + */ + struct layer_mask layers[LANDLOCK_MAX_NUM_LAYERS]; }; /* @@ -120,4 +143,9 @@ static inline bool access_mask_subset(access_mask_t subset, return (subset | superset) == superset; } +/* A bitmask that is large enough to hold set of optional accesses. */ +typedef u8 optional_access_t; +static_assert(BITS_PER_TYPE(optional_access_t) >= + HWEIGHT(_LANDLOCK_ACCESS_FS_OPTIONAL)); + #endif /* _SECURITY_LANDLOCK_ACCESS_H */ diff --git a/security/landlock/audit.c b/security/landlock/audit.c index 8d0edf94037d7..50536c5685262 100644 --- a/security/landlock/audit.c +++ b/security/landlock/audit.c @@ -45,6 +45,9 @@ static_assert(ARRAY_SIZE(fs_access_strings) == LANDLOCK_NUM_ACCESS_FS); static const char *const net_access_strings[] = { [BIT_INDEX(LANDLOCK_ACCESS_NET_BIND_TCP)] = "net.bind_tcp", [BIT_INDEX(LANDLOCK_ACCESS_NET_CONNECT_TCP)] = "net.connect_tcp", + [BIT_INDEX(LANDLOCK_ACCESS_NET_BIND_UDP)] = "net.bind_udp", + [BIT_INDEX(LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP)] = + "net.connect_send_udp", }; static_assert(ARRAY_SIZE(net_access_strings) == LANDLOCK_NUM_ACCESS_NET); @@ -184,11 +187,11 @@ static void test_get_hierarchy(struct kunit *const test) /* Get the youngest layer that denied the access_request. */ static size_t get_denied_layer(const struct landlock_ruleset *const domain, access_mask_t *const access_request, - const struct layer_access_masks *masks) + const struct layer_masks *masks) { - for (ssize_t i = ARRAY_SIZE(masks->access) - 1; i >= 0; i--) { - if (masks->access[i] & *access_request) { - *access_request &= masks->access[i]; + for (ssize_t i = ARRAY_SIZE(masks->layers) - 1; i >= 0; i--) { + if (masks->layers[i].access & *access_request) { + *access_request &= masks->layers[i].access; return i; } } @@ -205,12 +208,12 @@ static void test_get_denied_layer(struct kunit *const test) const struct landlock_ruleset dom = { .num_layers = 5, }; - const struct layer_access_masks masks = { - .access[0] = LANDLOCK_ACCESS_FS_EXECUTE | - LANDLOCK_ACCESS_FS_READ_DIR, - .access[1] = LANDLOCK_ACCESS_FS_READ_FILE | - LANDLOCK_ACCESS_FS_READ_DIR, - .access[2] = LANDLOCK_ACCESS_FS_REMOVE_DIR, + const struct layer_masks masks = { + .layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE | + LANDLOCK_ACCESS_FS_READ_DIR, + .layers[1].access = LANDLOCK_ACCESS_FS_READ_FILE | + LANDLOCK_ACCESS_FS_READ_DIR, + .layers[2].access = LANDLOCK_ACCESS_FS_REMOVE_DIR, }; access_mask_t access; @@ -246,7 +249,9 @@ static void test_get_denied_layer(struct kunit *const test) static size_t get_layer_from_deny_masks(access_mask_t *const access_request, const access_mask_t all_existing_optional_access, - const deny_masks_t deny_masks) + const deny_masks_t deny_masks, + optional_access_t quiet_optional_accesses, + bool *quiet) { const unsigned long access_opt = all_existing_optional_access; const unsigned long access_req = *access_request; @@ -254,6 +259,7 @@ get_layer_from_deny_masks(access_mask_t *const access_request, size_t youngest_layer = 0; size_t access_index = 0; unsigned long access_bit; + bool should_quiet = false; /* This will require change with new object types. */ WARN_ON_ONCE(access_opt != _LANDLOCK_ACCESS_FS_OPTIONAL); @@ -262,20 +268,34 @@ get_layer_from_deny_masks(access_mask_t *const access_request, BITS_PER_TYPE(access_mask_t)) { if (access_req & BIT(access_bit)) { const size_t layer = - (deny_masks >> (access_index * 4)) & + (deny_masks >> + (access_index * + HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1))) & (LANDLOCK_MAX_NUM_LAYERS - 1); + const bool layer_has_quiet = + !!(quiet_optional_accesses & BIT(access_index)); if (layer > youngest_layer) { youngest_layer = layer; missing = BIT(access_bit); + should_quiet = layer_has_quiet; } else if (layer == youngest_layer) { missing |= BIT(access_bit); + /* + * Whether the layer has rules with quiet flag + * covering the file accessed does not depend on + * the access, and so the following + * WARN_ON_ONCE() should not fail. + */ + WARN_ON_ONCE(should_quiet && !layer_has_quiet); + should_quiet = layer_has_quiet; } } access_index++; } *access_request = missing; + *quiet = should_quiet; return youngest_layer; } @@ -285,42 +305,188 @@ static void test_get_layer_from_deny_masks(struct kunit *const test) { deny_masks_t deny_mask; access_mask_t access; + optional_access_t quiet_optional_accesses; + bool quiet; /* truncate:0 ioctl_dev:2 */ deny_mask = 0x20; + quiet_optional_accesses = 0; access = LANDLOCK_ACCESS_FS_TRUNCATE; KUNIT_EXPECT_EQ(test, 0, - get_layer_from_deny_masks(&access, - _LANDLOCK_ACCESS_FS_OPTIONAL, - deny_mask)); + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); + KUNIT_EXPECT_EQ(test, quiet, false); + + access = LANDLOCK_ACCESS_FS_IOCTL_DEV; + KUNIT_EXPECT_EQ(test, 2, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV); + KUNIT_EXPECT_EQ(test, quiet, false); + + access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV; + KUNIT_EXPECT_EQ(test, 2, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV); + KUNIT_EXPECT_EQ(test, quiet, false); + + /* layer denying truncate: quiet, ioctl: not quiet */ + quiet_optional_accesses = 0b01; + + access = LANDLOCK_ACCESS_FS_TRUNCATE; + KUNIT_EXPECT_EQ(test, 0, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); + KUNIT_EXPECT_EQ(test, quiet, true); + + access = LANDLOCK_ACCESS_FS_IOCTL_DEV; + KUNIT_EXPECT_EQ(test, 2, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV); + KUNIT_EXPECT_EQ(test, quiet, false); + + access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV; + KUNIT_EXPECT_EQ(test, 2, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV); + KUNIT_EXPECT_EQ(test, quiet, false); + + /* Reverse order - truncate:2 ioctl_dev:0 */ + deny_mask = 0x02; + quiet_optional_accesses = 0; + + access = LANDLOCK_ACCESS_FS_TRUNCATE; + KUNIT_EXPECT_EQ(test, 2, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); + KUNIT_EXPECT_EQ(test, quiet, false); + + access = LANDLOCK_ACCESS_FS_IOCTL_DEV; + KUNIT_EXPECT_EQ(test, 0, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV); + KUNIT_EXPECT_EQ(test, quiet, false); access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV; KUNIT_EXPECT_EQ(test, 2, - get_layer_from_deny_masks(&access, - _LANDLOCK_ACCESS_FS_OPTIONAL, - deny_mask)); + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); + KUNIT_EXPECT_EQ(test, quiet, false); + + /* layer denying truncate: quiet, ioctl: not quiet */ + quiet_optional_accesses = 0b01; + + access = LANDLOCK_ACCESS_FS_TRUNCATE; + KUNIT_EXPECT_EQ(test, 2, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); + KUNIT_EXPECT_EQ(test, quiet, true); + + access = LANDLOCK_ACCESS_FS_IOCTL_DEV; + KUNIT_EXPECT_EQ(test, 0, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV); + KUNIT_EXPECT_EQ(test, quiet, false); + + access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV; + KUNIT_EXPECT_EQ(test, 2, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); + KUNIT_EXPECT_EQ(test, quiet, true); + + /* layer denying truncate: not quiet, ioctl: quiet */ + quiet_optional_accesses = 0b10; + + access = LANDLOCK_ACCESS_FS_TRUNCATE; + KUNIT_EXPECT_EQ(test, 2, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); + KUNIT_EXPECT_EQ(test, quiet, false); + + access = LANDLOCK_ACCESS_FS_IOCTL_DEV; + KUNIT_EXPECT_EQ(test, 0, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV); + KUNIT_EXPECT_EQ(test, quiet, true); + + access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV; + KUNIT_EXPECT_EQ(test, 2, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); + KUNIT_EXPECT_EQ(test, quiet, false); /* truncate:15 ioctl_dev:15 */ deny_mask = 0xff; + quiet_optional_accesses = 0; access = LANDLOCK_ACCESS_FS_TRUNCATE; KUNIT_EXPECT_EQ(test, 15, - get_layer_from_deny_masks(&access, - _LANDLOCK_ACCESS_FS_OPTIONAL, - deny_mask)); + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); + KUNIT_EXPECT_EQ(test, quiet, false); access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV; KUNIT_EXPECT_EQ(test, 15, - get_layer_from_deny_masks(&access, - _LANDLOCK_ACCESS_FS_OPTIONAL, - deny_mask)); + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV); + KUNIT_EXPECT_EQ(test, quiet, false); + + /* Both quiet (same layer so quietness must be the same) */ + quiet_optional_accesses = 0b11; + + access = LANDLOCK_ACCESS_FS_TRUNCATE; + KUNIT_EXPECT_EQ(test, 15, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE); + KUNIT_EXPECT_EQ(test, quiet, true); + + access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV; + KUNIT_EXPECT_EQ(test, 15, + get_layer_from_deny_masks( + &access, _LANDLOCK_ACCESS_FS_OPTIONAL, + deny_mask, quiet_optional_accesses, &quiet)); + KUNIT_EXPECT_EQ(test, access, + LANDLOCK_ACCESS_FS_TRUNCATE | + LANDLOCK_ACCESS_FS_IOCTL_DEV); + KUNIT_EXPECT_EQ(test, quiet, true); } #endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ @@ -346,11 +512,34 @@ static bool is_valid_request(const struct landlock_request *const request) if (request->deny_masks) { if (WARN_ON_ONCE(!request->all_existing_optional_access)) return false; + static_assert(sizeof(request->all_existing_optional_access) == + sizeof(u32)); + if (WARN_ON_ONCE( + request->quiet_optional_accesses >= + BIT(hweight32( + request->all_existing_optional_access)))) + return false; } return true; } +static access_mask_t +pick_access_mask_for_request_type(const enum landlock_request_type type, + const struct access_masks access_masks) +{ + switch (type) { + case LANDLOCK_REQUEST_FS_ACCESS: + return access_masks.fs; + case LANDLOCK_REQUEST_NET_ACCESS: + return access_masks.net; + default: + WARN_ONCE(1, "Invalid request type %d passed to %s", type, + __func__); + return 0; + } +} + /** * landlock_log_denial - Create audit records related to a denial * @@ -364,6 +553,7 @@ void landlock_log_denial(const struct landlock_cred_security *const subject, struct landlock_hierarchy *youngest_denied; size_t youngest_layer; access_mask_t missing; + bool object_quiet_flag = false, quiet_applicable_to_access = false; if (WARN_ON_ONCE(!subject || !subject->domain || !subject->domain->hierarchy || !request)) @@ -379,10 +569,15 @@ void landlock_log_denial(const struct landlock_cred_security *const subject, youngest_layer = get_denied_layer(subject->domain, &missing, request->layer_masks); + object_quiet_flag = + request->layer_masks->layers[youngest_layer] + .quiet; } else { youngest_layer = get_layer_from_deny_masks( &missing, _LANDLOCK_ACCESS_FS_OPTIONAL, - request->deny_masks); + request->deny_masks, + request->quiet_optional_accesses, + &object_quiet_flag); } youngest_denied = get_hierarchy(subject->domain, youngest_layer); @@ -417,6 +612,53 @@ void landlock_log_denial(const struct landlock_cred_security *const subject, return; } + /* + * Checks if the object is marked quiet by the layer that denied the + * request. If it's a different layer that marked it as quiet, but that + * layer is not the one that denied the request, we should still audit + * log the denial. + */ + if (object_quiet_flag) { + /* + * We now check if the denied requests are all covered by the + * layer's quiet access bits. + */ + const access_mask_t quiet_mask = + pick_access_mask_for_request_type( + request->type, youngest_denied->quiet_masks); + + quiet_applicable_to_access = (quiet_mask & missing) == missing; + } else { + /* + * Either the object is not quiet, or this is a scope request. + * We check request->type to distinguish between the two cases. + */ + const access_mask_t quiet_mask = + youngest_denied->quiet_masks.scope; + + switch (request->type) { + case LANDLOCK_REQUEST_SCOPE_SIGNAL: + quiet_applicable_to_access = + !!(quiet_mask & LANDLOCK_SCOPE_SIGNAL); + break; + case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET: + quiet_applicable_to_access = + !!(quiet_mask & + LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET); + break; + /* + * Leave LANDLOCK_REQUEST_PTRACE and + * LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY unhandled for now - they + * are never quiet. + */ + default: + break; + } + } + + if (quiet_applicable_to_access) + return; + /* Uses consistent allocation flags wrt common_lsm_audit(). */ ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN, AUDIT_LANDLOCK_ACCESS); diff --git a/security/landlock/audit.h b/security/landlock/audit.h index 56778331b58c7..620f8a24291d5 100644 --- a/security/landlock/audit.h +++ b/security/landlock/audit.h @@ -43,11 +43,12 @@ struct landlock_request { access_mask_t access; /* Required fields for requests with layer masks. */ - const struct layer_access_masks *layer_masks; + const struct layer_masks *layer_masks; /* Required fields for requests with deny masks. */ const access_mask_t all_existing_optional_access; deny_masks_t deny_masks; + optional_access_t quiet_optional_accesses; }; #ifdef CONFIG_AUDIT diff --git a/security/landlock/domain.c b/security/landlock/domain.c index 06b6bd845060f..9a8355fccd26e 100644 --- a/security/landlock/domain.c +++ b/security/landlock/domain.c @@ -90,11 +90,12 @@ static struct landlock_details *get_current_details(void) return ERR_CAST(buffer); /* - * Create the new details according to the path's length. Do not - * allocate with GFP_KERNEL_ACCOUNT because it is independent from the - * caller. + * Create the new details according to the path's length. Account to + * the calling task's memcg, like the other Landlock per-domain + * allocations, even if it may not control the related size. */ - details = kzalloc_flex(*details, exe_path, path_size); + details = + kzalloc_flex(*details, exe_path, path_size, GFP_KERNEL_ACCOUNT); if (!details) return ERR_PTR(-ENOMEM); @@ -156,6 +157,44 @@ get_layer_deny_mask(const access_mask_t all_existing_optional_access, << ((access_weight - 1) * HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1)); } +/** + * landlock_get_quiet_optional_accesses - Get optional accesses which are + * covered by quiet rule flags. + * + * @all_existing_optional_access: Bitmask of valid optional accesses. + * @deny_masks: Domain layer levels that denied each optional access (the + * deny_masks field on struct landlock_file_security). + * @masks: The struct layer_masks collected during the path walk. + * + * Return: a bitmask of which optional accesses are denied by layers for which + * the quiet flag was collected during the path walk. + */ +optional_access_t landlock_get_quiet_optional_accesses( + const access_mask_t all_existing_optional_access, + const deny_masks_t deny_masks, const struct layer_masks *const masks) +{ + const unsigned long access_opt = all_existing_optional_access; + size_t access_index = 0; + unsigned long access_bit; + optional_access_t quiet_optional_accesses = 0; + + /* This will require change with new object types. */ + WARN_ON_ONCE(access_opt != _LANDLOCK_ACCESS_FS_OPTIONAL); + + for_each_set_bit(access_bit, &access_opt, + BITS_PER_TYPE(access_mask_t)) { + const u8 layer = + (deny_masks >> (access_index * + HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1))) & + (LANDLOCK_MAX_NUM_LAYERS - 1); + + if (masks->layers[layer].quiet) + quiet_optional_accesses |= BIT(access_index); + access_index++; + } + return quiet_optional_accesses; +} + #ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST static void test_get_layer_deny_mask(struct kunit *const test) @@ -183,7 +222,7 @@ static void test_get_layer_deny_mask(struct kunit *const test) deny_masks_t landlock_get_deny_masks(const access_mask_t all_existing_optional_access, const access_mask_t optional_access, - const struct layer_access_masks *const masks) + const struct layer_masks *const masks) { const unsigned long access_opt = optional_access; unsigned long access_bit; @@ -200,8 +239,9 @@ landlock_get_deny_masks(const access_mask_t all_existing_optional_access, if (WARN_ON_ONCE(!access_opt)) return 0; - for (ssize_t i = ARRAY_SIZE(masks->access) - 1; i >= 0; i--) { - const access_mask_t denied = masks->access[i] & optional_access; + for (ssize_t i = ARRAY_SIZE(masks->layers) - 1; i >= 0; i--) { + const access_mask_t denied = masks->layers[i].access & + optional_access; const unsigned long newly_denied = denied & ~all_denied; if (!newly_denied) @@ -221,12 +261,12 @@ landlock_get_deny_masks(const access_mask_t all_existing_optional_access, static void test_landlock_get_deny_masks(struct kunit *const test) { - const struct layer_access_masks layers1 = { - .access[0] = LANDLOCK_ACCESS_FS_EXECUTE | - LANDLOCK_ACCESS_FS_IOCTL_DEV, - .access[1] = LANDLOCK_ACCESS_FS_TRUNCATE, - .access[2] = LANDLOCK_ACCESS_FS_IOCTL_DEV, - .access[9] = LANDLOCK_ACCESS_FS_EXECUTE, + const struct layer_masks layers1 = { + .layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE | + LANDLOCK_ACCESS_FS_IOCTL_DEV, + .layers[1].access = LANDLOCK_ACCESS_FS_TRUNCATE, + .layers[2].access = LANDLOCK_ACCESS_FS_IOCTL_DEV, + .layers[9].access = LANDLOCK_ACCESS_FS_EXECUTE, }; KUNIT_EXPECT_EQ(test, 0x1, diff --git a/security/landlock/domain.h b/security/landlock/domain.h index a9d57db0120dc..2a1660e3dea7e 100644 --- a/security/landlock/domain.h +++ b/security/landlock/domain.h @@ -33,10 +33,7 @@ enum landlock_log_status { * Rarely accessed, mainly when logging the first domain's denial. * * The contained pointers are initialized at the domain creation time and never - * changed again. Contrary to most other Landlock object types, this one is - * not allocated with GFP_KERNEL_ACCOUNT because its size may not be under the - * caller's control (e.g. unknown exe_path) and the data is not explicitly - * requested nor used by tasks. + * changed again. */ struct landlock_details { /** @@ -114,6 +111,11 @@ struct landlock_hierarchy { * %LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON. Set to false by default. */ log_new_exec : 1; + /** + * @quiet_masks: Bitmasks of access that should be quieted (i.e. not + * logged) if the related object is marked as quiet. + */ + struct access_masks quiet_masks; #endif /* CONFIG_AUDIT */ }; @@ -122,7 +124,11 @@ struct landlock_hierarchy { deny_masks_t landlock_get_deny_masks(const access_mask_t all_existing_optional_access, const access_mask_t optional_access, - const struct layer_access_masks *const masks); + const struct layer_masks *const masks); + +optional_access_t landlock_get_quiet_optional_accesses( + const access_mask_t all_existing_optional_access, + const deny_masks_t deny_masks, const struct layer_masks *const masks); int landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy); diff --git a/security/landlock/fs.c b/security/landlock/fs.c index 32d560f12dbd3..f7e5e4ef9eac3 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -325,7 +325,7 @@ retry: */ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset, const struct path *const path, - access_mask_t access_rights) + access_mask_t access_rights, const u32 flags) { int err; struct landlock_id id = { @@ -346,7 +346,7 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset, if (IS_ERR(id.key.object)) return PTR_ERR(id.key.object); mutex_lock(&ruleset->lock); - err = landlock_insert_rule(ruleset, id, access_rights); + err = landlock_insert_rule(ruleset, id, access_rights, flags); mutex_unlock(&ruleset->lock); /* * No need to check for an error because landlock_insert_rule() @@ -406,15 +406,15 @@ static const struct access_masks any_fs = { * src_parent would result in having the same or fewer access rights if it were * moved under new_parent. */ -static bool may_refer(const struct layer_access_masks *const src_parent, - const struct layer_access_masks *const src_child, - const struct layer_access_masks *const new_parent, +static bool may_refer(const struct layer_masks *const src_parent, + const struct layer_masks *const src_child, + const struct layer_masks *const new_parent, const bool child_is_dir) { - for (size_t i = 0; i < ARRAY_SIZE(new_parent->access); i++) { - access_mask_t child_access = src_parent->access[i] & - src_child->access[i]; - access_mask_t parent_access = new_parent->access[i]; + for (size_t i = 0; i < ARRAY_SIZE(new_parent->layers); i++) { + access_mask_t child_access = src_parent->layers[i].access & + src_child->layers[i].access; + access_mask_t parent_access = new_parent->layers[i].access; if (!child_is_dir) { child_access &= ACCESS_FILE; @@ -436,11 +436,11 @@ static bool may_refer(const struct layer_access_masks *const src_parent, * that child2 may be used from parent2 to parent1 without increasing its access * rights), false otherwise. */ -static bool no_more_access(const struct layer_access_masks *const parent1, - const struct layer_access_masks *const child1, +static bool no_more_access(const struct layer_masks *const parent1, + const struct layer_masks *const child1, const bool child1_is_dir, - const struct layer_access_masks *const parent2, - const struct layer_access_masks *const child2, + const struct layer_masks *const parent2, + const struct layer_masks *const child2, const bool child2_is_dir) { if (!may_refer(parent1, child1, parent2, child1_is_dir)) @@ -459,25 +459,25 @@ static bool no_more_access(const struct layer_access_masks *const parent1, static void test_no_more_access(struct kunit *const test) { - const struct layer_access_masks rx0 = { - .access[0] = LANDLOCK_ACCESS_FS_EXECUTE | - LANDLOCK_ACCESS_FS_READ_FILE, + const struct layer_masks rx0 = { + .layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE | + LANDLOCK_ACCESS_FS_READ_FILE, }; - const struct layer_access_masks mx0 = { - .access[0] = LANDLOCK_ACCESS_FS_EXECUTE | - LANDLOCK_ACCESS_FS_MAKE_REG, + const struct layer_masks mx0 = { + .layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE | + LANDLOCK_ACCESS_FS_MAKE_REG, }; - const struct layer_access_masks x0 = { - .access[0] = LANDLOCK_ACCESS_FS_EXECUTE, + const struct layer_masks x0 = { + .layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE, }; - const struct layer_access_masks x1 = { - .access[1] = LANDLOCK_ACCESS_FS_EXECUTE, + const struct layer_masks x1 = { + .layers[1].access = LANDLOCK_ACCESS_FS_EXECUTE, }; - const struct layer_access_masks x01 = { - .access[0] = LANDLOCK_ACCESS_FS_EXECUTE, - .access[1] = LANDLOCK_ACCESS_FS_EXECUTE, + const struct layer_masks x01 = { + .layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE, + .layers[1].access = LANDLOCK_ACCESS_FS_EXECUTE, }; - const struct layer_access_masks allows_all = {}; + const struct layer_masks allows_all = {}; /* Checks without restriction. */ NMA_TRUE(&x0, &allows_all, false, &allows_all, NULL, false); @@ -565,9 +565,13 @@ static void test_no_more_access(struct kunit *const test) #undef NMA_TRUE #undef NMA_FALSE -static bool is_layer_masks_allowed(const struct layer_access_masks *masks) +static bool is_layer_masks_allowed(const struct layer_masks *masks) { - return mem_is_zero(&masks->access, sizeof(masks->access)); + for (size_t i = 0; i < ARRAY_SIZE(masks->layers); i++) { + if (masks->layers[i].access) + return false; + } + return true; } /* @@ -576,16 +580,16 @@ static bool is_layer_masks_allowed(const struct layer_access_masks *masks) * Returns true if the request is allowed, false otherwise. */ static bool scope_to_request(const access_mask_t access_request, - struct layer_access_masks *masks) + struct layer_masks *masks) { bool saw_unfulfilled_access = false; if (WARN_ON_ONCE(!masks)) return true; - for (size_t i = 0; i < ARRAY_SIZE(masks->access); i++) { - masks->access[i] &= access_request; - if (masks->access[i]) + for (size_t i = 0; i < ARRAY_SIZE(masks->layers); i++) { + masks->layers[i].access &= access_request; + if (masks->layers[i].access) saw_unfulfilled_access = true; } return !saw_unfulfilled_access; @@ -596,41 +600,46 @@ static bool scope_to_request(const access_mask_t access_request, static void test_scope_to_request_with_exec_none(struct kunit *const test) { /* Allows everything. */ - struct layer_access_masks masks = {}; + struct layer_masks masks = {}; /* Checks and scopes with execute. */ KUNIT_EXPECT_TRUE(test, scope_to_request(LANDLOCK_ACCESS_FS_EXECUTE, &masks)); - KUNIT_EXPECT_EQ(test, 0, masks.access[0]); + KUNIT_EXPECT_EQ(test, 0, (access_mask_t)masks.layers[0].access); } static void test_scope_to_request_with_exec_some(struct kunit *const test) { /* Denies execute and write. */ - struct layer_access_masks masks = { - .access[0] = LANDLOCK_ACCESS_FS_EXECUTE, - .access[1] = LANDLOCK_ACCESS_FS_WRITE_FILE, + struct layer_masks masks = { + .layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE, + .layers[1].access = LANDLOCK_ACCESS_FS_WRITE_FILE, }; /* Checks and scopes with execute. */ KUNIT_EXPECT_FALSE(test, scope_to_request(LANDLOCK_ACCESS_FS_EXECUTE, &masks)); - KUNIT_EXPECT_EQ(test, LANDLOCK_ACCESS_FS_EXECUTE, masks.access[0]); - KUNIT_EXPECT_EQ(test, 0, masks.access[1]); + /* + * These casts to access_mask_t are needed because typeof(), used in + * KUNIT_EXPECT_EQ(), does not work on bitfields. + */ + KUNIT_EXPECT_EQ(test, LANDLOCK_ACCESS_FS_EXECUTE, + (access_mask_t)masks.layers[0].access); + KUNIT_EXPECT_EQ(test, 0, (access_mask_t)masks.layers[1].access); } static void test_scope_to_request_without_access(struct kunit *const test) { /* Denies execute and write. */ - struct layer_access_masks masks = { - .access[0] = LANDLOCK_ACCESS_FS_EXECUTE, - .access[1] = LANDLOCK_ACCESS_FS_WRITE_FILE, + struct layer_masks masks = { + .layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE, + .layers[1].access = LANDLOCK_ACCESS_FS_WRITE_FILE, }; /* Checks and scopes without access request. */ KUNIT_EXPECT_TRUE(test, scope_to_request(0, &masks)); - KUNIT_EXPECT_EQ(test, 0, masks.access[0]); - KUNIT_EXPECT_EQ(test, 0, masks.access[1]); + KUNIT_EXPECT_EQ(test, 0, (access_mask_t)masks.layers[0].access); + KUNIT_EXPECT_EQ(test, 0, (access_mask_t)masks.layers[1].access); } #endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ @@ -639,15 +648,15 @@ static void test_scope_to_request_without_access(struct kunit *const test) * Returns true if there is at least one access right different than * LANDLOCK_ACCESS_FS_REFER. */ -static bool is_eacces(const struct layer_access_masks *masks, +static bool is_eacces(const struct layer_masks *masks, const access_mask_t access_request) { if (!masks) return false; - for (size_t i = 0; i < ARRAY_SIZE(masks->access); i++) { + for (size_t i = 0; i < ARRAY_SIZE(masks->layers); i++) { /* LANDLOCK_ACCESS_FS_REFER alone must return -EXDEV. */ - if (masks->access[i] & access_request & + if (masks->layers[i].access & access_request & ~LANDLOCK_ACCESS_FS_REFER) return true; } @@ -661,7 +670,7 @@ static bool is_eacces(const struct layer_access_masks *masks, static void test_is_eacces_with_none(struct kunit *const test) { - const struct layer_access_masks masks = {}; + const struct layer_masks masks = {}; IE_FALSE(&masks, 0); IE_FALSE(&masks, LANDLOCK_ACCESS_FS_REFER); @@ -671,8 +680,8 @@ static void test_is_eacces_with_none(struct kunit *const test) static void test_is_eacces_with_refer(struct kunit *const test) { - const struct layer_access_masks masks = { - .access[0] = LANDLOCK_ACCESS_FS_REFER, + const struct layer_masks masks = { + .layers[0].access = LANDLOCK_ACCESS_FS_REFER, }; IE_FALSE(&masks, 0); @@ -683,8 +692,8 @@ static void test_is_eacces_with_refer(struct kunit *const test) static void test_is_eacces_with_write(struct kunit *const test) { - const struct layer_access_masks masks = { - .access[0] = LANDLOCK_ACCESS_FS_WRITE_FILE, + const struct layer_masks masks = { + .layers[0].access = LANDLOCK_ACCESS_FS_WRITE_FILE, }; IE_FALSE(&masks, 0); @@ -743,11 +752,11 @@ static bool is_access_to_paths_allowed(const struct landlock_ruleset *const domain, const struct path *const path, const access_mask_t access_request_parent1, - struct layer_access_masks *layer_masks_parent1, + struct layer_masks *layer_masks_parent1, struct landlock_request *const log_request_parent1, struct dentry *const dentry_child1, const access_mask_t access_request_parent2, - struct layer_access_masks *layer_masks_parent2, + struct layer_masks *layer_masks_parent2, struct landlock_request *const log_request_parent2, struct dentry *const dentry_child2) { @@ -755,9 +764,9 @@ is_access_to_paths_allowed(const struct landlock_ruleset *const domain, child1_is_directory = true, child2_is_directory = true; struct path walker_path; access_mask_t access_masked_parent1, access_masked_parent2; - struct layer_access_masks _layer_masks_child1, _layer_masks_child2; - struct layer_access_masks *layer_masks_child1 = NULL, - *layer_masks_child2 = NULL; + struct layer_masks _layer_masks_child1, _layer_masks_child2; + struct layer_masks *layer_masks_child1 = NULL, + *layer_masks_child2 = NULL; if (!access_request_parent1 && !access_request_parent2) return true; @@ -797,6 +806,10 @@ is_access_to_paths_allowed(const struct landlock_ruleset *const domain, } if (unlikely(dentry_child1)) { + /* + * Get the layer masks for the child dentries for use by domain + * check later. + */ if (landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS, &_layer_masks_child1, LANDLOCK_KEY_INODE)) @@ -952,7 +965,7 @@ static int current_check_access_path(const struct path *const path, }; const struct landlock_cred_security *const subject = landlock_get_applicable_subject(current_cred(), masks, NULL); - struct layer_access_masks layer_masks; + struct layer_masks layer_masks; struct landlock_request request = {}; if (!subject) @@ -1029,7 +1042,7 @@ static access_mask_t maybe_remove(const struct dentry *const dentry) static bool collect_domain_accesses(const struct landlock_ruleset *const domain, const struct dentry *const mnt_root, struct dentry *dir, - struct layer_access_masks *layer_masks_dom) + struct layer_masks *layer_masks_dom) { bool ret = false; @@ -1135,8 +1148,7 @@ static int current_check_refer_path(struct dentry *const old_dentry, access_mask_t access_request_parent1, access_request_parent2; struct path mnt_dir; struct dentry *old_parent; - struct layer_access_masks layer_masks_parent1 = {}, - layer_masks_parent2 = {}; + struct layer_masks layer_masks_parent1 = {}, layer_masks_parent2 = {}; struct landlock_request request1 = {}, request2 = {}; if (!subject) @@ -1202,7 +1214,6 @@ static int current_check_refer_path(struct dentry *const old_dentry, allow_parent2 = collect_domain_accesses(subject->domain, mnt_dir.dentry, new_dir->dentry, &layer_masks_parent2); - if (allow_parent1 && allow_parent2) return 0; @@ -1580,7 +1591,7 @@ static int hook_path_truncate(const struct path *const path) */ static void unmask_scoped_access(const struct landlock_ruleset *const client, const struct landlock_ruleset *const server, - struct layer_access_masks *const masks, + struct layer_masks *const masks, const access_mask_t access) { int client_layer, server_layer; @@ -1621,9 +1632,9 @@ static void unmask_scoped_access(const struct landlock_ruleset *const client, server_walker = server_walker->parent; for (; client_layer >= 0; client_layer--) { - if (masks->access[client_layer] & access && + if (masks->layers[client_layer].access & access && client_walker == server_walker) - masks->access[client_layer] &= ~access; + masks->layers[client_layer].access &= ~access; client_walker = client_walker->parent; server_walker = server_walker->parent; @@ -1635,7 +1646,7 @@ static int hook_unix_find(const struct path *const path, struct sock *other, { const struct landlock_ruleset *dom_other; const struct landlock_cred_security *subject; - struct layer_access_masks layer_masks; + struct layer_masks layer_masks; struct landlock_request request = {}; static const struct access_masks fs_resolve_unix = { .fs = LANDLOCK_ACCESS_FS_RESOLVE_UNIX, @@ -1739,7 +1750,7 @@ static bool is_device(const struct file *const file) static int hook_file_open(struct file *const file) { - struct layer_access_masks layer_masks = {}; + struct layer_masks layer_masks = {}; access_mask_t open_access_request, full_access_request, allowed_access, optional_access; const struct landlock_cred_security *const subject = @@ -1780,8 +1791,8 @@ static int hook_file_open(struct file *const file) * are still unfulfilled in any of the layers. */ allowed_access = full_access_request; - for (size_t i = 0; i < ARRAY_SIZE(layer_masks.access); i++) - allowed_access &= ~layer_masks.access[i]; + for (size_t i = 0; i < ARRAY_SIZE(layer_masks.layers); i++) + allowed_access &= ~layer_masks.layers[i].access; } /* @@ -1794,6 +1805,10 @@ static int hook_file_open(struct file *const file) #ifdef CONFIG_AUDIT landlock_file(file)->deny_masks = landlock_get_deny_masks( _LANDLOCK_ACCESS_FS_OPTIONAL, optional_access, &layer_masks); + landlock_file(file)->quiet_optional_accesses = + landlock_get_quiet_optional_accesses( + _LANDLOCK_ACCESS_FS_OPTIONAL, + landlock_file(file)->deny_masks, &layer_masks); #endif /* CONFIG_AUDIT */ if (access_mask_subset(open_access_request, allowed_access)) @@ -1830,6 +1845,7 @@ static int hook_file_truncate(struct file *const file) .access = LANDLOCK_ACCESS_FS_TRUNCATE, #ifdef CONFIG_AUDIT .deny_masks = landlock_file(file)->deny_masks, + .quiet_optional_accesses = landlock_file(file)->quiet_optional_accesses, #endif /* CONFIG_AUDIT */ }); return -EACCES; @@ -1869,6 +1885,7 @@ static int hook_file_ioctl_common(const struct file *const file, .access = LANDLOCK_ACCESS_FS_IOCTL_DEV, #ifdef CONFIG_AUDIT .deny_masks = landlock_file(file)->deny_masks, + .quiet_optional_accesses = landlock_file(file)->quiet_optional_accesses, #endif /* CONFIG_AUDIT */ }); return -EACCES; @@ -1901,6 +1918,14 @@ static bool control_current_fowner(struct fown_struct *const fown) lockdep_assert_held(&fown->lock); /* + * A process-group or session owner (PIDTYPE_PGID/PIDTYPE_SID) fans the + * signal out to every member at delivery time, so record the domain and + * let hook_file_send_sigiotask() check the live scope per recipient. + */ + if (fown->pid_type != PIDTYPE_PID && fown->pid_type != PIDTYPE_TGID) + return true; + + /* * Some callers (e.g. fcntl_dirnotify) may not be in an RCU read-side * critical section. */ @@ -1916,6 +1941,7 @@ static void hook_file_set_fowner(struct file *file) { struct landlock_ruleset *prev_dom; struct landlock_cred_security fown_subject = {}; + struct pid *prev_tg, *fown_tg = NULL; size_t fown_layer = 0; if (control_current_fowner(file_f_owner(file))) { @@ -1928,21 +1954,26 @@ static void hook_file_set_fowner(struct file *file) if (new_subject) { landlock_get_ruleset(new_subject->domain); fown_subject = *new_subject; + fown_tg = get_pid(task_tgid(current)); } } prev_dom = landlock_file(file)->fown_subject.domain; + prev_tg = landlock_file(file)->fown_tg; landlock_file(file)->fown_subject = fown_subject; + landlock_file(file)->fown_tg = fown_tg; #ifdef CONFIG_AUDIT landlock_file(file)->fown_layer = fown_layer; #endif /* CONFIG_AUDIT*/ /* May be called in an RCU read-side critical section. */ landlock_put_ruleset_deferred(prev_dom); + put_pid(prev_tg); } static void hook_file_free_security(struct file *file) { + put_pid(landlock_file(file)->fown_tg); landlock_put_ruleset_deferred(landlock_file(file)->fown_subject.domain); } diff --git a/security/landlock/fs.h b/security/landlock/fs.h index bf9948941f2fb..b4421d9df68ff 100644 --- a/security/landlock/fs.h +++ b/security/landlock/fs.h @@ -64,6 +64,13 @@ struct landlock_file_security { */ deny_masks_t deny_masks; /** + * @quiet_optional_accesses: Stores which optional accesses are covered + * by quiet rules within the layer referred to in deny_masks, one access + * per bit. Does not take into account whether the quiet access bits + * are actually set in the layer's corresponding landlock_hierarchy. + */ + optional_access_t quiet_optional_accesses; + /** * @fown_layer: Layer level of @fown_subject->domain with * LANDLOCK_SCOPE_SIGNAL. */ @@ -78,6 +85,16 @@ struct landlock_file_security { * euid. */ struct landlock_cred_security fown_subject; + /** + * @fown_tg: Thread group of the task that set the file owner, pinned + * while @fown_subject holds a domain. It lets + * hook_file_send_sigiotask() always allow a SIGIO delivered to the + * owner's own process -- e.g. the thread-group leader reached through a + * process-group owner -- matching the same-process exemption of + * hook_task_kill(). NULL when no domain is recorded. Protected by + * file->f_owner->lock, like @fown_subject. + */ + struct pid *fown_tg; }; #ifdef CONFIG_AUDIT @@ -86,7 +103,15 @@ struct landlock_file_security { /* clang-format off */ static_assert((typeof_member(struct landlock_file_security, fown_layer))~0 >= LANDLOCK_MAX_NUM_LAYERS); -/* clang-format off */ +/* clang-format on */ + +/* + * Make sure quiet_optional_accesses has enough bits to cover all optional + * accesses. + */ +static_assert(BITS_PER_TYPE(typeof_member(struct landlock_file_security, + quiet_optional_accesses)) >= + HWEIGHT(_LANDLOCK_ACCESS_FS_OPTIONAL)); #endif /* CONFIG_AUDIT */ @@ -126,6 +151,6 @@ __init void landlock_add_fs_hooks(void); int landlock_append_fs_rule(struct landlock_ruleset *const ruleset, const struct path *const path, - access_mask_t access_hierarchy); + access_mask_t access_hierarchy, const u32 flags); #endif /* _SECURITY_LANDLOCK_FS_H */ diff --git a/security/landlock/limits.h b/security/landlock/limits.h index b454ad73b15e8..08d5f2f6d321a 100644 --- a/security/landlock/limits.h +++ b/security/landlock/limits.h @@ -23,7 +23,7 @@ #define LANDLOCK_MASK_ACCESS_FS ((LANDLOCK_LAST_ACCESS_FS << 1) - 1) #define LANDLOCK_NUM_ACCESS_FS __const_hweight64(LANDLOCK_MASK_ACCESS_FS) -#define LANDLOCK_LAST_ACCESS_NET LANDLOCK_ACCESS_NET_CONNECT_TCP +#define LANDLOCK_LAST_ACCESS_NET LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP #define LANDLOCK_MASK_ACCESS_NET ((LANDLOCK_LAST_ACCESS_NET << 1) - 1) #define LANDLOCK_NUM_ACCESS_NET __const_hweight64(LANDLOCK_MASK_ACCESS_NET) @@ -31,6 +31,9 @@ #define LANDLOCK_MASK_SCOPE ((LANDLOCK_LAST_SCOPE << 1) - 1) #define LANDLOCK_NUM_SCOPE __const_hweight64(LANDLOCK_MASK_SCOPE) +#define LANDLOCK_NUM_ACCESS_MAX \ + MAX(MAX(LANDLOCK_NUM_ACCESS_FS, LANDLOCK_NUM_ACCESS_NET), LANDLOCK_NUM_SCOPE) + #define LANDLOCK_LAST_RESTRICT_SELF LANDLOCK_RESTRICT_SELF_TSYNC #define LANDLOCK_MASK_RESTRICT_SELF ((LANDLOCK_LAST_RESTRICT_SELF << 1) - 1) diff --git a/security/landlock/net.c b/security/landlock/net.c index c368649985c53..cbff59ec3abaa 100644 --- a/security/landlock/net.c +++ b/security/landlock/net.c @@ -20,7 +20,8 @@ #include "ruleset.h" int landlock_append_net_rule(struct landlock_ruleset *const ruleset, - const u16 port, access_mask_t access_rights) + const u16 port, access_mask_t access_rights, + const u32 flags) { int err; const struct landlock_id id = { @@ -35,7 +36,7 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset, ~landlock_get_net_access_mask(ruleset, 0); mutex_lock(&ruleset->lock); - err = landlock_insert_rule(ruleset, id, access_rights); + err = landlock_insert_rule(ruleset, id, access_rights, flags); mutex_unlock(&ruleset->lock); return err; @@ -44,10 +45,12 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset, static int current_check_access_socket(struct socket *const sock, struct sockaddr *const address, const int addrlen, - access_mask_t access_request) + access_mask_t access_request, + bool connecting) { + unsigned short sock_family; __be16 port; - struct layer_access_masks layer_masks = {}; + struct layer_masks layer_masks = {}; const struct landlock_rule *rule; struct landlock_id id = { .type = LANDLOCK_KEY_NET_PORT, @@ -66,30 +69,69 @@ static int current_check_access_socket(struct socket *const sock, if (addrlen < offsetofend(typeof(*address), sa_family)) return -EINVAL; + /* + * The socket is not locked, so sk_family can change concurrently due to + * e.g. setsockopt(IPV6_ADDRFORM). + */ + sock_family = READ_ONCE(sock->sk->sk_family); + switch (address->sa_family) { case AF_UNSPEC: - if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) { + if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP || + (access_request == LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP && + connecting)) { /* - * Connecting to an address with AF_UNSPEC dissolves - * the TCP association, which have the same effect as - * closing the connection while retaining the socket - * object (i.e., the file descriptor). As for dropping - * privileges, closing connections is always allowed. - * - * For a TCP access control system, this request is - * legitimate. Let the network stack handle potential - * inconsistencies and return -EINVAL if needed. + * Connecting to an address with AF_UNSPEC dissolves the + * remote association while retaining the socket object + * (i.e., the file descriptor). For TCP, it has the same + * effect as closing the connection. For UDP, it removes + * any preset remote address. As for dropping + * privileges, these actions are always allowed. Let + * the network stack handle potential inconsistencies + * and return -EINVAL if needed. */ return 0; - } else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) { + } else if (access_request == + LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP) { + if (sock_family == AF_INET6) { + /* + * We cannot allow sending UDP datagrams to an + * explicit AF_UNSPEC address on IPv6 sockets, + * even if AF_UNSPEC is treated as "no address" + * on such sockets (so it should always be + * allowed). That's because the socket's family + * can change under our feet (if another thread + * calls setsockopt(IPV6_ADDRFORM)) to IPv4, + * which would then treat AF_UNSPEC as AF_INET. + */ + audit_net.family = AF_UNSPEC; + audit_net.sk = sock->sk; + landlock_init_layer_masks( + subject->domain, access_request, + &layer_masks, LANDLOCK_KEY_NET_PORT); + landlock_log_denial( + subject, + &(struct landlock_request){ + .type = LANDLOCK_REQUEST_NET_ACCESS, + .audit.type = + LSM_AUDIT_DATA_NET, + .audit.u.net = &audit_net, + .access = access_request, + .layer_masks = &layer_masks, + }); + return -EACCES; + } + } else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP || + access_request == LANDLOCK_ACCESS_NET_BIND_UDP) { /* * Binding to an AF_UNSPEC address is treated * differently by IPv4 and IPv6 sockets. The socket's * family may change under our feet due to * setsockopt(IPV6_ADDRFORM), but that's ok: we either - * reject entirely or require - * %LANDLOCK_ACCESS_NET_BIND_TCP for the given port, so - * it cannot be used to bypass the policy. + * reject entirely for IPv6 or require + * %LANDLOCK_ACCESS_NET_BIND_TCP or + * %LANDLOCK_ACCESS_NET_BIND_UDP for IPv4, so it cannot + * be used to bypass the policy. * * IPv4 sockets map AF_UNSPEC to AF_INET for * retrocompatibility for bind accesses, only if the @@ -102,7 +144,7 @@ static int current_check_access_socket(struct socket *const sock, * these checks, but it is safer to return a proper * error and test consistency thanks to kselftest. */ - if (sock->sk->__sk_common.skc_family == AF_INET) { + if (sock_family == AF_INET) { const struct sockaddr_in *const sockaddr = (struct sockaddr_in *)address; @@ -121,7 +163,11 @@ static int current_check_access_socket(struct socket *const sock, } else { WARN_ON_ONCE(1); } - /* Only for bind(AF_UNSPEC+INADDR_ANY) on IPv4 socket. */ + /* + * AF_UNSPEC is treated as AF_INET only in + * bind(AF_UNSPEC+INADDR_ANY) on IPv4 sockets and when sending + * to AF_UNSPEC addresses on IPv4 sockets. + */ fallthrough; case AF_INET: { const struct sockaddr_in *addr4; @@ -132,10 +178,12 @@ static int current_check_access_socket(struct socket *const sock, addr4 = (struct sockaddr_in *)address; port = addr4->sin_port; - if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) { + if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP || + access_request == LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP) { audit_net.dport = port; audit_net.v4info.daddr = addr4->sin_addr.s_addr; - } else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) { + } else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP || + access_request == LANDLOCK_ACCESS_NET_BIND_UDP) { audit_net.sport = port; audit_net.v4info.saddr = addr4->sin_addr.s_addr; } else { @@ -154,10 +202,12 @@ static int current_check_access_socket(struct socket *const sock, addr6 = (struct sockaddr_in6 *)address; port = addr6->sin6_port; - if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP) { + if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP || + access_request == LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP) { audit_net.dport = port; audit_net.v6info.daddr = addr6->sin6_addr; - } else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) { + } else if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP || + access_request == LANDLOCK_ACCESS_NET_BIND_UDP) { audit_net.sport = port; audit_net.v6info.saddr = addr6->sin6_addr; } else { @@ -180,7 +230,7 @@ static int current_check_access_socket(struct socket *const sock, * check, but it is safer to return a proper error and test * consistency thanks to kselftest. */ - if (address->sa_family != sock->sk->__sk_common.skc_family && + if (address->sa_family != sock_family && address->sa_family != AF_UNSPEC) return -EINVAL; @@ -198,6 +248,7 @@ static int current_check_access_socket(struct socket *const sock, return 0; audit_net.family = address->sa_family; + audit_net.sk = sock->sk; landlock_log_denial(subject, &(struct landlock_request){ .type = LANDLOCK_REQUEST_NET_ACCESS, @@ -209,6 +260,44 @@ static int current_check_access_socket(struct socket *const sock, return -EACCES; } +static int current_check_autobind_udp_socket(struct socket *const sock) +{ + const struct access_masks bind_udp = { + .net = LANDLOCK_ACCESS_NET_BIND_UDP, + }; + struct sockaddr_storage port0 = {}; + unsigned short num; + bool slow; + + /* Quick return for non-Landlocked tasks. */ + if (!landlock_get_applicable_subject(current_cred(), bind_udp, NULL)) + return 0; + + /* + * On UDP sockets, if a local port has not already been bound, calling + * connect() or sending a first datagram has the side effect of + * autobinding an ephemeral port: we also have to check that the process + * would have had the right to bind(0) explicitly. Hold the socket lock + * around the inet_num read to exclude udp_lib_get_port()'s transient + * inet_num = snum write that is reverted to 0 on a failing reuseport + * bind. + */ + slow = lock_sock_fast(sock->sk); + num = inet_sk(sock->sk)->inet_num; + unlock_sock_fast(sock->sk, slow); + if (num != 0) + return 0; + + /* + * Construct a struct sockaddr* with port 0 to pretend the process tried + * to bind() on that address. + */ + port0.ss_family = READ_ONCE(sock->sk->sk_family); + + return current_check_access_socket(sock, (struct sockaddr *)&port0, + sizeof(port0), bind_udp.net, false); +} + static int hook_socket_bind(struct socket *const sock, struct sockaddr *const address, const int addrlen) { @@ -216,11 +305,13 @@ static int hook_socket_bind(struct socket *const sock, if (sk_is_tcp(sock->sk)) access_request = LANDLOCK_ACCESS_NET_BIND_TCP; + else if (sk_is_udp(sock->sk)) + access_request = LANDLOCK_ACCESS_NET_BIND_UDP; else return 0; return current_check_access_socket(sock, address, addrlen, - access_request); + access_request, false); } static int hook_socket_connect(struct socket *const sock, @@ -228,19 +319,57 @@ static int hook_socket_connect(struct socket *const sock, const int addrlen) { access_mask_t access_request; + int ret = 0; if (sk_is_tcp(sock->sk)) access_request = LANDLOCK_ACCESS_NET_CONNECT_TCP; + else if (sk_is_udp(sock->sk)) + access_request = LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP; else return 0; - return current_check_access_socket(sock, address, addrlen, - access_request); + ret = current_check_access_socket(sock, address, addrlen, + access_request, true); + + /* + * connect()ing to an AF_UNSPEC address does not trigger an autobind and + * should never be restricted. + */ + if (ret == 0 && sk_is_udp(sock->sk) && + addrlen >= offsetofend(typeof(*address), sa_family) && + address->sa_family != AF_UNSPEC) + ret = current_check_autobind_udp_socket(sock); + + return ret; +} + +static int hook_socket_sendmsg(struct socket *const sock, + struct msghdr *const msg, const int size) +{ + struct sockaddr *const address = msg->msg_name; + const int addrlen = msg->msg_namelen; + access_mask_t access_request; + int ret = 0; + + if (sk_is_udp(sock->sk)) + access_request = LANDLOCK_ACCESS_NET_CONNECT_SEND_UDP; + else + return 0; + + if (address != NULL) + ret = current_check_access_socket(sock, address, addrlen, + access_request, false); + + if (ret == 0) + ret = current_check_autobind_udp_socket(sock); + + return ret; } static struct security_hook_list landlock_hooks[] __ro_after_init = { LSM_HOOK_INIT(socket_bind, hook_socket_bind), LSM_HOOK_INIT(socket_connect, hook_socket_connect), + LSM_HOOK_INIT(socket_sendmsg, hook_socket_sendmsg), }; __init void landlock_add_net_hooks(void) diff --git a/security/landlock/net.h b/security/landlock/net.h index 09960c237a13e..5c0e3b4090cba 100644 --- a/security/landlock/net.h +++ b/security/landlock/net.h @@ -16,7 +16,8 @@ __init void landlock_add_net_hooks(void); int landlock_append_net_rule(struct landlock_ruleset *const ruleset, - const u16 port, access_mask_t access_rights); + const u16 port, access_mask_t access_rights, + const u32 flags); #else /* IS_ENABLED(CONFIG_INET) */ static inline void landlock_add_net_hooks(void) { @@ -24,7 +25,7 @@ static inline void landlock_add_net_hooks(void) static inline int landlock_append_net_rule(struct landlock_ruleset *const ruleset, const u16 port, - access_mask_t access_rights) + access_mask_t access_rights, const u32 flags) { return -EAFNOSUPPORT; } diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c index 181df7736bb92..4dd09ea22c844 100644 --- a/security/landlock/ruleset.c +++ b/security/landlock/ruleset.c @@ -21,6 +21,7 @@ #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/workqueue.h> +#include <uapi/linux/landlock.h> #include "access.h" #include "domain.h" @@ -255,6 +256,7 @@ static int insert_rule(struct landlock_ruleset *const ruleset, if (WARN_ON_ONCE(this->layers[0].level != 0)) return -EINVAL; this->layers[0].access |= (*layers)[0].access; + this->layers[0].flags.quiet |= (*layers)[0].flags.quiet; return 0; } @@ -305,12 +307,15 @@ static void build_check_layer(void) /* @ruleset must be locked by the caller. */ int landlock_insert_rule(struct landlock_ruleset *const ruleset, const struct landlock_id id, - const access_mask_t access) + const access_mask_t access, const u32 flags) { struct landlock_layer layers[] = { { .access = access, /* When @level is zero, insert_rule() extends @ruleset. */ .level = 0, + .flags = { + .quiet = !!(flags & LANDLOCK_ADD_RULE_QUIET), + }, } }; build_check_layer(); @@ -351,6 +356,7 @@ static int merge_tree(struct landlock_ruleset *const dst, return -EINVAL; layers[0].access = walker_rule->layers[0].access; + layers[0].flags = walker_rule->layers[0].flags; err = insert_rule(dst, id, &layers, ARRAY_SIZE(layers)); if (err) @@ -581,6 +587,10 @@ landlock_merge_ruleset(struct landlock_ruleset *const parent, if (err) return ERR_PTR(err); +#ifdef CONFIG_AUDIT + new_dom->hierarchy->quiet_masks = ruleset->quiet_masks; +#endif /* CONFIG_AUDIT */ + return no_free_ptr(new_dom); } @@ -628,7 +638,7 @@ landlock_find_rule(const struct landlock_ruleset *const ruleset, * remaining unfulfilled access rights and masks has no leftover set bits). */ bool landlock_unmask_layers(const struct landlock_rule *const rule, - struct layer_access_masks *masks) + struct layer_masks *masks) { if (!masks) return true; @@ -649,11 +659,17 @@ bool landlock_unmask_layers(const struct landlock_rule *const rule, const struct landlock_layer *const layer = &rule->layers[i]; /* Clear the bits where the layer in the rule grants access. */ - masks->access[layer->level - 1] &= ~layer->access; + masks->layers[layer->level - 1].access &= ~layer->access; + +#ifdef CONFIG_AUDIT + /* Collect rule flags for each layer. */ + if (layer->flags.quiet) + masks->layers[layer->level - 1].quiet = true; +#endif /* CONFIG_AUDIT */ } - for (size_t i = 0; i < ARRAY_SIZE(masks->access); i++) { - if (masks->access[i]) + for (size_t i = 0; i < ARRAY_SIZE(masks->layers); i++) { + if (masks->layers[i].access) return false; } return true; @@ -666,8 +682,9 @@ get_access_mask_t(const struct landlock_ruleset *const ruleset, /** * landlock_init_layer_masks - Initialize layer masks from an access request * - * Populates @masks such that for each access right in @access_request, - * the bits for all the layers are set where this access right is handled. + * Populates @masks such that for each access right in @access_request, the bits + * for all the layers are set where this access right is handled. Rule flags + * are also zeroed. * * @domain: The domain that defines the current restrictions. * @access_request: The requested access rights to check. @@ -680,7 +697,7 @@ get_access_mask_t(const struct landlock_ruleset *const ruleset, access_mask_t landlock_init_layer_masks(const struct landlock_ruleset *const domain, const access_mask_t access_request, - struct layer_access_masks *const masks, + struct layer_masks *const masks, const enum landlock_key_type key_type) { access_mask_t handled_accesses = 0; @@ -709,11 +726,19 @@ landlock_init_layer_masks(const struct landlock_ruleset *const domain, for (size_t i = 0; i < domain->num_layers; i++) { const access_mask_t handled = get_access_mask(domain, i); - masks->access[i] = access_request & handled; - handled_accesses |= masks->access[i]; + masks->layers[i].access = access_request & handled; + handled_accesses |= masks->layers[i].access; +#ifdef CONFIG_AUDIT + masks->layers[i].quiet = false; +#endif /* CONFIG_AUDIT */ + } + for (size_t i = domain->num_layers; i < ARRAY_SIZE(masks->layers); + i++) { + masks->layers[i].access = 0; +#ifdef CONFIG_AUDIT + masks->layers[i].quiet = false; +#endif /* CONFIG_AUDIT */ } - for (size_t i = domain->num_layers; i < ARRAY_SIZE(masks->access); i++) - masks->access[i] = 0; return handled_accesses; } diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index 889f4b30301a5..61f3c253d5c90 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -29,7 +29,18 @@ struct landlock_layer { /** * @level: Position of this layer in the layer stack. Starts from 1. */ - u16 level; + u8 level; + /** + * @flags: Bitfield for special flags attached to this rule. + */ + struct { + /** + * @quiet: Suppresses denial logs for the object covered by this + * rule in this domain. For filesystem rules, this inherits + * down the file hierarchy. + */ + u8 quiet : 1; + } flags; /** * @access: Bitfield of allowed actions on the kernel object. They are * relative to the object type (e.g. %LANDLOCK_ACTION_FS_READ). @@ -145,8 +156,8 @@ struct landlock_ruleset { * @work_free: Enables to free a ruleset within a lockless * section. This is only used by * landlock_put_ruleset_deferred() when @usage reaches zero. - * The fields @lock, @usage, @num_rules, @num_layers and - * @access_masks are then unused. + * The fields @lock, @usage, @num_rules, @num_layers, + * @quiet_masks and @access_masks are then unused. */ struct work_struct work_free; struct { @@ -173,6 +184,12 @@ struct landlock_ruleset { */ u32 num_layers; /** + * @quiet_masks: Stores the quiet flags for an unmerged + * ruleset. For a merged domain, this is stored in each + * layer's struct landlock_hierarchy instead. + */ + struct access_masks quiet_masks; + /** * @access_masks: Contains the subset of filesystem and * network actions that are restricted by a ruleset. * A domain saves all layers of merged rulesets in a @@ -202,7 +219,7 @@ DEFINE_FREE(landlock_put_ruleset, struct landlock_ruleset *, int landlock_insert_rule(struct landlock_ruleset *const ruleset, const struct landlock_id id, - const access_mask_t access); + const access_mask_t access, const u32 flags); struct landlock_ruleset * landlock_merge_ruleset(struct landlock_ruleset *const parent, @@ -302,12 +319,12 @@ landlock_get_scope_mask(const struct landlock_ruleset *const ruleset, } bool landlock_unmask_layers(const struct landlock_rule *const rule, - struct layer_access_masks *masks); + struct layer_masks *masks); access_mask_t landlock_init_layer_masks(const struct landlock_ruleset *const domain, const access_mask_t access_request, - struct layer_access_masks *masks, + struct layer_masks *masks, const enum landlock_key_type key_type); #endif /* _SECURITY_LANDLOCK_RULESET_H */ diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index accfd2e5a0cd4..36b02892c62f5 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -105,8 +105,11 @@ static void build_check_abi(void) ruleset_size = sizeof(ruleset_attr.handled_access_fs); ruleset_size += sizeof(ruleset_attr.handled_access_net); ruleset_size += sizeof(ruleset_attr.scoped); + ruleset_size += sizeof(ruleset_attr.quiet_access_fs); + ruleset_size += sizeof(ruleset_attr.quiet_access_net); + ruleset_size += sizeof(ruleset_attr.quiet_scoped); BUILD_BUG_ON(sizeof(ruleset_attr) != ruleset_size); - BUILD_BUG_ON(sizeof(ruleset_attr) != 24); + BUILD_BUG_ON(sizeof(ruleset_attr) != 48); path_beneath_size = sizeof(path_beneath_attr.allowed_access); path_beneath_size += sizeof(path_beneath_attr.parent_fd); @@ -166,7 +169,7 @@ static const struct file_operations ruleset_fops = { * If the change involves a fix that requires userspace awareness, also update * the errata documentation in Documentation/userspace-api/landlock.rst . */ -const int landlock_abi_version = 9; +const int landlock_abi_version = 10; /** * sys_landlock_create_ruleset - Create a new ruleset @@ -193,6 +196,9 @@ const int landlock_abi_version = 9; * - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time; * - %EINVAL: unknown @flags, or unknown access, or unknown scope, or too small * @size; + * - %EINVAL: quiet_access_fs, quiet_access_net, or quiet_scoped is not a + * subset of the corresponding handled_access_fs, handled_access_net, or + * scoped; * - %E2BIG: @attr or @size inconsistencies; * - %EFAULT: @attr or @size inconsistencies; * - %ENOMSG: empty &landlock_ruleset_attr.handled_access_fs. @@ -249,6 +255,21 @@ SYSCALL_DEFINE3(landlock_create_ruleset, if ((ruleset_attr.scoped | LANDLOCK_MASK_SCOPE) != LANDLOCK_MASK_SCOPE) return -EINVAL; + /* + * Check that quiet masks are subsets of the respective handled masks. + * Because of the checks above this is sufficient to also ensure that + * the quiet masks are valid access masks. + */ + if ((ruleset_attr.quiet_access_fs | ruleset_attr.handled_access_fs) != + ruleset_attr.handled_access_fs) + return -EINVAL; + if ((ruleset_attr.quiet_access_net | ruleset_attr.handled_access_net) != + ruleset_attr.handled_access_net) + return -EINVAL; + if ((ruleset_attr.quiet_scoped | ruleset_attr.scoped) != + ruleset_attr.scoped) + return -EINVAL; + /* Checks arguments and transforms to kernel struct. */ ruleset = landlock_create_ruleset(ruleset_attr.handled_access_fs, ruleset_attr.handled_access_net, @@ -256,6 +277,10 @@ SYSCALL_DEFINE3(landlock_create_ruleset, if (IS_ERR(ruleset)) return PTR_ERR(ruleset); + ruleset->quiet_masks.fs = ruleset_attr.quiet_access_fs; + ruleset->quiet_masks.net = ruleset_attr.quiet_access_net; + ruleset->quiet_masks.scope = ruleset_attr.quiet_scoped; + /* Creates anonymous FD referring to the ruleset. */ ruleset_fd = anon_inode_getfd("[landlock-ruleset]", &ruleset_fops, ruleset, O_RDWR | O_CLOEXEC); @@ -320,7 +345,7 @@ static int get_path_from_fd(const s32 fd, struct path *const path) } static int add_rule_path_beneath(struct landlock_ruleset *const ruleset, - const void __user *const rule_attr) + const void __user *const rule_attr, u32 flags) { struct landlock_path_beneath_attr path_beneath_attr; struct path path; @@ -335,9 +360,10 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset, /* * Informs about useless rule: empty allowed_access (i.e. deny rules) - * are ignored in path walks. + * are ignored in path walks. However, the rule is not useless if it is + * there to hold a quiet flag. */ - if (!path_beneath_attr.allowed_access) + if (!flags && !path_beneath_attr.allowed_access) return -ENOMSG; /* Checks that allowed_access matches the @ruleset constraints. */ @@ -345,6 +371,10 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset, if ((path_beneath_attr.allowed_access | mask) != mask) return -EINVAL; + /* Checks for useless quiet flag. */ + if (flags & LANDLOCK_ADD_RULE_QUIET && !ruleset->quiet_masks.fs) + return -EINVAL; + /* Gets and checks the new rule. */ err = get_path_from_fd(path_beneath_attr.parent_fd, &path); if (err) @@ -352,13 +382,13 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset, /* Imports the new rule. */ err = landlock_append_fs_rule(ruleset, &path, - path_beneath_attr.allowed_access); + path_beneath_attr.allowed_access, flags); path_put(&path); return err; } static int add_rule_net_port(struct landlock_ruleset *ruleset, - const void __user *const rule_attr) + const void __user *const rule_attr, u32 flags) { struct landlock_net_port_attr net_port_attr; int res; @@ -371,9 +401,10 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset, /* * Informs about useless rule: empty allowed_access (i.e. deny rules) - * are ignored by network actions. + * are ignored by network actions. However, the rule is not useless if + * it is there to hold a quiet flag. */ - if (!net_port_attr.allowed_access) + if (!flags && !net_port_attr.allowed_access) return -ENOMSG; /* Checks that allowed_access matches the @ruleset constraints. */ @@ -381,13 +412,17 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset, if ((net_port_attr.allowed_access | mask) != mask) return -EINVAL; + /* Checks for useless quiet flag. */ + if (flags & LANDLOCK_ADD_RULE_QUIET && !ruleset->quiet_masks.net) + return -EINVAL; + /* Denies inserting a rule with port greater than 65535. */ if (net_port_attr.port > U16_MAX) return -EINVAL; /* Imports the new rule. */ return landlock_append_net_rule(ruleset, net_port_attr.port, - net_port_attr.allowed_access); + net_port_attr.allowed_access, flags); } /** @@ -398,7 +433,7 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset, * @rule_type: Identify the structure type pointed to by @rule_attr: * %LANDLOCK_RULE_PATH_BENEATH or %LANDLOCK_RULE_NET_PORT. * @rule_attr: Pointer to a rule (matching the @rule_type). - * @flags: Must be 0. + * @flags: Must be 0 or %LANDLOCK_ADD_RULE_QUIET. * * This system call enables to define a new rule and add it to an existing * ruleset. @@ -408,20 +443,25 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset, * - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time; * - %EAFNOSUPPORT: @rule_type is %LANDLOCK_RULE_NET_PORT but TCP/IP is not * supported by the running kernel; - * - %EINVAL: @flags is not 0; + * - %EINVAL: @flags is not valid; * - %EINVAL: The rule accesses are inconsistent (i.e. * &landlock_path_beneath_attr.allowed_access or * &landlock_net_port_attr.allowed_access is not a subset of the ruleset * handled accesses) * - %EINVAL: &landlock_net_port_attr.port is greater than 65535; + * - %EINVAL: LANDLOCK_ADD_RULE_QUIET is passed but the ruleset has no + * quiet access bits set for the corresponding rule type. * - %ENOMSG: Empty accesses (e.g. &landlock_path_beneath_attr.allowed_access is - * 0); + * 0) and no flags; * - %EBADF: @ruleset_fd is not a file descriptor for the current thread, or a * member of @rule_attr is not a file descriptor as expected; * - %EBADFD: @ruleset_fd is not a ruleset file descriptor, or a member of * @rule_attr is not the expected file descriptor type; * - %EPERM: @ruleset_fd has no write access to the underlying ruleset; * - %EFAULT: @rule_attr was not a valid address. + * + * .. kernel-doc:: include/uapi/linux/landlock.h + * :identifiers: landlock_add_rule_flags */ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd, const enum landlock_rule_type, rule_type, @@ -432,8 +472,7 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd, if (!is_initialized()) return -EOPNOTSUPP; - /* No flag for now. */ - if (flags) + if (flags && flags != LANDLOCK_ADD_RULE_QUIET) return -EINVAL; /* Gets and checks the ruleset. */ @@ -443,9 +482,9 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd, switch (rule_type) { case LANDLOCK_RULE_PATH_BENEATH: - return add_rule_path_beneath(ruleset, rule_attr); + return add_rule_path_beneath(ruleset, rule_attr, flags); case LANDLOCK_RULE_NET_PORT: - return add_rule_net_port(ruleset, rule_attr); + return add_rule_net_port(ruleset, rule_attr, flags); default: return -EINVAL; } diff --git a/security/landlock/task.c b/security/landlock/task.c index 6d46042132ce1..7ddf211f75c37 100644 --- a/security/landlock/task.c +++ b/security/landlock/task.c @@ -411,6 +411,17 @@ static int hook_file_send_sigiotask(struct task_struct *tsk, if (!subject->domain) return 0; + /* + * Always allow delivery to the file owner's own process, including a + * thread-group leader reached through a process-group owner. This + * mirrors hook_task_kill()'s same-process exemption and preserves the + * guarantee of commit 18eb75f3af40 ("landlock: Always allow signals + * between threads of the same process"), which the registration-time + * check cannot honor for a process-group target. + */ + if (task_tgid(tsk) == landlock_file(fown->file)->fown_tg) + return 0; + scoped_guard(rcu) { is_scoped = domain_is_scoped(subject->domain, |
