// SPDX-License-Identifier: GPL-2.0 /* * Landlock tests - Namespace restriction * * Copyright © 2026 Cloudflare, Inc. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include /* Defines AT_RECURSIVE without type conflicts. */ #define _ASM_GENERIC_FCNTL_H #include #include "audit.h" #include "common.h" #include "trace.h" #define TRACE_TASK "ns_test" /* * Max length for /proc/self/ns/ paths (longest: "/proc/self/ns/cgroup"). */ #define NS_PROC_PATH_MAX 32 static int create_ns_ruleset(void) { const struct landlock_ruleset_attr attr = { .handled_permissions = LANDLOCK_PERMISSION_NAMESPACE_USE, }; return landlock_create_ruleset(&attr, sizeof(attr), 0); } static int add_ns_rule(int ruleset_fd, __u64 ns_type) { const struct landlock_namespace_attr attr = { .permissions = LANDLOCK_PERMISSION_NAMESPACE_USE, .allowed_namespace_types = ns_type, }; return landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &attr, 0); } static int add_ns_rule_full(int ruleset_fd, __u64 allowed, __u64 quiet) { const struct landlock_namespace_attr attr = { .permissions = LANDLOCK_PERMISSION_NAMESPACE_USE, .allowed_namespace_types = allowed, .quiet_namespace_types = quiet, }; return landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &attr, 0); } /* * Returns the /proc/self/NS entry name for a given CLONE_NEW* type, or NULL if * unknown. Used to check kernel support without side effects. */ static const char *ns_proc_name(__u64 ns_type) { switch (ns_type) { case CLONE_NEWNS: return "mnt"; case CLONE_NEWCGROUP: return "cgroup"; case CLONE_NEWUTS: return "uts"; case CLONE_NEWIPC: return "ipc"; case CLONE_NEWUSER: return "user"; case CLONE_NEWPID: return "pid"; case CLONE_NEWNET: return "net"; case CLONE_NEWTIME: return "time"; default: return NULL; } } static bool ns_is_supported(__u64 ns_type, char *proc_path, size_t size) { const char *ns_name; ns_name = ns_proc_name(ns_type); if (!ns_name) return false; snprintf(proc_path, size, "/proc/self/ns/%s", ns_name); return access(proc_path, F_OK) == 0; } /* Rule validation tests */ TEST(add_rule_bad_attr) { const struct landlock_ruleset_attr cap_only_attr = { .handled_permissions = LANDLOCK_PERMISSION_CAPABILITY_USE, }; int ruleset_fd; struct landlock_namespace_attr attr = {}; ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); /* Invalid rule pointer returns EFAULT. */ ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, NULL, 0)); ASSERT_EQ(EFAULT, errno); /* Empty permissions selector returns ENOMSG. */ attr.permissions = 0; attr.allowed_namespace_types = CLONE_NEWUTS; ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &attr, 0)); ASSERT_EQ(ENOMSG, errno); /* Valid namespace selector plus an extra unhandled selector bit. */ attr.permissions = LANDLOCK_PERMISSION_NAMESPACE_USE | LANDLOCK_PERMISSION_CAPABILITY_USE; attr.allowed_namespace_types = CLONE_NEWUTS; ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &attr, 0)); ASSERT_EQ(EINVAL, errno); /* permissions with wrong type. */ attr.permissions = LANDLOCK_PERMISSION_CAPABILITY_USE; attr.allowed_namespace_types = CLONE_NEWUTS; ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &attr, 0)); ASSERT_EQ(EINVAL, errno); /* * Unknown namespace bits (e.g. bit 63) are silently accepted for * forward compatibility. Only known CLONE_NEW* bits are stored. */ attr.permissions = LANDLOCK_PERMISSION_NAMESPACE_USE; attr.allowed_namespace_types = 1ULL << 63; ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &attr, 0)); /* Useless rule: neither allowed nor quiet types set returns ENOMSG. */ attr.permissions = LANDLOCK_PERMISSION_NAMESPACE_USE; attr.allowed_namespace_types = 0; attr.quiet_namespace_types = 0; ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &attr, 0)); ASSERT_EQ(ENOMSG, errno); /* Quiet-only rule (empty allowed set) is legal. */ attr.permissions = LANDLOCK_PERMISSION_NAMESPACE_USE; attr.allowed_namespace_types = 0; attr.quiet_namespace_types = CLONE_NEWUTS; ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &attr, 0)); /* Allow and quiet in the same rule. */ attr.permissions = LANDLOCK_PERMISSION_NAMESPACE_USE; attr.allowed_namespace_types = CLONE_NEWNET; attr.quiet_namespace_types = CLONE_NEWUTS; ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &attr, 0)); attr.quiet_namespace_types = 0; /* * Bit 1 is not a CLONE_NEW* value but is silently accepted for forward * compatibility (no hole rejection). */ attr.permissions = LANDLOCK_PERMISSION_NAMESPACE_USE; attr.allowed_namespace_types = (1ULL << 1); ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &attr, 0)); /* Multi-bit values are valid (bitmask allows multiple types). */ attr.permissions = LANDLOCK_PERMISSION_NAMESPACE_USE; attr.allowed_namespace_types = CLONE_NEWUTS | CLONE_NEWNET; ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &attr, 0)); /* * LANDLOCK_ADD_RULE_QUIET (and any other flag) is filesystem/network * only and must be rejected for namespace rules, even when every attr * field is otherwise valid. */ attr.permissions = LANDLOCK_PERMISSION_NAMESPACE_USE; attr.allowed_namespace_types = CLONE_NEWUTS; ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &attr, LANDLOCK_ADD_RULE_QUIET)); ASSERT_EQ(EINVAL, errno); EXPECT_EQ(0, close(ruleset_fd)); /* * Ruleset handles LANDLOCK_PERMISSION_CAPABILITY_USE but not * LANDLOCK_PERMISSION_NAMESPACE_USE: adding a namespace rule must be * rejected. */ ruleset_fd = landlock_create_ruleset(&cap_only_attr, sizeof(cap_only_attr), 0); ASSERT_LE(0, ruleset_fd); attr.permissions = LANDLOCK_PERMISSION_NAMESPACE_USE; attr.allowed_namespace_types = CLONE_NEWUTS; ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &attr, 0)); ASSERT_EQ(EINVAL, errno); EXPECT_EQ(0, close(ruleset_fd)); } /* * Unknown namespace types in the upper range are silently accepted (allow-list: * they have no effect since the kernel never checks them). */ TEST(add_rule_unknown) { int ruleset_fd; struct landlock_namespace_attr attr = { .permissions = LANDLOCK_PERMISSION_NAMESPACE_USE, }; ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); /* * Bit 31 is in the lower 32 bits but not a CLONE_NEW* value. Silently * accepted for forward compatibility (no hole rejection). */ attr.allowed_namespace_types = 1ULL << 31; ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &attr, 0)); /* Bit 32 is in the unknown upper range: silently accepted. */ attr.allowed_namespace_types = 1ULL << 32; ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &attr, 0)); EXPECT_EQ(0, close(ruleset_fd)); } /* * A rule that lists only namespace bits unknown to the running kernel is * accepted by landlock_add_rule() but has no runtime effect: once the domain is * enforced, any actual CLONE_NEW* operation is still denied by the per-category * deny-by-default behaviour. This documents the forward-compatibility * contract: unknown bits are silently accepted so the same policy can be loaded * across kernels, but they never grant a permission that the running kernel * knows nothing about. */ TEST(add_rule_unknown_no_runtime_effect) { const struct landlock_ruleset_attr ruleset_attr = { .handled_permissions = LANDLOCK_PERMISSION_NAMESPACE_USE, }; struct landlock_namespace_attr attr = { .permissions = LANDLOCK_PERMISSION_NAMESPACE_USE, /* Only unknown bits: bit 31 (in lower 32) and bit 32. */ .allowed_namespace_types = (1ULL << 31) | (1ULL << 32), }; int ruleset_fd; disable_caps(_metadata); ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); ASSERT_LE(0, ruleset_fd); ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &attr, 0)); /* * Baseline: absent the domain, creating CLONE_NEWUTS succeeds. This * ensures the EPERM below is attributable to Landlock rather than to a * missing capability, and fails if the hook always allows. */ set_cap(_metadata, CAP_SYS_ADMIN); ASSERT_EQ(0, unshare(CLONE_NEWUTS)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); /* * CLONE_NEWUTS is a real, known CLONE_NEW* type but was not authorised * by the rule above; deny-by-default applies. */ EXPECT_EQ(-1, unshare(CLONE_NEWUTS)); EXPECT_EQ(EPERM, errno); clear_cap(_metadata, CAP_SYS_ADMIN); } /* * The install-hook mirror of add_rule_unknown_no_runtime_effect: a rule listing * only unknown namespace bits has no runtime effect on setns(2) either. Once * enforced, entering a known namespace type is still denied by the per-category * deny-by-default behaviour. */ TEST(add_rule_unknown_no_runtime_effect_setns) { const struct landlock_ruleset_attr ruleset_attr = { .handled_permissions = LANDLOCK_PERMISSION_NAMESPACE_USE, }; struct landlock_namespace_attr attr = { .permissions = LANDLOCK_PERMISSION_NAMESPACE_USE, /* Only unknown bits: bit 31 (in lower 32) and bit 32. */ .allowed_namespace_types = (1ULL << 31) | (1ULL << 32), }; int ruleset_fd, ns_fd; disable_caps(_metadata); /* Open the NS FD before enforcing the domain. */ ns_fd = open("/proc/self/ns/uts", O_RDONLY); ASSERT_LE(0, ns_fd); ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); ASSERT_LE(0, ruleset_fd); ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &attr, 0)); /* * Baseline: absent the domain, entering the process's own UTS namespace * succeeds, so the later EPERM is attributable to Landlock rather than * to a missing capability, and fails if the hook always allows. */ set_cap(_metadata, CAP_SYS_ADMIN); ASSERT_EQ(0, setns(ns_fd, CLONE_NEWUTS)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); /* * CLONE_NEWUTS is a real, known CLONE_NEW* type but was not authorised * by the rule above; deny-by-default applies at the install hook. */ EXPECT_EQ(-1, setns(ns_fd, CLONE_NEWUTS)); EXPECT_EQ(EPERM, errno); clear_cap(_metadata, CAP_SYS_ADMIN); EXPECT_EQ(0, close(ns_fd)); } /* Namespace creation tests (variant-based positive/negative) */ /* clang-format off */ FIXTURE(ns_create) { /* clang-format on */ char proc_path[NS_PROC_PATH_MAX]; }; FIXTURE_VARIANT(ns_create) { const __u64 namespace_types; const bool is_sandboxed; const bool has_rule; const bool drop_all_caps; const int expected_result; }; /* * Unsandboxed baseline: no Landlock domain is enforced. User namespace creation * should succeed without any restriction. */ /* clang-format off */ FIXTURE_VARIANT_ADD(ns_create, user_unsandboxed) { /* clang-format on */ .namespace_types = CLONE_NEWUSER, .is_sandboxed = false, .has_rule = false, .drop_all_caps = false, .expected_result = 0, }; /* * User namespace creation denied: handled by Landlock but no rule allows * CLONE_NEWUSER. */ /* clang-format off */ FIXTURE_VARIANT_ADD(ns_create, user_denied) { /* clang-format on */ .namespace_types = CLONE_NEWUSER, .is_sandboxed = true, .has_rule = false, .drop_all_caps = false, .expected_result = EPERM, }; /* User namespace creation allowed: Landlock rule permits CLONE_NEWUSER. */ /* clang-format off */ FIXTURE_VARIANT_ADD(ns_create, user_allowed) { /* clang-format on */ .namespace_types = CLONE_NEWUSER, .is_sandboxed = true, .has_rule = true, .drop_all_caps = false, .expected_result = 0, }; /* * User namespace creation while unprivileged: the process has no capabilities * but unshare(CLONE_NEWUSER) is an unprivileged operation so it still succeeds. * The Landlock rule allows it. For setns, the capability check (CAP_SYS_ADMIN) * fails first since the process has no capabilities, yielding EPERM. */ /* clang-format off */ FIXTURE_VARIANT_ADD(ns_create, user_unprivileged) { /* clang-format on */ .namespace_types = CLONE_NEWUSER, .is_sandboxed = true, .has_rule = true, .drop_all_caps = true, .expected_result = 0, }; /* * Unsandboxed baseline for non-user namespace: no Landlock domain, process has * CAP_SYS_ADMIN. UTS creation should succeed. */ /* clang-format off */ FIXTURE_VARIANT_ADD(ns_create, uts_unsandboxed) { /* clang-format on */ .namespace_types = CLONE_NEWUTS, .is_sandboxed = false, .has_rule = false, .drop_all_caps = false, .expected_result = 0, }; /* * Non-user namespace denied: process has CAP_SYS_ADMIN (passes ns_capable), but * Landlock denies (no rule). */ /* clang-format off */ FIXTURE_VARIANT_ADD(ns_create, uts_denied) { /* clang-format on */ .namespace_types = CLONE_NEWUTS, .is_sandboxed = true, .has_rule = false, .drop_all_caps = false, .expected_result = EPERM, }; /* * Non-user namespace allowed: process has CAP_SYS_ADMIN and Landlock rule * permits CLONE_NEWUTS. */ /* clang-format off */ FIXTURE_VARIANT_ADD(ns_create, uts_allowed) { .namespace_types = CLONE_NEWUTS, .is_sandboxed = true, .has_rule = true, .drop_all_caps = false, .expected_result = 0, }; /* clang-format on */ /* * Unprivileged namespace creation: process lacks CAP_SYS_ADMIN, so the kernel * denies creation regardless of Landlock rules. Landlock cannot authorize what * the kernel denied (LSM hooks are restriction-only). The rule is present to * verify Landlock does not change the error code. */ /* clang-format off */ FIXTURE_VARIANT_ADD(ns_create, uts_unprivileged) { /* clang-format on */ .namespace_types = CLONE_NEWUTS, .is_sandboxed = true, .has_rule = true, .drop_all_caps = true, .expected_result = EPERM, }; /* clang-format off */ FIXTURE_VARIANT_ADD(ns_create, ipc_denied) { /* clang-format on */ .namespace_types = CLONE_NEWIPC, .is_sandboxed = true, .has_rule = false, .drop_all_caps = false, .expected_result = EPERM, }; /* clang-format off */ FIXTURE_VARIANT_ADD(ns_create, ipc_allowed) { .namespace_types = CLONE_NEWIPC, .is_sandboxed = true, .has_rule = true, .drop_all_caps = false, .expected_result = 0, }; /* clang-format on */ /* clang-format off */ FIXTURE_VARIANT_ADD(ns_create, ipc_unprivileged) { /* clang-format on */ .namespace_types = CLONE_NEWIPC, .is_sandboxed = true, .has_rule = true, .drop_all_caps = true, .expected_result = EPERM, }; /* clang-format off */ FIXTURE_VARIANT_ADD(ns_create, mnt_denied) { /* clang-format on */ .namespace_types = CLONE_NEWNS, .is_sandboxed = true, .has_rule = false, .drop_all_caps = false, .expected_result = EPERM, }; /* clang-format off */ FIXTURE_VARIANT_ADD(ns_create, mnt_allowed) { .namespace_types = CLONE_NEWNS, .is_sandboxed = true, .has_rule = true, .drop_all_caps = false, .expected_result = 0, }; /* clang-format on */ /* clang-format off */ FIXTURE_VARIANT_ADD(ns_create, mnt_unprivileged) { /* clang-format on */ .namespace_types = CLONE_NEWNS, .is_sandboxed = true, .has_rule = true, .drop_all_caps = true, .expected_result = EPERM, }; /* clang-format off */ FIXTURE_VARIANT_ADD(ns_create, cgroup_denied) { /* clang-format on */ .namespace_types = CLONE_NEWCGROUP, .is_sandboxed = true, .has_rule = false, .drop_all_caps = false, .expected_result = EPERM, }; /* clang-format off */ FIXTURE_VARIANT_ADD(ns_create, cgroup_allowed) { /* clang-format on */ .namespace_types = CLONE_NEWCGROUP, .is_sandboxed = true, .has_rule = true, .drop_all_caps = false, .expected_result = 0, }; /* clang-format off */ FIXTURE_VARIANT_ADD(ns_create, cgroup_unprivileged) { /* clang-format on */ .namespace_types = CLONE_NEWCGROUP, .is_sandboxed = true, .has_rule = true, .drop_all_caps = true, .expected_result = EPERM, }; /* clang-format off */ FIXTURE_VARIANT_ADD(ns_create, pid_denied) { /* clang-format on */ .namespace_types = CLONE_NEWPID, .is_sandboxed = true, .has_rule = false, .drop_all_caps = false, .expected_result = EPERM, }; /* clang-format off */ FIXTURE_VARIANT_ADD(ns_create, pid_allowed) { .namespace_types = CLONE_NEWPID, .is_sandboxed = true, .has_rule = true, .drop_all_caps = false, .expected_result = 0, }; /* clang-format on */ /* clang-format off */ FIXTURE_VARIANT_ADD(ns_create, pid_unprivileged) { /* clang-format on */ .namespace_types = CLONE_NEWPID, .is_sandboxed = true, .has_rule = true, .drop_all_caps = true, .expected_result = EPERM, }; /* clang-format off */ FIXTURE_VARIANT_ADD(ns_create, net_denied) { /* clang-format on */ .namespace_types = CLONE_NEWNET, .is_sandboxed = true, .has_rule = false, .drop_all_caps = false, .expected_result = EPERM, }; /* clang-format off */ FIXTURE_VARIANT_ADD(ns_create, net_allowed) { .namespace_types = CLONE_NEWNET, .is_sandboxed = true, .has_rule = true, .drop_all_caps = false, .expected_result = 0, }; /* clang-format on */ /* clang-format off */ FIXTURE_VARIANT_ADD(ns_create, net_unprivileged) { /* clang-format on */ .namespace_types = CLONE_NEWNET, .is_sandboxed = true, .has_rule = true, .drop_all_caps = true, .expected_result = EPERM, }; /* clang-format off */ FIXTURE_VARIANT_ADD(ns_create, time_denied) { /* clang-format on */ .namespace_types = CLONE_NEWTIME, .is_sandboxed = true, .has_rule = false, .drop_all_caps = false, .expected_result = EPERM, }; /* clang-format off */ FIXTURE_VARIANT_ADD(ns_create, time_allowed) { /* clang-format on */ .namespace_types = CLONE_NEWTIME, .is_sandboxed = true, .has_rule = true, .drop_all_caps = false, .expected_result = 0, }; /* clang-format off */ FIXTURE_VARIANT_ADD(ns_create, time_unprivileged) { /* clang-format on */ .namespace_types = CLONE_NEWTIME, .is_sandboxed = true, .has_rule = true, .drop_all_caps = true, .expected_result = EPERM, }; FIXTURE_SETUP(ns_create) { ASSERT_TRUE(ns_is_supported(variant->namespace_types, self->proc_path, sizeof(self->proc_path))) { TH_LOG("Namespace type 0x%llx not supported", (unsigned long long)variant->namespace_types); } if (variant->drop_all_caps) drop_caps(_metadata); else disable_caps(_metadata); } FIXTURE_TEARDOWN(ns_create) { } TEST_F(ns_create, unshare) { int ruleset_fd, err; if (variant->is_sandboxed) { ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); if (variant->has_rule) ASSERT_EQ(0, add_ns_rule(ruleset_fd, variant->namespace_types)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); } /* * Non-user namespaces need CAP_SYS_ADMIN for the privileged path. User * namespaces and unprivileged tests skip this. */ if (!variant->drop_all_caps && variant->namespace_types != CLONE_NEWUSER) set_cap(_metadata, CAP_SYS_ADMIN); err = unshare(variant->namespace_types); if (variant->expected_result) { EXPECT_EQ(-1, err); EXPECT_EQ(variant->expected_result, errno); } else { EXPECT_EQ(0, err); } if (!variant->drop_all_caps && variant->namespace_types != CLONE_NEWUSER) clear_cap(_metadata, CAP_SYS_ADMIN); } /* * clone3 exercises a different kernel entry point than unshare: it goes through * kernel_clone() -> copy_process() -> copy_namespaces() -> * create_new_namespaces(). Both paths converge at __ns_common_init() -> * security_namespace_init(), but the entry point and argument handling differ. */ TEST_F(ns_create, clone3) { int ruleset_fd, status; pid_t pid; struct clone_args args = {}; if (variant->is_sandboxed) { ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); if (variant->has_rule) ASSERT_EQ(0, add_ns_rule(ruleset_fd, variant->namespace_types)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); } if (!variant->drop_all_caps && variant->namespace_types != CLONE_NEWUSER) set_cap(_metadata, CAP_SYS_ADMIN); args.flags = variant->namespace_types; args.exit_signal = SIGCHLD; pid = sys_clone3(&args, sizeof(args)); if (pid == 0) _exit(EXIT_SUCCESS); if (variant->expected_result) { EXPECT_EQ(-1, pid); EXPECT_EQ(variant->expected_result, errno); } else { ASSERT_LE(0, pid); ASSERT_EQ(pid, waitpid(pid, &status, 0)); ASSERT_EQ(1, WIFEXITED(status)); ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); } if (!variant->drop_all_caps && variant->namespace_types != CLONE_NEWUSER) clear_cap(_metadata, CAP_SYS_ADMIN); } /* * setns exercises the namespace install path: validate_ns() -> * security_namespace_install() -> hook_namespace_install(). This is a * different LSM hook than creation, so it must be tested separately for each * type. * * Mount namespace setns requires both CAP_SYS_ADMIN and CAP_SYS_CHROOT (checked * by mntns_install), so the allowed variant sets both. */ TEST_F(ns_create, setns) { int ruleset_fd, ns_fd, err, expected; /* * setns into the process's own user NS returns EINVAL from * userns_install() (rejects re-entry), but when Landlock denies the * operation, security_namespace_install() returns EPERM before * userns_install() runs. */ if (variant->namespace_types == CLONE_NEWUSER && !variant->expected_result) { expected = EINVAL; } else { expected = variant->expected_result; } /* Open the NS FD before enforcing the domain. */ ns_fd = open(self->proc_path, O_RDONLY); ASSERT_LE(0, ns_fd); if (variant->is_sandboxed) { ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); if (variant->has_rule) ASSERT_EQ(0, add_ns_rule(ruleset_fd, variant->namespace_types)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); } if (!variant->drop_all_caps) { set_cap(_metadata, CAP_SYS_ADMIN); /* * mntns_install() requires CAP_SYS_CHROOT in addition to * CAP_SYS_ADMIN. */ if (variant->namespace_types == CLONE_NEWNS) set_cap(_metadata, CAP_SYS_CHROOT); } err = setns(ns_fd, variant->namespace_types); if (expected) { EXPECT_EQ(-1, err); EXPECT_EQ(expected, errno); } else { EXPECT_EQ(0, err); } if (!variant->drop_all_caps) { clear_cap(_metadata, CAP_SYS_ADMIN); if (variant->namespace_types == CLONE_NEWNS) clear_cap(_metadata, CAP_SYS_CHROOT); } EXPECT_EQ(0, close(ns_fd)); } /* Additional namespace creation tests */ /* * When LANDLOCK_PERMISSION_NAMESPACE_USE is not handled by any domain, * namespace creation must produce the same result as without Landlock. Unlike * the unsandboxed variants of ns_create (which have no domain at all), this * test verifies that a domain handling only FS access does not interfere with * namespace operations. */ TEST(ns_create_unhandled) { const struct landlock_ruleset_attr attr = { .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, }; int ruleset_fd; disable_caps(_metadata); ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0); ASSERT_LE(0, ruleset_fd); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); /* User namespace creation should still work (unhandled). */ EXPECT_EQ(0, unshare(CLONE_NEWUSER)); } /* * Layer stacking: both layers must allow CLONE_NEWUSER for the operation to * succeed. Variants exercise the three combinations of per-layer allow/deny * that exercise distinct semantics; the (deny, deny) combination is omitted * because it is covered by every other "deny" test in this file. */ /* clang-format off */ FIXTURE(ns_stacking) {}; /* clang-format on */ FIXTURE_VARIANT(ns_stacking) { bool first_layer_allows; bool second_layer_allows; }; /* Layer 1 allows, layer 2 denies -> child denies. */ /* clang-format off */ FIXTURE_VARIANT_ADD(ns_stacking, deny) { /* clang-format on */ .first_layer_allows = true, .second_layer_allows = false, }; /* Both layers allow CLONE_NEWUSER -> operation succeeds. */ /* clang-format off */ FIXTURE_VARIANT_ADD(ns_stacking, allow) { /* clang-format on */ .first_layer_allows = true, .second_layer_allows = true, }; /* * Layer 1 denies, layer 2 allows -> still denied: a child layer cannot grant * what an ancestor layer withheld. This complements the * parent-allows/child-denies variant above; together they verify the walker * checks both layers and accepts only the (allow, allow) cell. */ /* clang-format off */ FIXTURE_VARIANT_ADD(ns_stacking, parent_denies) { /* clang-format on */ .first_layer_allows = false, .second_layer_allows = true, }; FIXTURE_SETUP(ns_stacking) { disable_caps(_metadata); } FIXTURE_TEARDOWN(ns_stacking) { } /* * Verify that any layer can deny an operation: enforcement requires all layers * to allow. Variants exercise the three combinations that exercise distinct * walker paths (allow/deny, allow/allow, deny/allow); only allow/allow lets the * operation through. */ TEST_F(ns_stacking, two_layers) { int ruleset_fd; const bool expect_success = variant->first_layer_allows && variant->second_layer_allows; /* First layer: allow or deny depending on variant. */ ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); if (variant->first_layer_allows) ASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWUSER)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); /* Second layer: allow or deny depending on variant. */ ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); if (variant->second_layer_allows) ASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWUSER)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); if (expect_success) { EXPECT_EQ(0, unshare(CLONE_NEWUSER)); } else { EXPECT_EQ(-1, unshare(CLONE_NEWUSER)); EXPECT_EQ(EPERM, errno); } } /* * Combined capability and namespace permissions in a single domain. Verifies * that both permission types can coexist and are enforced independently. */ TEST(combined_cap_ns) { const struct landlock_ruleset_attr attr = { .handled_permissions = LANDLOCK_PERMISSION_CAPABILITY_USE | LANDLOCK_PERMISSION_NAMESPACE_USE, }; const struct landlock_capability_attr cap_attr = { .permissions = LANDLOCK_PERMISSION_CAPABILITY_USE, .allowed_capabilities = (1ULL << CAP_SYS_ADMIN), }; const struct landlock_namespace_attr ns_attr = { .permissions = LANDLOCK_PERMISSION_NAMESPACE_USE, .allowed_namespace_types = CLONE_NEWUSER, }; int ruleset_fd; /* Isolate hostname changes from other tests. */ ASSERT_EQ(0, unshare(CLONE_NEWUTS)); disable_caps(_metadata); ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0); ASSERT_LE(0, ruleset_fd); ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY, &cap_attr, 0)); ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &ns_attr, 0)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); /* CAP_SYS_ADMIN use allowed by capability rule. */ set_cap(_metadata, CAP_SYS_ADMIN); EXPECT_EQ(0, sethostname("test", 4)); clear_cap(_metadata, CAP_SYS_ADMIN); /* CAP_SYS_CHROOT denied (not in allowed capability rules). */ set_cap(_metadata, CAP_SYS_CHROOT); EXPECT_EQ(-1, chroot("/")); EXPECT_EQ(EPERM, errno); clear_cap(_metadata, CAP_SYS_CHROOT); /* * UTS namespace creation denied by Landlock (not in allowed namespace * rules). CAP_SYS_ADMIN is needed for the kernel's ns_capable() check * to pass, so that Landlock's hook is actually reached. */ set_cap(_metadata, CAP_SYS_ADMIN); EXPECT_EQ(-1, unshare(CLONE_NEWUTS)); EXPECT_EQ(EPERM, errno); clear_cap(_metadata, CAP_SYS_ADMIN); /* User namespace creation allowed by namespace rule. */ EXPECT_EQ(0, unshare(CLONE_NEWUSER)); } /* * Partial allow: one namespace type is allowed, another is denied. Verifies * that rules are per-type. */ TEST(ns_create_partial) { int ruleset_fd; disable_caps(_metadata); ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); /* Only allow UTS namespace creation. */ ASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWUTS)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); /* UTS namespace should be allowed. */ set_cap(_metadata, CAP_SYS_ADMIN); EXPECT_EQ(0, unshare(CLONE_NEWUTS)); clear_cap(_metadata, CAP_SYS_ADMIN); /* User namespace should be denied (no rule). */ EXPECT_EQ(-1, unshare(CLONE_NEWUSER)); EXPECT_EQ(EPERM, errno); } /* * open_tree(2) and fsmount(2) acquire a file descriptor referring to a * newly-created mount namespace. Both call paths funnel into * security_namespace_init() with CLONE_NEWNS, gated by * LANDLOCK_PERMISSION_NAMESPACE_USE. Without coverage here, regressions in * those paths would slip past the suite. */ /* clang-format off */ FIXTURE(ns_mount_fd) {}; /* clang-format on */ FIXTURE_VARIANT(ns_mount_fd) { bool sandboxed; bool has_rule; int expected_errno; }; /* clang-format off */ FIXTURE_VARIANT_ADD(ns_mount_fd, denied) { /* clang-format on */ .sandboxed = true, .has_rule = false, .expected_errno = EPERM, }; /* clang-format off */ FIXTURE_VARIANT_ADD(ns_mount_fd, allowed) { /* clang-format on */ .sandboxed = true, .has_rule = true, .expected_errno = 0, }; /* clang-format off */ FIXTURE_VARIANT_ADD(ns_mount_fd, unsandboxed) { /* clang-format on */ .sandboxed = false, .has_rule = false, .expected_errno = 0, }; FIXTURE_SETUP(ns_mount_fd) { } FIXTURE_TEARDOWN(ns_mount_fd) { } /* * open_tree(OPEN_TREE_CLONE) creates an anonymous mount namespace to hold the * cloned mount tree. hook_namespace_init() fires with CLONE_NEWNS. */ TEST_F(ns_mount_fd, open_tree_clone) { int ruleset_fd, fd; disable_caps(_metadata); if (variant->sandboxed) { ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); if (variant->has_rule) ASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWNS)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); } set_cap(_metadata, CAP_SYS_ADMIN); fd = sys_open_tree(AT_FDCWD, "/", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC | AT_RECURSIVE); if (variant->expected_errno) { EXPECT_EQ(-1, fd); EXPECT_EQ(variant->expected_errno, errno); } else { ASSERT_LE(0, fd); EXPECT_EQ(0, close(fd)); } clear_cap(_metadata, CAP_SYS_ADMIN); } /* * open_tree(OPEN_TREE_NAMESPACE) clones the mount tree into a new * (non-anonymous) mount namespace. Same hook (CLONE_NEWNS) but a different * code path inside fs/namespace.c (open_new_namespace -> alloc_mnt_ns). * OPEN_TREE_NAMESPACE and OPEN_TREE_CLONE are mutually exclusive. */ TEST_F(ns_mount_fd, open_tree_namespace) { int ruleset_fd, fd; disable_caps(_metadata); if (variant->sandboxed) { ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); if (variant->has_rule) ASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWNS)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); } set_cap(_metadata, CAP_SYS_ADMIN); fd = sys_open_tree(AT_FDCWD, "/", OPEN_TREE_NAMESPACE | OPEN_TREE_CLOEXEC | AT_RECURSIVE); if (variant->expected_errno) { EXPECT_EQ(-1, fd); EXPECT_EQ(variant->expected_errno, errno); } else { ASSERT_LE(0, fd); EXPECT_EQ(0, close(fd)); } clear_cap(_metadata, CAP_SYS_ADMIN); } /* * fsmount(2) without FSMOUNT_NAMESPACE creates an anonymous mount namespace to * attach the new superblock. hook_namespace_init() fires with CLONE_NEWNS. * The fs context (fsopen + fsconfig) is set up before sandboxing because * Landlock here only handles the namespace permission. */ TEST_F(ns_mount_fd, fsmount_default) { int ruleset_fd, fs_fd, mnt_fd; disable_caps(_metadata); set_cap(_metadata, CAP_SYS_ADMIN); fs_fd = sys_fsopen("tmpfs", 0); ASSERT_LE(0, fs_fd); ASSERT_EQ(0, sys_fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0)); if (variant->sandboxed) { ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); if (variant->has_rule) ASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWNS)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); } mnt_fd = sys_fsmount(fs_fd, FSMOUNT_CLOEXEC, 0); if (variant->expected_errno) { EXPECT_EQ(-1, mnt_fd); EXPECT_EQ(variant->expected_errno, errno); } else { ASSERT_LE(0, mnt_fd); EXPECT_EQ(0, close(mnt_fd)); } EXPECT_EQ(0, close(fs_fd)); clear_cap(_metadata, CAP_SYS_ADMIN); } /* * fsmount(2) with FSMOUNT_NAMESPACE creates a (non-anonymous) mount namespace * for the new mount. Same hook as the default path, different code path. */ TEST_F(ns_mount_fd, fsmount_namespace) { int ruleset_fd, fs_fd, mnt_fd; disable_caps(_metadata); set_cap(_metadata, CAP_SYS_ADMIN); fs_fd = sys_fsopen("tmpfs", 0); ASSERT_LE(0, fs_fd); ASSERT_EQ(0, sys_fsconfig(fs_fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0)); if (variant->sandboxed) { ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); if (variant->has_rule) ASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWNS)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); } mnt_fd = sys_fsmount(fs_fd, FSMOUNT_CLOEXEC | FSMOUNT_NAMESPACE, 0); if (variant->expected_errno) { EXPECT_EQ(-1, mnt_fd); EXPECT_EQ(variant->expected_errno, errno); } else { ASSERT_LE(0, mnt_fd); EXPECT_EQ(0, close(mnt_fd)); } EXPECT_EQ(0, close(fs_fd)); clear_cap(_metadata, CAP_SYS_ADMIN); } /* * unshare(2) with multiple CLONE_NEW* flags: when * LANDLOCK_PERMISSION_NAMESPACE_USE denies any of the requested types, the * entire syscall fails with EPERM. This documents the kernel's atomic * behavior: create_new_namespaces(), called by copy_namespaces(), creates * namespaces sequentially, and the first Landlock denial rolls back the whole * operation. Mixing CLONE_NEWUSER (no capability check) with another * CLONE_NEW* type is the typical container-runtime bootstrap pattern. */ /* clang-format off */ FIXTURE(ns_create_multi_flag) {}; /* clang-format on */ FIXTURE_VARIANT(ns_create_multi_flag) { __u64 allowed_types; int expected_errno; }; /* clang-format off */ FIXTURE_VARIANT_ADD(ns_create_multi_flag, partial_denied) { /* clang-format on */ /* User namespace allowed; UTS namespace denied. */ .allowed_types = CLONE_NEWUSER, .expected_errno = EPERM, }; /* clang-format off */ FIXTURE_VARIANT_ADD(ns_create_multi_flag, both_allowed) { /* clang-format on */ .allowed_types = CLONE_NEWUSER | CLONE_NEWUTS, .expected_errno = 0, }; FIXTURE_SETUP(ns_create_multi_flag) { } FIXTURE_TEARDOWN(ns_create_multi_flag) { } TEST_F(ns_create_multi_flag, unshare) { int ruleset_fd, status, err; pid_t child; disable_caps(_metadata); /* Run unshare(2) in a child to avoid polluting the test process. */ child = fork(); ASSERT_LE(0, child); if (child == 0) { ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); ASSERT_EQ(0, add_ns_rule(ruleset_fd, variant->allowed_types)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); err = unshare(CLONE_NEWUSER | CLONE_NEWUTS); if (variant->expected_errno) { EXPECT_EQ(-1, err); EXPECT_EQ(variant->expected_errno, errno); } else { EXPECT_EQ(0, err); } _exit(_metadata->exit_code); } ASSERT_EQ(child, waitpid(child, &status, 0)); ASSERT_EQ(1, WIFEXITED(status)); ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); } /* clang-format off */ FIXTURE(setns_cross_process) {}; /* clang-format on */ FIXTURE_VARIANT(setns_cross_process) { bool is_sandboxed; bool has_rule; int expected_setns; }; /* clang-format off */ FIXTURE_VARIANT_ADD(setns_cross_process, denied) { /* clang-format on */ .is_sandboxed = true, .has_rule = false, .expected_setns = EPERM, }; /* clang-format off */ FIXTURE_VARIANT_ADD(setns_cross_process, sandboxed_allowed) { /* clang-format on */ .is_sandboxed = true, .has_rule = true, .expected_setns = 0, }; /* clang-format off */ FIXTURE_VARIANT_ADD(setns_cross_process, unsandboxed) { /* clang-format on */ .is_sandboxed = false, .has_rule = false, .expected_setns = 0, }; FIXTURE_SETUP(setns_cross_process) { } FIXTURE_TEARDOWN(setns_cross_process) { } /* * setns into a child's UTS namespace: when sandboxed with * LANDLOCK_PERMISSION_NAMESPACE_USE denying UTS, the rule-based check applies * regardless of which process created the namespace. This fixture exercises * only CLONE_NEWUTS; the same enforcement applies to every namespace type (see * hook_namespace_install() in security/landlock/ns.c), so per-type variants * would not exercise different code paths. */ TEST_F(setns_cross_process, setns) { int ruleset_fd, ns_fd, status; pid_t child; int pipe_parent[2], pipe_child[2]; char buf, path[64]; disable_caps(_metadata); /* * Enable dumpable so the parent can read /proc//ns/uts. Without * this, ptrace access checks (PTRACE_MODE_READ) prevent opening another * process's namespace entries. */ ASSERT_EQ(0, prctl(PR_SET_DUMPABLE, 1, 0, 0, 0)); ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC)); ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC)); child = fork(); ASSERT_LE(0, child); if (child == 0) { EXPECT_EQ(0, close(pipe_parent[1])); EXPECT_EQ(0, close(pipe_child[0])); /* Child: create a UTS namespace. */ set_cap(_metadata, CAP_SYS_ADMIN); ASSERT_EQ(0, unshare(CLONE_NEWUTS)); drop_caps(_metadata); ASSERT_EQ(0, prctl(PR_SET_DUMPABLE, 1, 0, 0, 0)); /* Signal parent that the namespace is ready. */ ASSERT_EQ(1, write(pipe_child[1], ".", 1)); /* Wait for parent to finish testing. */ ASSERT_EQ(1, read(pipe_parent[0], &buf, 1)); _exit(_metadata->exit_code); } EXPECT_EQ(0, close(pipe_parent[0])); EXPECT_EQ(0, close(pipe_child[1])); /* Wait for child namespace. */ ASSERT_EQ(1, read(pipe_child[0], &buf, 1)); EXPECT_EQ(0, close(pipe_child[0])); /* Open the child's NS FD BEFORE creating the domain. */ snprintf(path, sizeof(path), "/proc/%d/ns/uts", child); ns_fd = open(path, O_RDONLY); ASSERT_LE(0, ns_fd); if (variant->is_sandboxed) { ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); if (variant->has_rule) ASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWUTS)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); } set_cap(_metadata, CAP_SYS_ADMIN); if (variant->expected_setns) { EXPECT_EQ(-1, setns(ns_fd, CLONE_NEWUTS)); EXPECT_EQ(variant->expected_setns, errno); } else { EXPECT_EQ(0, setns(ns_fd, CLONE_NEWUTS)); } clear_cap(_metadata, CAP_SYS_ADMIN); EXPECT_EQ(0, close(ns_fd)); /* Release child. */ ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); EXPECT_EQ(0, close(pipe_parent[1])); ASSERT_EQ(child, waitpid(child, &status, 0)); ASSERT_EQ(1, WIFEXITED(status)); ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); } /* * Verify that LANDLOCK_PERMISSION_NAMESPACE_USE and * LANDLOCK_PERMISSION_CAPABILITY_USE apply simultaneously: creating/entering a * non-user namespace requires both the namespace type to be allowed AND * CAP_SYS_ADMIN to be allowed. User namespace creation is the exception (no * capable() call from the kernel). */ TEST(setns_and_create) { int ruleset_fd, ns_fd; const struct landlock_ruleset_attr attr = { .handled_permissions = LANDLOCK_PERMISSION_NAMESPACE_USE | LANDLOCK_PERMISSION_CAPABILITY_USE, }; const struct landlock_namespace_attr ns_attr = { .permissions = LANDLOCK_PERMISSION_NAMESPACE_USE, .allowed_namespace_types = CLONE_NEWUTS, }; const struct landlock_capability_attr cap_attr = { .permissions = LANDLOCK_PERMISSION_CAPABILITY_USE, .allowed_capabilities = (1ULL << CAP_SYS_ADMIN), }; disable_caps(_metadata); ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0); ASSERT_LE(0, ruleset_fd); ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &ns_attr, 0)); ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY, &cap_attr, 0)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); /* UTS unshare: allowed by NS rule + CAP_SYS_ADMIN allowed. */ set_cap(_metadata, CAP_SYS_ADMIN); ASSERT_EQ(0, unshare(CLONE_NEWUTS)); /* IPC unshare: denied by NS rule (type not allowed). */ EXPECT_EQ(-1, unshare(CLONE_NEWIPC)); EXPECT_EQ(EPERM, errno); /* setns into current UTS: allowed by NS rule. */ ns_fd = open("/proc/self/ns/uts", O_RDONLY); ASSERT_LE(0, ns_fd); EXPECT_EQ(0, setns(ns_fd, CLONE_NEWUTS)); clear_cap(_metadata, CAP_SYS_ADMIN); EXPECT_EQ(0, close(ns_fd)); /* * User namespace creation: only LANDLOCK_PERMISSION_NAMESPACE_USE * needed (no capable() call from the kernel for user NS). Denied * because CLONE_NEWUSER is not in the allowed namespace types. */ EXPECT_EQ(-1, unshare(CLONE_NEWUSER)); EXPECT_EQ(EPERM, errno); } /* * Verify that LANDLOCK_PERMISSION_CAPABILITY_USE can deny the CAP_SYS_ADMIN * check that the kernel performs before the Landlock namespace hook is reached. * The NS type is allowed but the required capability is not, so the operation * fails on the capability check. * * User namespace creation is the exception: no capable() call, so the operation * succeeds with just LANDLOCK_PERMISSION_NAMESPACE_USE. */ TEST(two_permissions_cap_denied) { const struct landlock_ruleset_attr attr = { .handled_permissions = LANDLOCK_PERMISSION_NAMESPACE_USE | LANDLOCK_PERMISSION_CAPABILITY_USE, }; const struct landlock_namespace_attr ns_attr = { .permissions = LANDLOCK_PERMISSION_NAMESPACE_USE, .allowed_namespace_types = CLONE_NEWUTS | CLONE_NEWUSER, }; /* CAP_SYS_ADMIN is NOT allowed. */ const struct landlock_capability_attr cap_attr = { .permissions = LANDLOCK_PERMISSION_CAPABILITY_USE, .allowed_capabilities = (1ULL << CAP_SYS_CHROOT), }; int ruleset_fd, ns_fd; disable_caps(_metadata); ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0); ASSERT_LE(0, ruleset_fd); ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &ns_attr, 0)); ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY, &cap_attr, 0)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); /* * UTS creation: the process holds CAP_SYS_ADMIN but Landlock denies it * (not in the cap rule), so the kernel's ns_capable(CAP_SYS_ADMIN) gate * fails before the namespace hook is reached. */ set_cap(_metadata, CAP_SYS_ADMIN); EXPECT_EQ(-1, unshare(CLONE_NEWUTS)); EXPECT_EQ(EPERM, errno); /* * setns into the current UTS namespace: the type is allowed but * CAP_SYS_ADMIN is denied, so the kernel's ns_capable(CAP_SYS_ADMIN) * gate fails before the namespace hook is reached (the setns capability * leg, complementing the unshare/create leg above). */ ns_fd = open("/proc/self/ns/uts", O_RDONLY); ASSERT_LE(0, ns_fd); EXPECT_EQ(-1, setns(ns_fd, CLONE_NEWUTS)); EXPECT_EQ(EPERM, errno); EXPECT_EQ(0, close(ns_fd)); clear_cap(_metadata, CAP_SYS_ADMIN); /* * User NS creation: no capable() call from the kernel, so only * LANDLOCK_PERMISSION_NAMESPACE_USE applies. CLONE_NEWUSER is in the * allowed set, so this succeeds. */ EXPECT_EQ(0, unshare(CLONE_NEWUSER)); } /* * Combining CLONE_NEWUSER with another namespace type in a single unshare(2) * call does not exempt the non-user namespace from the capability check: the * kernel checks ns_capable(new_user_ns, CAP_SYS_ADMIN) for it, and the * namespace-agnostic capability hook denies it when the domain handles * LANDLOCK_PERMISSION_CAPABILITY_USE without allowing CAP_SYS_ADMIN. Both * namespace types are allowed here, yet the combined call fails atomically, * exactly like the equivalent two-call sequence. */ TEST(two_permissions_combined_unshare_denied) { const struct landlock_ruleset_attr attr = { .handled_permissions = LANDLOCK_PERMISSION_NAMESPACE_USE | LANDLOCK_PERMISSION_CAPABILITY_USE, }; /* Both namespace types are allowed; no capability is allowed. */ const struct landlock_namespace_attr ns_attr = { .permissions = LANDLOCK_PERMISSION_NAMESPACE_USE, .allowed_namespace_types = CLONE_NEWUSER | CLONE_NEWUTS, }; int ruleset_fd; disable_caps(_metadata); ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0); ASSERT_LE(0, ruleset_fd); ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &ns_attr, 0)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); /* * CAP_SYS_ADMIN is held so the kernel's commoncap check passes (via * ownership of the new user namespace) and the denial is attributable * to Landlock. The UTS leg needs CAP_SYS_ADMIN, which the capability * hook denies, so the atomic unshare(2) fails and no namespace is * created. */ set_cap(_metadata, CAP_SYS_ADMIN); EXPECT_EQ(-1, unshare(CLONE_NEWUSER | CLONE_NEWUTS)); EXPECT_EQ(EPERM, errno); clear_cap(_metadata, CAP_SYS_ADMIN); /* The user namespace alone has no capability check, so it succeeds. */ EXPECT_EQ(0, unshare(CLONE_NEWUSER)); } /* * Positive complement to two_permissions_combined_unshare_denied: allowing * CAP_SYS_ADMIN lets the same combined unshare(CLONE_NEWUSER | CLONE_NEWUTS) * succeed, proving the denial above comes from the missing capability allowance * and not the namespace rule. Run in a child because a successful unshare(2) * mutates the caller. */ TEST(two_permissions_combined_unshare_allowed) { const struct landlock_ruleset_attr attr = { .handled_permissions = LANDLOCK_PERMISSION_NAMESPACE_USE | LANDLOCK_PERMISSION_CAPABILITY_USE, }; const struct landlock_namespace_attr ns_attr = { .permissions = LANDLOCK_PERMISSION_NAMESPACE_USE, .allowed_namespace_types = CLONE_NEWUSER | CLONE_NEWUTS, }; const struct landlock_capability_attr cap_attr = { .permissions = LANDLOCK_PERMISSION_CAPABILITY_USE, .allowed_capabilities = (1ULL << CAP_SYS_ADMIN), }; int ruleset_fd, status; pid_t child; disable_caps(_metadata); child = fork(); ASSERT_LE(0, child); if (child == 0) { ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0); ASSERT_LE(0, ruleset_fd); ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &ns_attr, 0)); ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY, &cap_attr, 0)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); set_cap(_metadata, CAP_SYS_ADMIN); EXPECT_EQ(0, unshare(CLONE_NEWUSER | CLONE_NEWUTS)); clear_cap(_metadata, CAP_SYS_ADMIN); _exit(_metadata->exit_code); } ASSERT_EQ(child, waitpid(child, &status, 0)); ASSERT_EQ(1, WIFEXITED(status)); ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); } /* * Inverse of two_permissions_cap_denied: the required capability is allowed but * the namespace type is not, so the Landlock namespace hook denies the * operation (the capability leg passes). A positive control (an allowed type * creates successfully with the same allowed capability) proves CAP_SYS_ADMIN * is genuinely exercisable, so the UTS denial is attributable to the namespace * hook rather than the capability hook. */ TEST(two_permissions_ns_denied) { const struct landlock_ruleset_attr attr = { .handled_permissions = LANDLOCK_PERMISSION_NAMESPACE_USE | LANDLOCK_PERMISSION_CAPABILITY_USE, }; /* CLONE_NEWUTS is NOT allowed; CLONE_NEWNET is. */ const struct landlock_namespace_attr ns_attr = { .permissions = LANDLOCK_PERMISSION_NAMESPACE_USE, .allowed_namespace_types = CLONE_NEWNET, }; const struct landlock_capability_attr cap_attr = { .permissions = LANDLOCK_PERMISSION_CAPABILITY_USE, .allowed_capabilities = (1ULL << CAP_SYS_ADMIN), }; int ruleset_fd; disable_caps(_metadata); ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0); ASSERT_LE(0, ruleset_fd); ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &ns_attr, 0)); ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY, &cap_attr, 0)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); /* * Positive control: CLONE_NEWNET is allowed and CAP_SYS_ADMIN is * allowed, so the kernel's ns_capable() gate passes and the namespace * hook allows it. */ set_cap(_metadata, CAP_SYS_ADMIN); EXPECT_EQ(0, unshare(CLONE_NEWNET)); /* * CAP_SYS_ADMIN is allowed (ns_capable passes), but CLONE_NEWUTS is not * in the allowed namespace types, so the Landlock namespace hook denies * it. */ EXPECT_EQ(-1, unshare(CLONE_NEWUTS)); EXPECT_EQ(EPERM, errno); clear_cap(_metadata, CAP_SYS_ADMIN); } /* * Mount namespace setns is unique: the kernel checks both CAP_SYS_ADMIN and * CAP_SYS_CHROOT in mntns_install(). Verify that allowing CAP_SYS_ADMIN alone * is not sufficient. */ TEST(two_permissions_mnt_setns) { const struct landlock_ruleset_attr attr = { .handled_permissions = LANDLOCK_PERMISSION_NAMESPACE_USE | LANDLOCK_PERMISSION_CAPABILITY_USE, }; const struct landlock_namespace_attr ns_attr = { .permissions = LANDLOCK_PERMISSION_NAMESPACE_USE, .allowed_namespace_types = CLONE_NEWNS, }; const struct landlock_capability_attr cap_admin = { .permissions = LANDLOCK_PERMISSION_CAPABILITY_USE, .allowed_capabilities = (1ULL << CAP_SYS_ADMIN), }; const struct landlock_capability_attr cap_admin_chroot = { .permissions = LANDLOCK_PERMISSION_CAPABILITY_USE, .allowed_capabilities = (1ULL << CAP_SYS_ADMIN) | (1ULL << CAP_SYS_CHROOT), }; int ruleset_fd, ns_fd; disable_caps(_metadata); /* Layer 1: allow mount NS + CAP_SYS_ADMIN only (no CAP_SYS_CHROOT). */ ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0); ASSERT_LE(0, ruleset_fd); ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &ns_attr, 0)); ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY, &cap_admin, 0)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); ns_fd = open("/proc/self/ns/mnt", O_RDONLY); ASSERT_LE(0, ns_fd); /* * Fails: mntns_install() checks CAP_SYS_ADMIN (allowed) then * CAP_SYS_CHROOT (denied by LANDLOCK_PERMISSION_CAPABILITY_USE). */ set_cap(_metadata, CAP_SYS_ADMIN); set_cap(_metadata, CAP_SYS_CHROOT); EXPECT_EQ(-1, setns(ns_fd, CLONE_NEWNS)); EXPECT_EQ(EPERM, errno); clear_cap(_metadata, CAP_SYS_ADMIN); clear_cap(_metadata, CAP_SYS_CHROOT); /* Layer 2: also allows CAP_SYS_CHROOT. */ ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0); ASSERT_LE(0, ruleset_fd); ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &ns_attr, 0)); ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY, &cap_admin_chroot, 0)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); /* * Still fails: layer 1 still denies CAP_SYS_CHROOT. Landlock layer * stacking means the most restrictive layer wins. */ set_cap(_metadata, CAP_SYS_ADMIN); set_cap(_metadata, CAP_SYS_CHROOT); EXPECT_EQ(-1, setns(ns_fd, CLONE_NEWNS)); EXPECT_EQ(EPERM, errno); clear_cap(_metadata, CAP_SYS_ADMIN); clear_cap(_metadata, CAP_SYS_CHROOT); EXPECT_EQ(0, close(ns_fd)); } /* * Positive complement to two_permissions_mnt_setns: a single layer that allows * the mount namespace type AND both CAP_SYS_ADMIN and CAP_SYS_CHROOT lets * setns(CLONE_NEWNS) into the process's own mount namespace succeed. Proves * the denial legs above are caused by the withheld CAP_SYS_CHROOT, not an * unconditional block of mount setns. */ TEST(two_permissions_mnt_setns_allowed) { const struct landlock_ruleset_attr attr = { .handled_permissions = LANDLOCK_PERMISSION_NAMESPACE_USE | LANDLOCK_PERMISSION_CAPABILITY_USE, }; const struct landlock_namespace_attr ns_attr = { .permissions = LANDLOCK_PERMISSION_NAMESPACE_USE, .allowed_namespace_types = CLONE_NEWNS, }; const struct landlock_capability_attr cap_attr = { .permissions = LANDLOCK_PERMISSION_CAPABILITY_USE, .allowed_capabilities = (1ULL << CAP_SYS_ADMIN) | (1ULL << CAP_SYS_CHROOT), }; int ruleset_fd, ns_fd; disable_caps(_metadata); /* Open the NS FD before enforcing the domain. */ ns_fd = open("/proc/self/ns/mnt", O_RDONLY); ASSERT_LE(0, ns_fd); ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0); ASSERT_LE(0, ruleset_fd); ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &ns_attr, 0)); ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY, &cap_attr, 0)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); /* * mntns_install() checks CAP_SYS_ADMIN then CAP_SYS_CHROOT (both * allowed by the rule) and the mount NS type is allowed, so setns into * the process's own mount namespace succeeds. */ set_cap(_metadata, CAP_SYS_ADMIN); set_cap(_metadata, CAP_SYS_CHROOT); EXPECT_EQ(0, setns(ns_fd, CLONE_NEWNS)); clear_cap(_metadata, CAP_SYS_ADMIN); clear_cap(_metadata, CAP_SYS_CHROOT); EXPECT_EQ(0, close(ns_fd)); } /* * setns() through a namespace fd inherited across fork() is still governed by * the child's own Landlock domain: the child enforces a domain denying * CLONE_NEWUTS, then joining the inherited /proc/self/ns/uts fd is denied. */ TEST(setns_inherited_fd) { int ns_fd, status; pid_t child; disable_caps(_metadata); /* Open the UTS ns fd before fork() so the child inherits it. */ ns_fd = open("/proc/self/ns/uts", O_RDONLY); ASSERT_LE(0, ns_fd); child = fork(); ASSERT_LE(0, child); if (child == 0) { int ruleset_fd; /* * Baseline: joining the inherited fd works before the domain, * so the later EPERM is attributable to Landlock. */ set_cap(_metadata, CAP_SYS_ADMIN); ASSERT_EQ(0, setns(ns_fd, CLONE_NEWUTS)); clear_cap(_metadata, CAP_SYS_ADMIN); ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); /* No rule allows UTS. */ enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); set_cap(_metadata, CAP_SYS_ADMIN); EXPECT_EQ(-1, setns(ns_fd, CLONE_NEWUTS)); EXPECT_EQ(EPERM, errno); clear_cap(_metadata, CAP_SYS_ADMIN); _exit(_metadata->exit_code); return; } EXPECT_EQ(0, close(ns_fd)); ASSERT_EQ(child, waitpid(child, &status, 0)); if (WIFSIGNALED(status) || !WIFEXITED(status) || WEXITSTATUS(status) != EXIT_SUCCESS) _metadata->exit_code = KSFT_FAIL; } /* Audit tests */ static int matches_log_ns_create(int audit_fd, __u64 ns_type) { static const char log_template[] = REGEX_LANDLOCK_PREFIX " blockers=namespace\\.use namespace_type=0x%x namespace_id=0$"; char log_match[sizeof(log_template) + 10]; int log_match_len; log_match_len = snprintf(log_match, sizeof(log_match), log_template, (unsigned int)ns_type); if (log_match_len >= sizeof(log_match)) return -E2BIG; return audit_match_record(audit_fd, AUDIT_LANDLOCK_ACCESS, log_match, NULL); } static int matches_log_ns_setns(int audit_fd, __u64 ns_type, __u64 ns_id) { static const char log_template[] = REGEX_LANDLOCK_PREFIX " blockers=namespace\\.use namespace_type=0x%x namespace_id=%llu$"; char log_match[sizeof(log_template) + 32]; int log_match_len; log_match_len = snprintf(log_match, sizeof(log_match), log_template, (unsigned int)ns_type, (unsigned long long)ns_id); if (log_match_len >= sizeof(log_match)) return -E2BIG; return audit_match_record(audit_fd, AUDIT_LANDLOCK_ACCESS, log_match, NULL); } FIXTURE(ns_audit) { struct audit_filter audit_filter; int audit_fd; }; FIXTURE_SETUP(ns_audit) { ASSERT_TRUE(is_in_init_user_ns()); disable_caps(_metadata); set_cap(_metadata, CAP_AUDIT_CONTROL); self->audit_fd = audit_init_with_exe_filter(&self->audit_filter); EXPECT_LE(0, self->audit_fd); clear_cap(_metadata, CAP_AUDIT_CONTROL); } FIXTURE_TEARDOWN(ns_audit) { set_cap(_metadata, CAP_AUDIT_CONTROL); EXPECT_EQ(0, audit_cleanup(self->audit_fd, &self->audit_filter)); clear_cap(_metadata, CAP_AUDIT_CONTROL); } /* * Verifies that a denied namespace creation produces the expected audit record * with the namespace.use blocker string and namespace_type. */ TEST_F(ns_audit, create_denied) { struct audit_records records = {}; int ruleset_fd; __u64 domain_id; ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); set_cap(_metadata, CAP_SYS_ADMIN); EXPECT_EQ(-1, unshare(CLONE_NEWUTS)); EXPECT_EQ(EPERM, errno); clear_cap(_metadata, CAP_SYS_ADMIN); EXPECT_EQ(0, matches_log_ns_create(self->audit_fd, CLONE_NEWUTS)); /* * The domain allocation record is emitted in the same event as the * first denial; anchor its status=allocated and enforcing pid. Both * access and domain records are now consumed, so none remain. */ EXPECT_EQ(0, matches_log_domain_allocated(self->audit_fd, getpid(), &domain_id)); EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); EXPECT_EQ(0, records.access); EXPECT_EQ(0, records.domain); } TEST_F(ns_audit, create_allowed) { struct audit_records records = {}; int ruleset_fd; ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); ASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWUTS)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); set_cap(_metadata, CAP_SYS_ADMIN); EXPECT_EQ(0, unshare(CLONE_NEWUTS)); clear_cap(_metadata, CAP_SYS_ADMIN); /* No records: allowed operations never trigger audit logging. */ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); EXPECT_EQ(0, records.access); } TEST_F(ns_audit, setns_allowed) { struct audit_records records = {}; int ruleset_fd, ns_fd; ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); ASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWUTS)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); ns_fd = open("/proc/self/ns/uts", O_RDONLY); ASSERT_LE(0, ns_fd); /* Allowed: should succeed with no audit record. */ set_cap(_metadata, CAP_SYS_ADMIN); EXPECT_EQ(0, setns(ns_fd, CLONE_NEWUTS)); clear_cap(_metadata, CAP_SYS_ADMIN); EXPECT_EQ(0, close(ns_fd)); /* No records: allowed setns never triggers audit logging. */ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); EXPECT_EQ(0, records.access); } TEST_F(ns_audit, setns_denied) { struct audit_records records = {}; int ruleset_fd, ns_fd; __u64 ns_id, domain_id; ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); /* No rule allows UTS -> denied. */ enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); ns_fd = open("/proc/self/ns/uts", O_RDONLY); ASSERT_LE(0, ns_fd); /* The audit record logs the target namespace's exact ns_id. */ ASSERT_EQ(0, ioctl(ns_fd, NS_GET_ID, &ns_id)); set_cap(_metadata, CAP_SYS_ADMIN); EXPECT_EQ(-1, setns(ns_fd, CLONE_NEWUTS)); EXPECT_EQ(EPERM, errno); clear_cap(_metadata, CAP_SYS_ADMIN); EXPECT_EQ(0, close(ns_fd)); /* Verify the audit record for setns denial, anchored to the ns_id. */ EXPECT_EQ(0, matches_log_ns_setns(self->audit_fd, CLONE_NEWUTS, ns_id)); /* * The domain allocation record is emitted in the same event as the * first denial; anchor its status=allocated and enforcing pid. Both * access and domain records are now consumed, so none remain. */ EXPECT_EQ(0, matches_log_domain_allocated(self->audit_fd, getpid(), &domain_id)); EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); EXPECT_EQ(0, records.access); EXPECT_EQ(0, records.domain); } /* * A quieted denial is still denied (EPERM); only its audit record is * suppressed. Quiet-only rule (empty allowed set). */ TEST_F(ns_audit, create_quieted) { struct audit_records records = {}; int ruleset_fd; ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); ASSERT_EQ(0, add_ns_rule_full(ruleset_fd, 0, CLONE_NEWUTS)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); set_cap(_metadata, CAP_SYS_ADMIN); EXPECT_EQ(-1, unshare(CLONE_NEWUTS)); EXPECT_EQ(EPERM, errno); clear_cap(_metadata, CAP_SYS_ADMIN); /* The denial is suppressed: no access record. */ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); EXPECT_EQ(0, records.access); } /* * A quieted setns denial is still denied (EPERM); only its audit record is * suppressed. The install-hook mirror of create_quieted. */ TEST_F(ns_audit, setns_quieted) { struct audit_records records = {}; int ruleset_fd, ns_fd; ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); ASSERT_EQ(0, add_ns_rule_full(ruleset_fd, 0, CLONE_NEWUTS)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); ns_fd = open("/proc/self/ns/uts", O_RDONLY); ASSERT_LE(0, ns_fd); set_cap(_metadata, CAP_SYS_ADMIN); EXPECT_EQ(-1, setns(ns_fd, CLONE_NEWUTS)); EXPECT_EQ(EPERM, errno); clear_cap(_metadata, CAP_SYS_ADMIN); EXPECT_EQ(0, close(ns_fd)); /* The denial is suppressed: no access record. */ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); EXPECT_EQ(0, records.access); } /* * Quiet is per-member: quieting one namespace type does not silence the denial * of another type denied by the same layer. */ TEST_F(ns_audit, quiet_is_per_member) { struct audit_records records = {}; int ruleset_fd; __u64 domain_id; ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); /* * Quiet CLONE_NEWNET only; CLONE_NEWUTS is neither allowed nor quiet. */ ASSERT_EQ(0, add_ns_rule_full(ruleset_fd, 0, CLONE_NEWNET)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); set_cap(_metadata, CAP_SYS_ADMIN); EXPECT_EQ(-1, unshare(CLONE_NEWUTS)); EXPECT_EQ(EPERM, errno); clear_cap(_metadata, CAP_SYS_ADMIN); /* The unquieted CLONE_NEWUTS denial is still logged. */ EXPECT_EQ(0, matches_log_ns_create(self->audit_fd, CLONE_NEWUTS)); EXPECT_EQ(0, matches_log_domain_allocated(self->audit_fd, getpid(), &domain_id)); EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); EXPECT_EQ(0, records.access); EXPECT_EQ(0, records.domain); } /* * Quieting an unknown namespace bit has no effect: a rule that quiets only a * bit the running kernel does not know still logs the denial of a known type. * Mirrors the forward-compatibility contract for the quiet mask. */ TEST_F(ns_audit, quiet_unknown_bit_no_effect) { struct audit_records records = {}; int ruleset_fd; __u64 domain_id; ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); /* Quiet only an unknown bit (bit 31 is not a CLONE_NEW* value). */ ASSERT_EQ(0, add_ns_rule_full(ruleset_fd, 0, (1ULL << 31))); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); set_cap(_metadata, CAP_SYS_ADMIN); EXPECT_EQ(-1, unshare(CLONE_NEWUTS)); EXPECT_EQ(EPERM, errno); clear_cap(_metadata, CAP_SYS_ADMIN); /* The unknown quiet bit does not suppress the known denial. */ EXPECT_EQ(0, matches_log_ns_create(self->audit_fd, CLONE_NEWUTS)); EXPECT_EQ(0, matches_log_domain_allocated(self->audit_fd, getpid(), &domain_id)); EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); EXPECT_EQ(0, records.access); EXPECT_EQ(0, records.domain); } /* A single rule can both allow one member and quiet the denial of another. */ TEST_F(ns_audit, allow_and_quiet) { struct audit_records records = {}; int ruleset_fd; ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); ASSERT_EQ(0, add_ns_rule_full(ruleset_fd, CLONE_NEWNET, CLONE_NEWUTS)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); /* Allowed member: succeeds. */ set_cap(_metadata, CAP_SYS_ADMIN); EXPECT_EQ(0, unshare(CLONE_NEWNET)); /* Quieted member: denied but not logged. */ EXPECT_EQ(-1, unshare(CLONE_NEWUTS)); EXPECT_EQ(EPERM, errno); clear_cap(_metadata, CAP_SYS_ADMIN); EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); EXPECT_EQ(0, records.access); } /* * Quieting a member that is also allowed is inert: an allowed member is never * denied, so its quiet bit has no effect. */ TEST_F(ns_audit, allow_and_quiet_same_member) { struct audit_records records = {}; int ruleset_fd; ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); /* Same member both allowed and quieted. */ ASSERT_EQ(0, add_ns_rule_full(ruleset_fd, CLONE_NEWNET, CLONE_NEWNET)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); /* The member is allowed (quiet is inert), so it succeeds. */ set_cap(_metadata, CAP_SYS_ADMIN); EXPECT_EQ(0, unshare(CLONE_NEWNET)); clear_cap(_metadata, CAP_SYS_ADMIN); /* Nothing denied, nothing logged. */ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); EXPECT_EQ(0, records.access); } /* * Only the youngest denying layer decides quieting: a parent that quiets a * member cannot silence a denial by a deeper layer that handles but does not * quiet it. */ TEST_F(ns_audit, quiet_youngest_layer_wins) { struct audit_records records = {}; int ruleset_fd; /* Parent layer: handles namespaces and quiets CLONE_NEWUTS. */ ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); ASSERT_EQ(0, add_ns_rule_full(ruleset_fd, 0, CLONE_NEWUTS)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); /* Child layer: handles namespaces but does not quiet CLONE_NEWUTS. */ ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); set_cap(_metadata, CAP_SYS_ADMIN); EXPECT_EQ(-1, unshare(CLONE_NEWUTS)); EXPECT_EQ(EPERM, errno); clear_cap(_metadata, CAP_SYS_ADMIN); /* The child layer denies and does not quiet: logged. */ EXPECT_EQ(0, matches_log_ns_create(self->audit_fd, CLONE_NEWUTS)); EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); EXPECT_EQ(0, records.access); } /* * A parent layer that quiets and denies a member suppresses its own denial even * when it is not the youngest layer: a child layer allows the member, so the * parent is the youngest (and only) denying layer and its stored quiet mask * fires. Complements quiet_youngest_layer_wins. */ TEST_F(ns_audit, quiet_parent_only_denying_layer) { struct audit_records records = {}; int ruleset_fd; /* Parent layer: quiet-only, so it denies CLONE_NEWUTS. */ ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); ASSERT_EQ(0, add_ns_rule_full(ruleset_fd, 0, CLONE_NEWUTS)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); /* Child layer: allows CLONE_NEWUTS, so only the parent denies it. */ ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); ASSERT_EQ(0, add_ns_rule(ruleset_fd, CLONE_NEWUTS)); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); set_cap(_metadata, CAP_SYS_ADMIN); EXPECT_EQ(-1, unshare(CLONE_NEWUTS)); EXPECT_EQ(EPERM, errno); clear_cap(_metadata, CAP_SYS_ADMIN); /* The parent quiets and is the youngest denying layer: suppressed. */ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); EXPECT_EQ(0, records.access); } /* Trace tests */ /* clang-format off */ FIXTURE(ns_trace) { /* clang-format on */ int tracefs_ok; }; FIXTURE_SETUP(ns_trace) { int ret; set_cap(_metadata, CAP_SYS_ADMIN); ASSERT_EQ(0, unshare(CLONE_NEWNS)); ASSERT_EQ(0, mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL)); ret = tracefs_fixture_setup(); if (ret) { clear_cap(_metadata, CAP_SYS_ADMIN); self->tracefs_ok = 0; SKIP(return, "tracefs not available"); } self->tracefs_ok = 1; ASSERT_EQ(0, tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, true)); ASSERT_EQ(0, tracefs_enable_event(TRACEFS_ADD_RULE_NAMESPACE_ENABLE, true)); ASSERT_EQ(0, tracefs_enable_event( TRACEFS_DENY_PERMISSION_NAMESPACE_ENABLE, true)); ASSERT_EQ(0, tracefs_clear()); clear_cap(_metadata, CAP_SYS_ADMIN); } FIXTURE_TEARDOWN(ns_trace) { if (!self->tracefs_ok) return; set_cap(_metadata, CAP_SYS_ADMIN); tracefs_enable_event(TRACEFS_CREATE_RULESET_ENABLE, false); tracefs_enable_event(TRACEFS_ADD_RULE_NAMESPACE_ENABLE, false); tracefs_enable_event(TRACEFS_DENY_PERMISSION_NAMESPACE_ENABLE, false); tracefs_fixture_teardown(); clear_cap(_metadata, CAP_SYS_ADMIN); } /* * Check the permission fields and version history, including two rejected calls * and two successful calls that do not extend the effective policy: a repeated * rule and an unknown-only rule. */ TEST_F(ns_trace, rule_events) { const char *const version_1 = REGEX_ADD_RULE_NAMESPACE_VERSION(TRACE_TASK, "1"); const char *const version_2 = REGEX_ADD_RULE_NAMESPACE_VERSION(TRACE_TASK, "2"); const char *const version_3 = REGEX_ADD_RULE_NAMESPACE_VERSION(TRACE_TASK, "3"); const struct landlock_namespace_attr valid_attr = { .permissions = LANDLOCK_PERMISSION_NAMESPACE_USE, .allowed_namespace_types = CLONE_NEWUTS, .quiet_namespace_types = CLONE_NEWNET, }; const struct landlock_namespace_attr invalid_attr = { .permissions = LANDLOCK_PERMISSION_CAPABILITY_USE, .allowed_namespace_types = CLONE_NEWUTS, }; char expected[32], field[64]; char *buf; int ruleset_fd; if (!self->tracefs_ok) SKIP(return, "tracefs not available"); ASSERT_EQ(0, tracefs_clear_buf()); ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); ASSERT_EQ(0, add_ns_rule_full(ruleset_fd, CLONE_NEWUTS, CLONE_NEWNET)); ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &invalid_attr, 0)); ASSERT_EQ(EINVAL, errno); ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NAMESPACE, &valid_attr, LANDLOCK_ADD_RULE_QUIET)); ASSERT_EQ(EINVAL, errno); ASSERT_EQ(0, add_ns_rule_full(ruleset_fd, CLONE_NEWUTS, CLONE_NEWNET)); ASSERT_EQ(0, add_ns_rule(ruleset_fd, 1ULL << 63)); EXPECT_EQ(0, close(ruleset_fd)); buf = tracefs_read_buf(); ASSERT_NE(NULL, buf); ASSERT_EQ(1, tracefs_count_matches(buf, REGEX_CREATE_RULESET(TRACE_TASK))); ASSERT_EQ(0, tracefs_extract_field( buf, REGEX_CREATE_RULESET(TRACE_TASK), "handled_permissions", field, sizeof(field))); EXPECT_STREQ("namespace.use", field); ASSERT_EQ(3, tracefs_count_matches(buf, "landlock_add_rule_namespace: ")); ASSERT_EQ(3, tracefs_count_matches( buf, REGEX_ADD_RULE_NAMESPACE(TRACE_TASK))); EXPECT_EQ(1, tracefs_count_matches(buf, version_1)); EXPECT_EQ(1, tracefs_count_matches(buf, version_2)); EXPECT_EQ(1, tracefs_count_matches(buf, version_3)); ASSERT_EQ(0, tracefs_extract_field(buf, version_1, "permissions", field, sizeof(field))); EXPECT_STREQ("namespace.use", field); ASSERT_EQ(0, tracefs_extract_field(buf, version_1, "allowed_namespace_types", field, sizeof(field))); /* Trace fields use raw CLONE_NEW* values, not internal bit indexes. */ snprintf(expected, sizeof(expected), "0x%llx", (unsigned long long)CLONE_NEWUTS); EXPECT_STREQ(expected, field); ASSERT_EQ(0, tracefs_extract_field(buf, version_1, "quiet_namespace_types", field, sizeof(field))); snprintf(expected, sizeof(expected), "0x%llx", (unsigned long long)CLONE_NEWNET); EXPECT_STREQ(expected, field); /* The rejected calls emit no event and do not consume version 2. */ ASSERT_EQ(0, tracefs_extract_field(buf, version_2, "permissions", field, sizeof(field))); EXPECT_STREQ("namespace.use", field); ASSERT_EQ(0, tracefs_extract_field(buf, version_2, "allowed_namespace_types", field, sizeof(field))); snprintf(expected, sizeof(expected), "0x%llx", (unsigned long long)CLONE_NEWUTS); EXPECT_STREQ(expected, field); ASSERT_EQ(0, tracefs_extract_field(buf, version_2, "quiet_namespace_types", field, sizeof(field))); snprintf(expected, sizeof(expected), "0x%llx", (unsigned long long)CLONE_NEWNET); EXPECT_STREQ(expected, field); /* The unknown-only call succeeds but contributes no effective bits. */ ASSERT_EQ(0, tracefs_extract_field(buf, version_3, "permissions", field, sizeof(field))); EXPECT_STREQ("namespace.use", field); ASSERT_EQ(0, tracefs_extract_field(buf, version_3, "allowed_namespace_types", field, sizeof(field))); EXPECT_STREQ("0x0", field); ASSERT_EQ(0, tracefs_extract_field(buf, version_3, "quiet_namespace_types", field, sizeof(field))); EXPECT_STREQ("0x0", field); free(buf); } static void test_ns_trace_denial(struct __test_metadata *const _metadata, const bool use_setns, const bool quiet) { char expected[32], field[64]; int ruleset_fd, ns_fd = -1, ret; __u64 expected_ns_id = 0; char *buf; ASSERT_EQ(0, tracefs_clear_buf()); if (use_setns) { ns_fd = open("/proc/self/ns/uts", O_RDONLY | O_CLOEXEC); ASSERT_LE(0, ns_fd); ASSERT_EQ(0, ioctl(ns_fd, NS_GET_ID, &expected_ns_id)); } ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); if (quiet) ASSERT_EQ(0, add_ns_rule_full(ruleset_fd, 0, CLONE_NEWUTS)); set_cap(_metadata, CAP_SYS_ADMIN); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); if (use_setns) ret = setns(ns_fd, CLONE_NEWUTS); else ret = unshare(CLONE_NEWUTS); EXPECT_EQ(-1, ret); EXPECT_EQ(EPERM, errno); clear_cap(_metadata, CAP_SYS_ADMIN); if (ns_fd >= 0) EXPECT_EQ(0, close(ns_fd)); buf = tracefs_read_buf(); ASSERT_NE(NULL, buf); ASSERT_EQ(1, tracefs_count_matches( buf, REGEX_DENY_PERMISSION_NAMESPACE(TRACE_TASK))) { TH_LOG("Expected one namespace denial event\n%s", buf); } ASSERT_EQ(0, tracefs_extract_field( buf, REGEX_DENY_PERMISSION_NAMESPACE(TRACE_TASK), "domain", field, sizeof(field))); EXPECT_STRNE("0", field); ASSERT_EQ(0, tracefs_extract_field( buf, REGEX_DENY_PERMISSION_NAMESPACE(TRACE_TASK), "same_exec", field, sizeof(field))); EXPECT_STREQ("1", field); ASSERT_EQ(0, tracefs_extract_field( buf, REGEX_DENY_PERMISSION_NAMESPACE(TRACE_TASK), "logged", field, sizeof(field))); EXPECT_STREQ(quiet ? "0" : "1", field); ASSERT_EQ(0, tracefs_extract_field( buf, REGEX_DENY_PERMISSION_NAMESPACE(TRACE_TASK), "blockers", field, sizeof(field))); EXPECT_STREQ("use", field); ASSERT_EQ(0, tracefs_extract_field( buf, REGEX_DENY_PERMISSION_NAMESPACE(TRACE_TASK), "namespace_type", field, sizeof(field))); snprintf(expected, sizeof(expected), "0x%llx", (unsigned long long)CLONE_NEWUTS); EXPECT_STREQ(expected, field); ASSERT_EQ(0, tracefs_extract_field( buf, REGEX_DENY_PERMISSION_NAMESPACE(TRACE_TASK), "namespace_id", field, sizeof(field))); EXPECT_EQ(use_setns ? expected_ns_id : 0, strtoull(field, NULL, 10)); free(buf); } TEST_F(ns_trace, deny_create) { if (!self->tracefs_ok) SKIP(return, "tracefs not available"); test_ns_trace_denial(_metadata, false, false); } TEST_F(ns_trace, deny_create_quiet) { if (!self->tracefs_ok) SKIP(return, "tracefs not available"); test_ns_trace_denial(_metadata, false, true); } TEST_F(ns_trace, deny_setns) { if (!self->tracefs_ok) SKIP(return, "tracefs not available"); test_ns_trace_denial(_metadata, true, false); } /* clang-format off */ FIXTURE(ns_proc_open) { /* clang-format on */ struct audit_filter audit_filter; int audit_fd; }; FIXTURE_VARIANT(ns_proc_open) { __u64 ns_type; }; /* clang-format off */ FIXTURE_VARIANT_ADD(ns_proc_open, mnt) { /* clang-format on */ .ns_type = CLONE_NEWNS, }; /* clang-format off */ FIXTURE_VARIANT_ADD(ns_proc_open, user) { /* clang-format on */ .ns_type = CLONE_NEWUSER, }; FIXTURE_SETUP(ns_proc_open) { ASSERT_TRUE(is_in_init_user_ns()); disable_caps(_metadata); set_cap(_metadata, CAP_AUDIT_CONTROL); self->audit_fd = audit_init_with_exe_filter(&self->audit_filter); EXPECT_LE(0, self->audit_fd); clear_cap(_metadata, CAP_AUDIT_CONTROL); } FIXTURE_TEARDOWN(ns_proc_open) { set_cap(_metadata, CAP_AUDIT_CONTROL); EXPECT_EQ(0, audit_cleanup(self->audit_fd, &self->audit_filter)); clear_cap(_metadata, CAP_AUDIT_CONTROL); } /* * Opening /proc/self/ns/ only acquires a procfs reference, not membership * or an acquired fd of the kind LANDLOCK_PERMISSION_NAMESPACE_USE gates. * Verify the open is unrestricted even when the permission is handled with no * rules. */ TEST_F(ns_proc_open, open_unrestricted) { char proc_path[NS_PROC_PATH_MAX]; struct audit_records records = {}; int ruleset_fd, fd; ASSERT_TRUE( ns_is_supported(variant->ns_type, proc_path, sizeof(proc_path))) { TH_LOG("Namespace type 0x%llx not supported", (unsigned long long)variant->ns_type); } ruleset_fd = create_ns_ruleset(); ASSERT_LE(0, ruleset_fd); enforce_ruleset(_metadata, ruleset_fd); EXPECT_EQ(0, close(ruleset_fd)); fd = open(proc_path, O_RDONLY); ASSERT_LE(0, fd) { TH_LOG("open(%s) failed: %s", proc_path, strerror(errno)); } /* No Landlock denial. */ EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); EXPECT_EQ(0, records.access); EXPECT_EQ(0, close(fd)); } TEST_HARNESS_MAIN