diff options
Diffstat (limited to 'security/landlock/domain.c')
| -rw-r--r-- | security/landlock/domain.c | 775 |
1 files changed, 775 insertions, 0 deletions
diff --git a/security/landlock/domain.c b/security/landlock/domain.c new file mode 100644 index 000000000..93c7104fd --- /dev/null +++ b/security/landlock/domain.c @@ -0,0 +1,775 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Landlock - Domain management + * + * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net> + * Copyright © 2018-2020 ANSSI + * Copyright © 2024-2025 Microsoft Corporation + * Copyright © 2026 Cloudflare, Inc. + */ + +#include <kunit/test.h> +#include <linux/bitops.h> +#include <linux/bits.h> +#include <linux/cleanup.h> +#include <linux/cred.h> +#include <linux/err.h> +#include <linux/file.h> +#include <linux/lockdep.h> +#include <linux/mm.h> +#include <linux/mutex.h> +#include <linux/overflow.h> +#include <linux/path.h> +#include <linux/pid.h> +#include <linux/rbtree.h> +#include <linux/refcount.h> +#include <linux/sched.h> +#include <linux/signal.h> +#include <linux/slab.h> +#include <linux/uidgid.h> +#include <linux/workqueue.h> + +#include "access.h" +#include "common.h" +#include "domain.h" +#include "id.h" +#include "limits.h" +#include "ruleset.h" + +static void build_check_domain(void) +{ + const struct landlock_domain domain = { + .num_layers = ~0, + }; + + BUILD_BUG_ON(domain.num_layers < LANDLOCK_MAX_NUM_LAYERS); +} + +static struct landlock_domain *create_domain(const u32 num_layers) +{ + struct landlock_domain *new_domain; + + build_check_domain(); + new_domain = kzalloc_flex(*new_domain, handled_masks, num_layers, + GFP_KERNEL_ACCOUNT); + if (!new_domain) + return ERR_PTR(-ENOMEM); + + refcount_set(&new_domain->usage, 1); + new_domain->rules.root_inode = RB_ROOT; + +#if IS_ENABLED(CONFIG_INET) + new_domain->rules.root_net_port = RB_ROOT; +#endif /* IS_ENABLED(CONFIG_INET) */ + + new_domain->num_layers = num_layers; + return new_domain; +} + +static void free_domain(struct landlock_domain *const domain) +{ + might_sleep(); + landlock_free_rules(&domain->rules); + landlock_put_hierarchy(domain->hierarchy); + kfree(domain); +} + +void landlock_put_domain(struct landlock_domain *const domain) +{ + might_sleep(); + if (domain && refcount_dec_and_test(&domain->usage)) + free_domain(domain); +} + +static void free_domain_work(struct work_struct *const work) +{ + struct landlock_domain *domain; + + domain = container_of(work, struct landlock_domain, work_free); + free_domain(domain); +} + +void landlock_put_domain_deferred(struct landlock_domain *const domain) +{ + if (domain && refcount_dec_and_test(&domain->usage)) { + INIT_WORK(&domain->work_free, free_domain_work); + schedule_work(&domain->work_free); + } +} + +/* The returned access has the same lifetime as the domain. */ +static const struct landlock_rule * +find_rule(const struct landlock_domain *const domain, + const struct landlock_id id) +{ + const struct rb_root *root; + const struct rb_node *node; + + root = landlock_get_rule_root((struct landlock_rules *)&domain->rules, + id.type); + if (IS_ERR(root)) + return NULL; + node = root->rb_node; + + while (node) { + struct landlock_rule *this = + rb_entry(node, struct landlock_rule, node); + + if (this->key.data == id.key.data) + return this; + if (this->key.data < id.key.data) + node = node->rb_right; + else + node = node->rb_left; + } + return NULL; +} + +/** + * landlock_unmask_layers - Remove the access rights in @masks which are + * granted by a matching rule + * + * Looks up the rule matching @id in @domain, then updates the set of + * (per-layer) unfulfilled access rights @masks so that all the access rights + * granted by that rule are removed (because they are now fulfilled). + * + * @domain: The Landlock domain to search for a matching rule. + * @id: Identifier for the rule target (e.g. inode, port). + * @masks: A matrix of unfulfilled access rights for each layer. + * @matched_rule: Optional output for the matched rule (for tracing); set to + * the matching rule when non-NULL, unchanged otherwise. + * + * Return: True if the request is allowed (i.e. the access rights granted all + * remaining unfulfilled access rights and masks has no leftover set bits). + */ +bool landlock_unmask_layers(const struct landlock_domain *const domain, + const struct landlock_id id, + struct layer_masks *masks, + const struct landlock_rule **matched_rule) +{ + const struct landlock_rule *rule; + + if (!masks) + return true; + + rule = find_rule(domain, id); + if (!rule) + return false; + + if (matched_rule) + *matched_rule = rule; + + /* + * An access is granted if, for each policy layer, at least one rule + * encountered on the pathwalk grants the requested access, regardless + * of its position in the layer stack. We must then check the remaining + * layers for each inode, from the first added layer to the last one. + * When there are multiple requested accesses, for each policy layer, + * the full set of requested accesses may not be granted by only one + * rule, but by the union (binary OR) of multiple rules. For example, + * /a/b <execute> + /a <read> grants /a/b <execute + read>. + * + * This function is called once per matching rule during the pathwalk, + * progressively clearing bits in @masks. The overall access decision + * is per-layer: access is granted iff masks->layers[l].access == 0 for + * all layers l. When two independent mechanisms can each grant access + * within a layer (e.g. a path rule OR a scope exception), the + * composition must evaluate per-layer: FOR-ALL l (A(l) OR B(l)), not + * (FOR-ALL l A(l)) OR (FOR-ALL l B(l)), to prevent bypass when + * different layers grant via different mechanisms. + */ + for (size_t i = 0; i < rule->num_layers; i++) { + const struct landlock_layer *const layer = &rule->layers[i]; + + /* Clear the bits where the layer in the rule grants access. */ + masks->layers[layer->level - 1].access &= ~layer->access; + +#ifdef CONFIG_SECURITY_LANDLOCK_LOG + /* Collect rule flags for each layer. */ + if (layer->flags.quiet) + masks->layers[layer->level - 1].quiet = true; +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ + } + + for (size_t i = 0; i < ARRAY_SIZE(masks->layers); i++) { + if (masks->layers[i].access) + return false; + } + return true; +} + +typedef access_mask_t +get_access_mask_t(const struct landlock_domain *const domain, + const u16 layer_level); + +/** + * 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. Rule flags + * are also zeroed. + * + * @domain: The domain that defines the current restrictions. + * @access_request: The requested access rights to check. + * @masks: Layer access masks to populate. + * @key_type: The key type to switch between access masks of different types. + * + * Return: An access mask where each access right bit is set which is handled in + * any of the active layers in @domain. + */ +access_mask_t +landlock_init_layer_masks(const struct landlock_domain *const domain, + const access_mask_t access_request, + struct layer_masks *const masks, + const enum landlock_key_type key_type) +{ + access_mask_t handled_accesses = 0; + get_access_mask_t *get_access_mask; + + switch (key_type) { + case LANDLOCK_KEY_INODE: + get_access_mask = landlock_get_fs_access_mask; + break; + +#if IS_ENABLED(CONFIG_INET) + case LANDLOCK_KEY_NET_PORT: + get_access_mask = landlock_get_net_access_mask; + break; +#endif /* IS_ENABLED(CONFIG_INET) */ + + default: + WARN_ON_ONCE(1); + return 0; + } + + /* An empty access request can happen because of O_WRONLY | O_RDWR. */ + if (!access_request) + return 0; + + for (size_t i = 0; i < domain->num_layers; i++) { + const access_mask_t handled = get_access_mask(domain, i); + + masks->layers[i].access = access_request & handled; + handled_accesses |= masks->layers[i].access; +#ifdef CONFIG_SECURITY_LANDLOCK_LOG + masks->layers[i].quiet = false; +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ + } + for (size_t i = domain->num_layers; i < ARRAY_SIZE(masks->layers); + i++) { + masks->layers[i].access = 0; +#ifdef CONFIG_SECURITY_LANDLOCK_LOG + masks->layers[i].quiet = false; +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ + } + + return handled_accesses; +} + +static int merge_tree(struct landlock_domain *const dst, + struct landlock_ruleset *const src, + const enum landlock_key_type key_type) +{ + struct landlock_rule *walker_rule, *next_rule; + struct rb_root *src_root; + int err = 0; + + might_sleep(); + lockdep_assert_held(&src->lock); + + src_root = landlock_get_rule_root(&src->rules, key_type); + if (IS_ERR(src_root)) + return PTR_ERR(src_root); + + /* Merges the @src tree. */ + rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, src_root, + node) { + struct landlock_layer layers[] = { { + .level = dst->num_layers, + } }; + const struct landlock_id id = { + .key = walker_rule->key, + .type = key_type, + }; + + if (WARN_ON_ONCE(walker_rule->num_layers != 1)) + return -EINVAL; + + if (WARN_ON_ONCE(walker_rule->layers[0].level != 0)) + return -EINVAL; + + layers[0].access = walker_rule->layers[0].access; + layers[0].flags = walker_rule->layers[0].flags; + + err = landlock_store_rule(&dst->rules, id, &layers, + ARRAY_SIZE(layers)); + if (err) + return err; + } + return err; +} + +static int merge_ruleset(struct landlock_domain *const dst, + struct landlock_ruleset *const src) +{ + int err = 0; + + might_sleep(); + /* Should already be checked by landlock_merge_ruleset() */ + if (WARN_ON_ONCE(!src)) + return 0; + /* Only merge into a domain. */ + if (WARN_ON_ONCE(!dst || !dst->hierarchy)) + return -EINVAL; + + lockdep_assert_held(&src->lock); + + /* Stacks the new layer. */ + if (WARN_ON_ONCE(dst->num_layers < 1)) + return -EINVAL; + + dst->handled_masks[dst->num_layers - 1] = + landlock_upgrade_handled_access_masks(src->handled_masks); + + /* Merges the @src inode tree. */ + err = merge_tree(dst, src, LANDLOCK_KEY_INODE); + if (err) + return err; + +#if IS_ENABLED(CONFIG_INET) + /* Merges the @src network port tree. */ + err = merge_tree(dst, src, LANDLOCK_KEY_NET_PORT); + if (err) + return err; +#endif /* IS_ENABLED(CONFIG_INET) */ + + return 0; +} + +static int inherit_tree(struct landlock_domain *const parent, + struct landlock_domain *const child, + const enum landlock_key_type key_type) +{ + struct landlock_rule *walker_rule, *next_rule; + struct rb_root *parent_root; + int err = 0; + + might_sleep(); + + parent_root = landlock_get_rule_root(&parent->rules, key_type); + if (IS_ERR(parent_root)) + return PTR_ERR(parent_root); + + /* Copies the @parent inode or network tree. */ + rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, + parent_root, node) { + const struct landlock_id id = { + .key = walker_rule->key, + .type = key_type, + }; + + err = landlock_store_rule(&child->rules, id, + &walker_rule->layers, + walker_rule->num_layers); + if (err) + return err; + } + return err; +} + +static int inherit_ruleset(struct landlock_domain *const parent, + struct landlock_domain *const child) +{ + int err = 0; + + might_sleep(); + if (!parent) + return 0; + + /* Copies the @parent inode tree. */ + err = inherit_tree(parent, child, LANDLOCK_KEY_INODE); + if (err) + return err; + +#if IS_ENABLED(CONFIG_INET) + /* Copies the @parent network port tree. */ + err = inherit_tree(parent, child, LANDLOCK_KEY_NET_PORT); + if (err) + return err; +#endif /* IS_ENABLED(CONFIG_INET) */ + + if (WARN_ON_ONCE(child->num_layers <= parent->num_layers)) + return -EINVAL; + + /* + * Copies the parent layer stack and leaves a space for the new layer. + */ + memcpy(child->handled_masks, parent->handled_masks, + flex_array_size(parent, handled_masks, parent->num_layers)); + + if (WARN_ON_ONCE(!parent->hierarchy)) + return -EINVAL; + + landlock_get_hierarchy(parent->hierarchy); + child->hierarchy->parent = parent->hierarchy; + + return 0; +} + +/** + * landlock_merge_ruleset - Merge a ruleset with a domain + * + * @parent: Parent domain. + * @ruleset: New ruleset to be merged. + * + * The current task is requesting to be restricted. The subjective credentials + * must not be in an overridden state. cf. landlock_init_hierarchy_log(). + * + * The caller must hold @ruleset->lock. + * + * Return: A new domain merging @parent and @ruleset on success, or ERR_PTR() on + * failure. If @parent is NULL, the new domain duplicates @ruleset. + */ +struct landlock_domain * +landlock_merge_ruleset(struct landlock_domain *const parent, + struct landlock_ruleset *const ruleset) +{ + struct landlock_domain *new_dom __free(landlock_put_domain) = NULL; + u32 num_layers; + int err; + + might_sleep(); + lockdep_assert_held(&ruleset->lock); + if (WARN_ON_ONCE(!ruleset)) + return ERR_PTR(-EINVAL); + + if (parent) { + if (parent->num_layers >= LANDLOCK_MAX_NUM_LAYERS) + return ERR_PTR(-E2BIG); + num_layers = parent->num_layers + 1; + } else { + num_layers = 1; + } + + /* Creates a new domain... */ + new_dom = create_domain(num_layers); + if (IS_ERR(new_dom)) + return new_dom; + + new_dom->hierarchy = + kzalloc_obj(*new_dom->hierarchy, GFP_KERNEL_ACCOUNT); + if (!new_dom->hierarchy) + return ERR_PTR(-ENOMEM); + + refcount_set(&new_dom->hierarchy->usage, 1); + + /* ...as a child of @parent... */ + err = inherit_ruleset(parent, new_dom); + if (err) + return ERR_PTR(err); + + /* ...and including @ruleset. */ + err = merge_ruleset(new_dom, ruleset); + if (err) + return ERR_PTR(err); + + err = landlock_init_hierarchy_log(new_dom->hierarchy); + if (err) + return ERR_PTR(err); + +#ifdef CONFIG_SECURITY_LANDLOCK_LOG + new_dom->hierarchy->quiet_masks = ruleset->quiet_masks; +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ + + return no_free_ptr(new_dom); +} + +#ifdef CONFIG_SECURITY_LANDLOCK_LOG + +/** + * get_current_exe - Get the current's executable path, if any + * + * @exe_str: Returned pointer to a path string with a lifetime tied to the + * returned buffer, if any. + * @exe_size: Returned size of @exe_str (including the trailing null + * character), if any. + * + * Return: A pointer to an allocated buffer where @exe_str point to, %NULL if + * there is no executable path, or an error otherwise. + */ +static const void *get_current_exe(const char **const exe_str, + size_t *const exe_size) +{ + const size_t buffer_size = LANDLOCK_PATH_MAX_SIZE; + struct mm_struct *mm = current->mm; + struct file *file __free(fput) = NULL; + char *buffer __free(kfree) = NULL; + const char *exe; + ssize_t size; + + if (!mm) + return NULL; + + file = get_mm_exe_file(mm); + if (!file) + return NULL; + + buffer = kmalloc(buffer_size, GFP_KERNEL); + if (!buffer) + return ERR_PTR(-ENOMEM); + + exe = d_path(&file->f_path, buffer, buffer_size); + if (WARN_ON_ONCE(IS_ERR(exe))) + /* Should never happen according to LANDLOCK_PATH_MAX_SIZE. */ + return ERR_CAST(exe); + + size = buffer + buffer_size - exe; + if (WARN_ON_ONCE(size <= 0)) + return ERR_PTR(-ENAMETOOLONG); + + *exe_size = size; + *exe_str = exe; + return no_free_ptr(buffer); +} + +/* + * Return: A newly allocated object describing a domain, or an error + * otherwise. + */ +static struct landlock_details *get_current_details(void) +{ + /* Cf. audit_log_d_path_exe() */ + static const char null_path[] = "(null)"; + const char *path_str = null_path; + size_t path_size = sizeof(null_path); + const void *buffer __free(kfree) = NULL; + struct landlock_details *details; + + buffer = get_current_exe(&path_str, &path_size); + if (IS_ERR(buffer)) + return ERR_CAST(buffer); + + /* + * 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, GFP_KERNEL_ACCOUNT); + if (!details) + return ERR_PTR(-ENOMEM); + + memcpy(details->exe_path, path_str, path_size); + details->pid = get_pid(task_tgid(current)); + details->uid = from_kuid(&init_user_ns, current_uid()); + get_task_comm(details->comm, current); + return details; +} + +/** + * landlock_init_hierarchy_log - Partially initialize landlock_hierarchy + * + * @hierarchy: The hierarchy to initialize. + * + * The current task is referenced as the domain that is enforcing the + * restriction. The subjective credentials must not be in an overridden state. + * + * @hierarchy->parent and @hierarchy->usage should already be set. + * + * Return: 0 on success, -errno on failure. + */ +int landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy) +{ + struct landlock_details *details; + + details = get_current_details(); + if (IS_ERR(details)) + return PTR_ERR(details); + + hierarchy->details = details; + hierarchy->id = landlock_get_id_range(1); + /* + * The hierarchy is born unobservable: landlock_restrict_self() moves it + * out of LANDLOCK_LOG_UNCOMMITTED once it has emitted the creation + * event, so the matching free_domain event fires for it and not for a + * hierarchy whose creation was never observed. + */ + hierarchy->log_status = LANDLOCK_LOG_UNCOMMITTED; + hierarchy->log_same_exec = true; + hierarchy->log_new_exec = false; + atomic64_set(&hierarchy->num_denials, 0); + return 0; +} + +static deny_masks_t +get_layer_deny_mask(const access_mask_t all_existing_optional_access, + const unsigned long access_bit, const size_t layer) +{ + unsigned long access_weight; + + /* This may require change with new object types. */ + WARN_ON_ONCE(all_existing_optional_access != + _LANDLOCK_ACCESS_FS_OPTIONAL); + + if (WARN_ON_ONCE(layer >= LANDLOCK_MAX_NUM_LAYERS)) + return 0; + + access_weight = hweight_long(all_existing_optional_access & + GENMASK(access_bit, 0)); + if (WARN_ON_ONCE(access_weight < 1)) + return 0; + + return layer + << ((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) +{ + const unsigned long truncate = BIT_INDEX(LANDLOCK_ACCESS_FS_TRUNCATE); + const unsigned long ioctl_dev = BIT_INDEX(LANDLOCK_ACCESS_FS_IOCTL_DEV); + + KUNIT_EXPECT_EQ(test, 0, + get_layer_deny_mask(_LANDLOCK_ACCESS_FS_OPTIONAL, + truncate, 0)); + KUNIT_EXPECT_EQ(test, 0x3, + get_layer_deny_mask(_LANDLOCK_ACCESS_FS_OPTIONAL, + truncate, 3)); + + KUNIT_EXPECT_EQ(test, 0, + get_layer_deny_mask(_LANDLOCK_ACCESS_FS_OPTIONAL, + ioctl_dev, 0)); + KUNIT_EXPECT_EQ(test, 0xf0, + get_layer_deny_mask(_LANDLOCK_ACCESS_FS_OPTIONAL, + ioctl_dev, 15)); +} + +#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_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_masks *const masks) +{ + const unsigned long access_opt = optional_access; + unsigned long access_bit; + deny_masks_t deny_masks = 0; + access_mask_t all_denied = 0; + + /* This may require change with new object types. */ + WARN_ON_ONCE(!access_mask_subset(optional_access, + all_existing_optional_access)); + + if (WARN_ON_ONCE(!masks)) + return 0; + + if (WARN_ON_ONCE(!access_opt)) + return 0; + + 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) + continue; + + for_each_set_bit(access_bit, &newly_denied, + 8 * sizeof(access_mask_t)) { + deny_masks |= get_layer_deny_mask( + all_existing_optional_access, access_bit, i); + } + all_denied |= denied; + } + return deny_masks; +} + +#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST + +static void test_landlock_get_deny_masks(struct kunit *const test) +{ + 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, + landlock_get_deny_masks(_LANDLOCK_ACCESS_FS_OPTIONAL, + LANDLOCK_ACCESS_FS_TRUNCATE, + &layers1)); + KUNIT_EXPECT_EQ(test, 0x20, + landlock_get_deny_masks(_LANDLOCK_ACCESS_FS_OPTIONAL, + LANDLOCK_ACCESS_FS_IOCTL_DEV, + &layers1)); + KUNIT_EXPECT_EQ( + test, 0x21, + landlock_get_deny_masks(_LANDLOCK_ACCESS_FS_OPTIONAL, + LANDLOCK_ACCESS_FS_TRUNCATE | + LANDLOCK_ACCESS_FS_IOCTL_DEV, + &layers1)); +} + +#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ + +#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST + +static struct kunit_case test_cases[] = { + /* clang-format off */ + KUNIT_CASE(test_get_layer_deny_mask), + KUNIT_CASE(test_landlock_get_deny_masks), + {} + /* clang-format on */ +}; + +static struct kunit_suite test_suite = { + .name = "landlock_domain", + .test_cases = test_cases, +}; + +kunit_test_suite(test_suite); + +#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ + +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ |
