// SPDX-License-Identifier: GPL-2.0-only /* * Landlock - Log helpers * * Copyright © 2023-2025 Microsoft Corporation * Copyright © 2026 Cloudflare, Inc. */ #include #include #include #include "access.h" #include "audit.h" #include "common.h" #include "cred.h" #include "domain.h" #include "limits.h" #include "log.h" #include "ruleset.h" #include "trace.h" static struct landlock_hierarchy * get_hierarchy(const struct landlock_domain *const domain, const size_t layer) { struct landlock_hierarchy *hierarchy = domain->hierarchy; ssize_t i; if (WARN_ON_ONCE(layer >= domain->num_layers)) return hierarchy; for (i = domain->num_layers - 1; i > layer; i--) { if (WARN_ON_ONCE(!hierarchy->parent)) break; hierarchy = hierarchy->parent; } return hierarchy; } #ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST static void test_get_hierarchy(struct kunit *const test) { struct landlock_hierarchy dom0_hierarchy = { .id = 10, }; struct landlock_hierarchy dom1_hierarchy = { .parent = &dom0_hierarchy, .id = 20, }; struct landlock_hierarchy dom2_hierarchy = { .parent = &dom1_hierarchy, .id = 30, }; struct landlock_domain dom2 = { .hierarchy = &dom2_hierarchy, .num_layers = 3, }; KUNIT_EXPECT_EQ(test, 10, get_hierarchy(&dom2, 0)->id); KUNIT_EXPECT_EQ(test, 20, get_hierarchy(&dom2, 1)->id); KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, 2)->id); /* KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, -1)->id); */ } #endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ /* Get the youngest layer that denied the access_request. */ static size_t get_denied_layer(const struct landlock_domain *const domain, access_mask_t *const access_request, const struct layer_masks *masks) { 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; } } /* Not found - fall back to default values */ *access_request = 0; return domain->num_layers - 1; } #ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST static void test_get_denied_layer(struct kunit *const test) { const struct landlock_domain dom = { .num_layers = 5, }; 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; access = LANDLOCK_ACCESS_FS_EXECUTE; KUNIT_EXPECT_EQ(test, 0, get_denied_layer(&dom, &access, &masks)); KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_EXECUTE); access = LANDLOCK_ACCESS_FS_READ_FILE; KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks)); KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_FILE); access = LANDLOCK_ACCESS_FS_READ_DIR; KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks)); KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR); access = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR; KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks)); KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR); access = LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_READ_DIR; KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks)); KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR); access = LANDLOCK_ACCESS_FS_WRITE_FILE; KUNIT_EXPECT_EQ(test, 4, get_denied_layer(&dom, &access, &masks)); KUNIT_EXPECT_EQ(test, access, 0); } #endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_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, optional_access_t quiet_optional_accesses, bool *quiet) { const unsigned long access_opt = all_existing_optional_access; const unsigned long access_req = *access_request; access_mask_t missing = 0; 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); for_each_set_bit(access_bit, &access_opt, BITS_PER_TYPE(access_mask_t)) { if (access_req & BIT(access_bit)) { const size_t layer = (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; } #ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST 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, 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, 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, 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, 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 */ static bool is_valid_request(const struct landlock_request *const request) { if (WARN_ON_ONCE(request->layer_plus_one > LANDLOCK_MAX_NUM_LAYERS)) return false; if (WARN_ON_ONCE(!(!!request->layer_plus_one ^ !!request->access))) return false; if (request->access) { if (WARN_ON_ONCE(!(!!request->layer_masks ^ !!request->all_existing_optional_access))) return false; } else { if (WARN_ON_ONCE(request->layer_masks || request->all_existing_optional_access)) return false; } 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; } } /* * Whether a quiet rule silences the denial: the rule must cover the whole * denied access in the layer that denied it (a quiet rule in a non-denying * layer does not suppress the denial). */ static bool is_denial_quieted(const struct landlock_request *const request, const struct landlock_hierarchy *const youngest_denied, const access_mask_t missing, const bool object_quiet_flag) { if (object_quiet_flag) { const access_mask_t quiet_mask = pick_access_mask_for_request_type( request->type, youngest_denied->quiet_masks); return (quiet_mask & missing) == missing; } /* * Either the object is not quiet, or this is a scope request. We check * request->type to distinguish between the two cases. */ switch (request->type) { case LANDLOCK_REQUEST_SCOPE_SIGNAL: return !!(youngest_denied->quiet_masks.scope & LANDLOCK_SCOPE_SIGNAL); case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET: return !!(youngest_denied->quiet_masks.scope & LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET); /* * Leave LANDLOCK_REQUEST_PTRACE and LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY * unhandled for now - they are never quiet. */ default: return false; } } /* * Computes whether a denial from youngest_denied is selected for logging by the * domain's policy: its logging must not be disabled (by both per-execution * flags being off, or by an ancestor's * LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF), the per-execution flag matching * same_exec must be set, and no quiet rule may cover the denied access. * landlock_log_denial() computes this once and passes it to * landlock_audit_denial(), which additionally requires audit_enabled. */ static bool is_denial_logged(const struct landlock_request *const request, const struct landlock_hierarchy *const youngest_denied, const access_mask_t missing, const bool same_exec, const bool object_quiet_flag) { if (READ_ONCE(youngest_denied->log_status) == LANDLOCK_LOG_DISABLED) return false; if (!(same_exec ? youngest_denied->log_same_exec : youngest_denied->log_new_exec)) return false; return !is_denial_quieted(request, youngest_denied, missing, object_quiet_flag); } /** * landlock_log_denial - Log a denied access * * @subject: The Landlock subject's credential denying an action. * @request: Detail of the user space request. */ void landlock_log_denial(const struct landlock_cred_security *const subject, const struct landlock_request *const request) { struct landlock_hierarchy *youngest_denied; size_t youngest_layer; access_mask_t missing; bool object_quiet_flag = false; if (WARN_ON_ONCE(!subject || !subject->domain || !subject->domain->hierarchy || !request)) return; if (!is_valid_request(request)) return; missing = request->access; if (missing) { /* Gets the nearest domain that denies the request. */ if (request->layer_masks) { 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->quiet_optional_accesses, &object_quiet_flag); } youngest_denied = get_hierarchy(subject->domain, youngest_layer); } else { youngest_layer = request->layer_plus_one - 1; youngest_denied = get_hierarchy(subject->domain, youngest_layer); } const bool same_exec = !!(subject->domain_exec & BIT(youngest_layer)); const bool logged = is_denial_logged(request, youngest_denied, missing, same_exec, object_quiet_flag); /* * Consistently keeps track of the number of denied access requests even * if audit is currently disabled, or if audit rules currently exclude * this record type, or if landlock_restrict_self(2)'s flags quiet logs. */ atomic64_inc(&youngest_denied->num_denials); landlock_trace_denial(request, youngest_denied, missing, same_exec, logged); landlock_audit_denial(request, youngest_denied, missing, logged); } /** * landlock_log_free_domain - Log domain deallocation * * @hierarchy: The domain's hierarchy being deallocated. * * Called from landlock_put_domain_deferred() (via a work queue scheduled by * hook_cred_free()) or directly from landlock_put_domain(). */ void landlock_log_free_domain(const struct landlock_hierarchy *const hierarchy) { if (WARN_ON_ONCE(!hierarchy)) return; landlock_trace_free_domain(hierarchy); landlock_audit_free_domain(hierarchy); } #ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST static struct kunit_case test_cases[] = { /* clang-format off */ KUNIT_CASE(test_get_hierarchy), KUNIT_CASE(test_get_denied_layer), KUNIT_CASE(test_get_layer_from_deny_masks), {} /* clang-format on */ }; static struct kunit_suite test_suite = { .name = "landlock_log", .test_cases = test_cases, }; kunit_test_suite(test_suite); #endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */