diff --git a/security/landlock/access.h b/security/landlock/access.h index ce374a65f865..d926078bf0a5 100644 --- a/security/landlock/access.h +++ b/security/landlock/access.h @@ -143,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 8c56f7f6467a..50536c568526 100644 --- a/security/landlock/audit.c +++ b/security/landlock/audit.c @@ -249,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; @@ -257,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); @@ -265,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; } @@ -288,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)); + 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, 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 */ @@ -349,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 * @@ -367,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)) @@ -382,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); @@ -420,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 b85d752273ac..620f8a24291d 100644 --- a/security/landlock/audit.h +++ b/security/landlock/audit.h @@ -48,6 +48,7 @@ struct landlock_request { /* 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 4ae45b300071..9a8355fccd26 100644 --- a/security/landlock/domain.c +++ b/security/landlock/domain.c @@ -157,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) diff --git a/security/landlock/domain.h b/security/landlock/domain.h index 9f560f3c3bd1..2a1660e3dea7 100644 --- a/security/landlock/domain.h +++ b/security/landlock/domain.h @@ -126,6 +126,10 @@ landlock_get_deny_masks(const access_mask_t all_existing_optional_access, const access_mask_t optional_access, 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); static inline void diff --git a/security/landlock/fs.c b/security/landlock/fs.c index bd68a752abbf..6292887e6cef 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -1805,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)) @@ -1841,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; @@ -1880,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; diff --git a/security/landlock/fs.h b/security/landlock/fs.h index e4c530511360..b4421d9df68f 100644 --- a/security/landlock/fs.h +++ b/security/landlock/fs.h @@ -63,6 +63,13 @@ struct landlock_file_security { * _LANDLOCK_ACCESS_FS_OPTIONAL). */ 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. @@ -96,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 */