| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Landlock tests - Capability restriction | |
| * | |
| * Copyright © 2026 Cloudflare, Inc. | |
| */ | |
| #define _GNU_SOURCE | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <linux/capability.h> | |
| #include <linux/landlock.h> | |
| #include <sched.h> | |
| #include <stdio.h> | |
| #include <string.h> | |
| #include <sys/mount.h> | |
| #include <sys/wait.h> | |
| #include <sys/xattr.h> | |
| #include <unistd.h> | |
| #include "audit.h" | |
| #include "common.h" | |
| #include "trace.h" | |
| #define TRACE_TASK "cap_test" | |
| static bool list_has_xattr(const char *list, ssize_t len, const char *name) | |
| { | |
| const char *p; | |
| for (p = list; p < list + len; p += strlen(p) + 1) | |
| if (strcmp(p, name) == 0) | |
| return true; | |
| return false; | |
| } | |
| static int create_cap_ruleset(void) | |
| { | |
| const struct landlock_ruleset_attr attr = { | |
| .handled_permissions = LANDLOCK_PERMISSION_CAPABILITY_USE, | |
| }; | |
| return landlock_create_ruleset(&attr, sizeof(attr), 0); | |
| } | |
| static int add_cap_rule(int ruleset_fd, __u64 cap) | |
| { | |
| const struct landlock_capability_attr attr = { | |
| .permissions = LANDLOCK_PERMISSION_CAPABILITY_USE, | |
| .allowed_capabilities = (1ULL << cap), | |
| }; | |
| return landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY, &attr, | |
| 0); | |
| } | |
| static int add_cap_rule_full(int ruleset_fd, __u64 allowed, __u64 quiet) | |
| { | |
| const struct landlock_capability_attr attr = { | |
| .permissions = LANDLOCK_PERMISSION_CAPABILITY_USE, | |
| .allowed_capabilities = allowed, | |
| .quiet_capabilities = quiet, | |
| }; | |
| return landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY, &attr, | |
| 0); | |
| } | |
| TEST(add_rule_bad_attr) | |
| { | |
| const struct landlock_ruleset_attr ns_only_attr = { | |
| .handled_permissions = LANDLOCK_PERMISSION_NAMESPACE_USE, | |
| }; | |
| int ruleset_fd; | |
| struct landlock_capability_attr attr = {}; | |
| ruleset_fd = create_cap_ruleset(); | |
| ASSERT_LE(0, ruleset_fd); | |
| /* Invalid rule pointer returns EFAULT. */ | |
| ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY, | |
| NULL, 0)); | |
| ASSERT_EQ(EFAULT, errno); | |
| /* Empty permissions selector returns ENOMSG. */ | |
| attr.permissions = 0; | |
| attr.allowed_capabilities = (1ULL << CAP_NET_RAW); | |
| ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY, | |
| &attr, 0)); | |
| ASSERT_EQ(ENOMSG, errno); | |
| /* Useless rule: neither allowed nor quiet capabilities set. */ | |
| attr.permissions = LANDLOCK_PERMISSION_CAPABILITY_USE; | |
| attr.allowed_capabilities = 0; | |
| attr.quiet_capabilities = 0; | |
| ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY, | |
| &attr, 0)); | |
| ASSERT_EQ(ENOMSG, errno); | |
| /* Quiet-only rule (empty allowed set) is legal. */ | |
| attr.permissions = LANDLOCK_PERMISSION_CAPABILITY_USE; | |
| attr.allowed_capabilities = 0; | |
| attr.quiet_capabilities = (1ULL << CAP_NET_RAW); | |
| ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY, | |
| &attr, 0)); | |
| /* Allow and quiet different members in the same rule. */ | |
| attr.permissions = LANDLOCK_PERMISSION_CAPABILITY_USE; | |
| attr.allowed_capabilities = (1ULL << CAP_SYS_ADMIN); | |
| attr.quiet_capabilities = (1ULL << CAP_NET_RAW); | |
| ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY, | |
| &attr, 0)); | |
| /* Valid capability selector plus an extra unhandled selector bit. */ | |
| attr.permissions = LANDLOCK_PERMISSION_CAPABILITY_USE | | |
| LANDLOCK_PERMISSION_NAMESPACE_USE; | |
| attr.allowed_capabilities = (1ULL << CAP_NET_RAW); | |
| ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY, | |
| &attr, 0)); | |
| ASSERT_EQ(EINVAL, errno); | |
| /* permissions with wrong type. */ | |
| attr.permissions = LANDLOCK_PERMISSION_NAMESPACE_USE; | |
| attr.allowed_capabilities = (1ULL << CAP_NET_RAW); | |
| ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY, | |
| &attr, 0)); | |
| ASSERT_EQ(EINVAL, errno); | |
| /* | |
| * Unknown capability bits (e.g. bit 63) are silently accepted for | |
| * forward compatibility. Only known bits are stored. | |
| */ | |
| attr.permissions = LANDLOCK_PERMISSION_CAPABILITY_USE; | |
| attr.allowed_capabilities = 1ULL << 63; | |
| attr.quiet_capabilities = 0; | |
| ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY, | |
| &attr, 0)); | |
| /* | |
| * LANDLOCK_ADD_RULE_QUIET (and any other flag) is filesystem/network | |
| * only and must be rejected for capability rules, even when every attr | |
| * field is otherwise valid. | |
| */ | |
| attr.permissions = LANDLOCK_PERMISSION_CAPABILITY_USE; | |
| attr.allowed_capabilities = (1ULL << CAP_NET_RAW); | |
| ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY, | |
| &attr, LANDLOCK_ADD_RULE_QUIET)); | |
| ASSERT_EQ(EINVAL, errno); | |
| EXPECT_EQ(0, close(ruleset_fd)); | |
| /* | |
| * Ruleset handles LANDLOCK_PERMISSION_NAMESPACE_USE but not | |
| * LANDLOCK_PERMISSION_CAPABILITY_USE: adding a capability rule must be | |
| * rejected. | |
| */ | |
| ruleset_fd = | |
| landlock_create_ruleset(&ns_only_attr, sizeof(ns_only_attr), 0); | |
| ASSERT_LE(0, ruleset_fd); | |
| attr.permissions = LANDLOCK_PERMISSION_CAPABILITY_USE; | |
| attr.allowed_capabilities = (1ULL << CAP_NET_RAW); | |
| ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY, | |
| &attr, 0)); | |
| ASSERT_EQ(EINVAL, errno); | |
| EXPECT_EQ(0, close(ruleset_fd)); | |
| } | |
| /* | |
| * Unknown capability values above CAP_LAST_CAP are silently accepted | |
| * (allow-list: they have no effect since the kernel never checks them). | |
| */ | |
| TEST(add_rule_unknown) | |
| { | |
| int ruleset_fd; | |
| struct landlock_capability_attr attr = { | |
| .permissions = LANDLOCK_PERMISSION_CAPABILITY_USE, | |
| }; | |
| ruleset_fd = create_cap_ruleset(); | |
| ASSERT_LE(0, ruleset_fd); | |
| /* Just above CAP_LAST_CAP should succeed. */ | |
| attr.allowed_capabilities = (1ULL << (CAP_LAST_CAP + 1)); | |
| ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY, | |
| &attr, 0)); | |
| /* High values (below bit 63) should succeed. */ | |
| attr.allowed_capabilities = (1ULL << 62); | |
| ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY, | |
| &attr, 0)); | |
| EXPECT_EQ(0, close(ruleset_fd)); | |
| } | |
| /* | |
| * A rule that lists only capability bits unknown to the running kernel is | |
| * accepted by landlock_add_rule() but has no runtime effect: once the domain is | |
| * enforced, any actual CAP_* capability 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 capability 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_CAPABILITY_USE, | |
| }; | |
| struct landlock_capability_attr attr = { | |
| .permissions = LANDLOCK_PERMISSION_CAPABILITY_USE, | |
| /* Only unknown bits above CAP_LAST_CAP. */ | |
| .allowed_capabilities = (1ULL << (CAP_LAST_CAP + 1)) | | |
| (1ULL << 62), | |
| }; | |
| 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_CAPABILITY, | |
| &attr, 0)); | |
| /* | |
| * Isolate hostname changes and establish a baseline: with CAP_SYS_ADMIN | |
| * and absent the domain, sethostname(2) 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)); | |
| ASSERT_EQ(0, sethostname("baseline", 8)); | |
| clear_cap(_metadata, CAP_SYS_ADMIN); | |
| enforce_ruleset(_metadata, ruleset_fd); | |
| EXPECT_EQ(0, close(ruleset_fd)); | |
| /* | |
| * CAP_SYS_ADMIN is a real, known capability but was not authorised by | |
| * the rule above; deny-by-default applies. sethostname(2) requires | |
| * CAP_SYS_ADMIN. | |
| */ | |
| set_cap(_metadata, CAP_SYS_ADMIN); | |
| EXPECT_EQ(-1, sethostname("test", 4)); | |
| EXPECT_EQ(EPERM, errno); | |
| clear_cap(_metadata, CAP_SYS_ADMIN); | |
| } | |
| /* clang-format off */ | |
| FIXTURE(cap_enforce) {}; | |
| /* clang-format on */ | |
| FIXTURE_VARIANT(cap_enforce) | |
| { | |
| const bool is_sandboxed; | |
| const bool handle_caps; | |
| const __u64 allowed_cap; | |
| const int expected_sysadmin; | |
| const int expected_chroot; | |
| }; | |
| /* | |
| * Unsandboxed baseline: no Landlock domain is enforced. Both capabilities | |
| * should work normally. | |
| */ | |
| /* clang-format off */ | |
| FIXTURE_VARIANT_ADD(cap_enforce, unsandboxed) { | |
| .is_sandboxed = false, | |
| .handle_caps = false, | |
| .allowed_cap = 0, | |
| .expected_sysadmin = 0, | |
| .expected_chroot = 0, | |
| }; | |
| /* clang-format on */ | |
| /* | |
| * Denied: capabilities are handled but no rule allows them. All capability | |
| * checks must be denied by Landlock even if the capability is effective. | |
| */ | |
| /* clang-format off */ | |
| FIXTURE_VARIANT_ADD(cap_enforce, denied) { | |
| .is_sandboxed = true, | |
| .handle_caps = true, | |
| .allowed_cap = 0, | |
| .expected_sysadmin = EPERM, | |
| .expected_chroot = EPERM, | |
| }; | |
| /* clang-format on */ | |
| /* | |
| * Allowed: CAP_SYS_ADMIN is allowed by rule, CAP_SYS_CHROOT is not. Only the | |
| * explicitly allowed capability should succeed. | |
| */ | |
| /* clang-format off */ | |
| FIXTURE_VARIANT_ADD(cap_enforce, allowed) { | |
| .is_sandboxed = true, | |
| .handle_caps = true, | |
| .allowed_cap = CAP_SYS_ADMIN, | |
| .expected_sysadmin = 0, | |
| .expected_chroot = EPERM, | |
| }; | |
| /* clang-format on */ | |
| /* | |
| * Unhandled: the ruleset does not handle LANDLOCK_PERMISSION_CAPABILITY_USE at | |
| * all (only handles FS access). Both capabilities should work since the domain | |
| * does not restrict them. | |
| */ | |
| /* clang-format off */ | |
| FIXTURE_VARIANT_ADD(cap_enforce, unhandled) { | |
| .is_sandboxed = true, | |
| .handle_caps = false, | |
| .allowed_cap = 0, | |
| .expected_sysadmin = 0, | |
| .expected_chroot = 0, | |
| }; | |
| /* clang-format on */ | |
| FIXTURE_SETUP(cap_enforce) | |
| { | |
| disable_caps(_metadata); | |
| } | |
| FIXTURE_TEARDOWN(cap_enforce) | |
| { | |
| } | |
| /* | |
| * Capability enforcement: tests the four fundamental enforcement scenarios | |
| * (unsandboxed baseline, denied, allowed, unhandled) using two independent | |
| * capability checks (sethostname for CAP_SYS_ADMIN, chroot for CAP_SYS_CHROOT). | |
| */ | |
| TEST_F(cap_enforce, use) | |
| { | |
| int ruleset_fd; | |
| /* Isolate hostname changes from other tests. */ | |
| set_cap(_metadata, CAP_SYS_ADMIN); | |
| ASSERT_EQ(0, unshare(CLONE_NEWUTS)); | |
| clear_cap(_metadata, CAP_SYS_ADMIN); | |
| if (variant->is_sandboxed) { | |
| if (variant->handle_caps) { | |
| ruleset_fd = create_cap_ruleset(); | |
| } else { | |
| const struct landlock_ruleset_attr attr = { | |
| .handled_access_fs = | |
| LANDLOCK_ACCESS_FS_READ_FILE, | |
| }; | |
| ruleset_fd = | |
| landlock_create_ruleset(&attr, sizeof(attr), 0); | |
| } | |
| ASSERT_LE(0, ruleset_fd); | |
| if (variant->allowed_cap) | |
| ASSERT_EQ(0, add_cap_rule(ruleset_fd, | |
| variant->allowed_cap)); | |
| enforce_ruleset(_metadata, ruleset_fd); | |
| EXPECT_EQ(0, close(ruleset_fd)); | |
| } | |
| /* Test CAP_SYS_ADMIN via sethostname. */ | |
| set_cap(_metadata, CAP_SYS_ADMIN); | |
| if (variant->expected_sysadmin) { | |
| EXPECT_EQ(-1, sethostname("test", 4)); | |
| EXPECT_EQ(variant->expected_sysadmin, errno); | |
| } else { | |
| EXPECT_EQ(0, sethostname("test", 4)); | |
| } | |
| clear_cap(_metadata, CAP_SYS_ADMIN); | |
| /* Test CAP_SYS_CHROOT via chroot. */ | |
| set_cap(_metadata, CAP_SYS_CHROOT); | |
| if (variant->expected_chroot) { | |
| EXPECT_EQ(-1, chroot("/")); | |
| EXPECT_EQ(variant->expected_chroot, errno); | |
| } else { | |
| EXPECT_EQ(0, chroot("/")); | |
| } | |
| clear_cap(_metadata, CAP_SYS_CHROOT); | |
| } | |
| /* | |
| * Layer stacking: both layers must allow CAP_SYS_ADMIN for the capability to be | |
| * exercisable. Variants cover the three per-layer combinations that exercise | |
| * distinct walker paths (allow/deny, allow/allow, deny/allow), an unsandboxed | |
| * baseline, and a mixed-layer case where one layer does not handle | |
| * LANDLOCK_PERMISSION_CAPABILITY_USE at all. | |
| */ | |
| /* clang-format off */ | |
| FIXTURE(cap_stacking) {}; | |
| /* clang-format on */ | |
| FIXTURE_VARIANT(cap_stacking) | |
| { | |
| const bool is_sandboxed; | |
| const bool first_layer_allows; | |
| const bool second_layer_allows; | |
| const bool second_layer_is_fs_only; | |
| const int expected_sysadmin; | |
| const int expected_chroot; | |
| }; | |
| /* | |
| * Unsandboxed baseline: no Landlock layers are stacked. Both capabilities | |
| * should work normally. | |
| */ | |
| /* clang-format off */ | |
| FIXTURE_VARIANT_ADD(cap_stacking, unsandboxed) { | |
| .is_sandboxed = false, | |
| .first_layer_allows = false, | |
| .second_layer_allows = false, | |
| .expected_sysadmin = 0, | |
| .expected_chroot = 0, | |
| }; | |
| /* clang-format on */ | |
| /* Layer 1 allows CAP_SYS_ADMIN, layer 2 denies -> denied. */ | |
| /* clang-format off */ | |
| FIXTURE_VARIANT_ADD(cap_stacking, deny) { | |
| .is_sandboxed = true, | |
| .first_layer_allows = true, | |
| .second_layer_allows = false, | |
| .expected_sysadmin = EPERM, | |
| .expected_chroot = EPERM, | |
| }; | |
| /* clang-format on */ | |
| /* Both layers allow CAP_SYS_ADMIN -> sysadmin succeeds, chroot still denied. */ | |
| /* clang-format off */ | |
| FIXTURE_VARIANT_ADD(cap_stacking, allow) { | |
| .is_sandboxed = true, | |
| .first_layer_allows = true, | |
| .second_layer_allows = true, | |
| .expected_sysadmin = 0, | |
| .expected_chroot = EPERM, | |
| }; | |
| /* clang-format on */ | |
| /* | |
| * Layer 1 denies CAP_SYS_ADMIN, layer 2 allows -> still denied: a child layer | |
| * cannot grant what an ancestor layer withheld. Complements the | |
| * parent-allows/child-denies variant; together they verify the walker checks | |
| * both layers and accepts only the (allow, allow) cell. | |
| */ | |
| /* clang-format off */ | |
| FIXTURE_VARIANT_ADD(cap_stacking, parent_denies) { | |
| .is_sandboxed = true, | |
| .first_layer_allows = false, | |
| .second_layer_allows = true, | |
| .expected_sysadmin = EPERM, | |
| .expected_chroot = EPERM, | |
| }; | |
| /* clang-format on */ | |
| /* | |
| * Mixed layers: first layer handles LANDLOCK_PERMISSION_CAPABILITY_USE (denies | |
| * all caps), second layer is FS-only (does not handle it). The permission | |
| * walker iterates from youngest (layer 1) to oldest (layer 0) and must skip the | |
| * FS-only layer to find the denying layer beneath. | |
| */ | |
| /* clang-format off */ | |
| FIXTURE_VARIANT_ADD(cap_stacking, mixed_layers) { | |
| /* clang-format on */ | |
| .is_sandboxed = true, | |
| .first_layer_allows = false, | |
| .second_layer_is_fs_only = true, | |
| .expected_sysadmin = EPERM, | |
| .expected_chroot = EPERM, | |
| }; | |
| FIXTURE_SETUP(cap_stacking) | |
| { | |
| disable_caps(_metadata); | |
| /* Isolate every successful sethostname() from the other tests. */ | |
| set_cap(_metadata, CAP_SYS_ADMIN); | |
| ASSERT_EQ(0, unshare(CLONE_NEWUTS)); | |
| clear_cap(_metadata, CAP_SYS_ADMIN); | |
| } | |
| FIXTURE_TEARDOWN(cap_stacking) | |
| { | |
| } | |
| TEST_F(cap_stacking, two_layers) | |
| { | |
| int ruleset_fd; | |
| if (variant->is_sandboxed) { | |
| /* | |
| * First layer: handles LANDLOCK_PERMISSION_CAPABILITY_USE; rule | |
| * added per variant. | |
| */ | |
| ruleset_fd = create_cap_ruleset(); | |
| ASSERT_LE(0, ruleset_fd); | |
| if (variant->first_layer_allows) | |
| ASSERT_EQ(0, add_cap_rule(ruleset_fd, CAP_SYS_ADMIN)); | |
| enforce_ruleset(_metadata, ruleset_fd); | |
| EXPECT_EQ(0, close(ruleset_fd)); | |
| if (variant->second_layer_is_fs_only) { | |
| /* | |
| * Second layer: FS-only (does not handle | |
| * LANDLOCK_PERMISSION_CAPABILITY_USE). The permission | |
| * walker must skip this layer. | |
| */ | |
| const struct landlock_ruleset_attr fs_attr = { | |
| .handled_access_fs = | |
| LANDLOCK_ACCESS_FS_READ_FILE, | |
| }; | |
| ruleset_fd = landlock_create_ruleset( | |
| &fs_attr, sizeof(fs_attr), 0); | |
| } else { | |
| /* Second layer: cap allow or deny. */ | |
| ruleset_fd = create_cap_ruleset(); | |
| if (variant->second_layer_allows) | |
| ASSERT_EQ(0, add_cap_rule(ruleset_fd, | |
| CAP_SYS_ADMIN)); | |
| } | |
| ASSERT_LE(0, ruleset_fd); | |
| enforce_ruleset(_metadata, ruleset_fd); | |
| EXPECT_EQ(0, close(ruleset_fd)); | |
| } | |
| /* Test CAP_SYS_ADMIN via sethostname. */ | |
| set_cap(_metadata, CAP_SYS_ADMIN); | |
| if (variant->expected_sysadmin) { | |
| EXPECT_EQ(-1, sethostname("test", 4)); | |
| EXPECT_EQ(variant->expected_sysadmin, errno); | |
| } else { | |
| EXPECT_EQ(0, sethostname("test", 4)); | |
| } | |
| clear_cap(_metadata, CAP_SYS_ADMIN); | |
| /* Test CAP_SYS_CHROOT via chroot. */ | |
| set_cap(_metadata, CAP_SYS_CHROOT); | |
| if (variant->expected_chroot) { | |
| EXPECT_EQ(-1, chroot("/")); | |
| EXPECT_EQ(variant->expected_chroot, errno); | |
| } else { | |
| EXPECT_EQ(0, chroot("/")); | |
| } | |
| clear_cap(_metadata, CAP_SYS_CHROOT); | |
| } | |
| /* | |
| * Verify that LANDLOCK_PERMISSION_CAPABILITY_USE enforces when the domain is | |
| * applied without no_new_privs, using CAP_SYS_ADMIN for | |
| * landlock_restrict_self() authorization instead. Privileged processes (e.g. | |
| * container managers) can sandbox themselves this way. | |
| */ | |
| TEST(cap_without_nnp) | |
| { | |
| int ruleset_fd; | |
| disable_caps(_metadata); | |
| /* Isolate an unexpected successful sethostname(). */ | |
| set_cap(_metadata, CAP_SYS_ADMIN); | |
| ASSERT_EQ(0, unshare(CLONE_NEWUTS)); | |
| clear_cap(_metadata, CAP_SYS_ADMIN); | |
| ruleset_fd = create_cap_ruleset(); | |
| ASSERT_LE(0, ruleset_fd); | |
| /* Allow CAP_SYS_CHROOT but not CAP_SYS_ADMIN. */ | |
| ASSERT_EQ(0, add_cap_rule(ruleset_fd, CAP_SYS_CHROOT)); | |
| /* | |
| * Enforce WITHOUT NNP: landlock_restrict_self() succeeds when the | |
| * caller has CAP_SYS_ADMIN (checked before the new domain takes | |
| * effect). | |
| */ | |
| set_cap(_metadata, CAP_SYS_ADMIN); | |
| ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0)); | |
| EXPECT_EQ(0, close(ruleset_fd)); | |
| /* | |
| * CAP_SYS_ADMIN is still in effective set but Landlock denies it: | |
| * cap_capable() returns 0, then hook_capable() returns -EPERM. | |
| */ | |
| EXPECT_EQ(-1, sethostname("test", 4)); | |
| EXPECT_EQ(EPERM, errno); | |
| clear_cap(_metadata, CAP_SYS_ADMIN); | |
| /* CAP_SYS_CHROOT is allowed by the rule. */ | |
| set_cap(_metadata, CAP_SYS_CHROOT); | |
| EXPECT_EQ(0, chroot("/")); | |
| clear_cap(_metadata, CAP_SYS_CHROOT); | |
| } | |
| /* | |
| * Verify that capabilities gained through user namespace ownership are still | |
| * restricted by LANDLOCK_PERMISSION_CAPABILITY_USE. When a process creates a | |
| * user namespace, the kernel grants CAP_FULL_SET in the new namespace via | |
| * cap_capable_helper()'s ownership bypass. Landlock's hook_capable() must | |
| * still deny capabilities not in the allowed set, ensuring that user namespace | |
| * creation cannot be used to escape capability restrictions. | |
| */ | |
| TEST(cap_userns_ownership_bypass) | |
| { | |
| pid_t child; | |
| int status; | |
| child = fork(); | |
| ASSERT_LE(0, child); | |
| if (child == 0) { | |
| int ruleset_fd; | |
| disable_caps(_metadata); | |
| ruleset_fd = create_cap_ruleset(); | |
| ASSERT_LE(0, ruleset_fd); | |
| /* Allow CAP_SYS_ADMIN only. */ | |
| ASSERT_EQ(0, add_cap_rule(ruleset_fd, CAP_SYS_ADMIN)); | |
| enforce_ruleset(_metadata, ruleset_fd); | |
| EXPECT_EQ(0, close(ruleset_fd)); | |
| /* | |
| * Create a user namespace. This is unprivileged and does not | |
| * require capabilities. LANDLOCK_PERMISSION_NAMESPACE_USE is | |
| * not handled so namespace creation is unrestricted. | |
| */ | |
| ASSERT_EQ(0, unshare(CLONE_NEWUSER)); | |
| /* | |
| * After unshare(CLONE_NEWUSER), the kernel set cap_effective = | |
| * CAP_FULL_SET in the new namespace. Create a UTS namespace | |
| * (requires CAP_SYS_ADMIN in the new user NS). Landlock allows | |
| * CAP_SYS_ADMIN. | |
| */ | |
| ASSERT_EQ(0, unshare(CLONE_NEWUTS)) | |
| { | |
| TH_LOG("unshare(CLONE_NEWUTS): %s", strerror(errno)); | |
| } | |
| /* | |
| * sethostname checks against uts_ns->user_ns, which is now the | |
| * new user NS. CAP_SYS_ADMIN is allowed. | |
| */ | |
| EXPECT_EQ(0, sethostname("test", 4)); | |
| /* | |
| * chroot checks against current_user_ns(), which is the new | |
| * user NS. The process has CAP_SYS_CHROOT in cap_effective | |
| * (from user NS creation), so cap_capable() returns 0. But | |
| * Landlock denies because no rule allows CAP_SYS_CHROOT. | |
| */ | |
| EXPECT_EQ(-1, chroot("/")); | |
| EXPECT_EQ(EPERM, errno); | |
| _exit(_metadata->exit_code); | |
| return; | |
| } | |
| ASSERT_EQ(child, waitpid(child, &status, 0)); | |
| if (WIFSIGNALED(status) || !WIFEXITED(status) || | |
| WEXITSTATUS(status) != EXIT_SUCCESS) | |
| _metadata->exit_code = KSFT_FAIL; | |
| } | |
| /* | |
| * Verify that capability restrictions do not depend on which user namespace the | |
| * kernel checks. An actor in the initial user namespace can enter a descendant | |
| * user namespace with CAP_SYS_ADMIN. With the same Linux capability state, | |
| * handling capability use without an allow rule must deny that target-namespace | |
| * check even though the target differs from current_user_ns(). | |
| */ | |
| TEST(cap_target_userns_independent) | |
| { | |
| int sockets[2], status, target_userns_fd; | |
| pid_t actor, target; | |
| disable_caps(_metadata); | |
| ASSERT_EQ(0, | |
| socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sockets)); | |
| target = fork(); | |
| ASSERT_LE(0, target); | |
| if (target == 0) { | |
| int fd; | |
| close(sockets[0]); | |
| if (unshare(CLONE_NEWUSER)) | |
| _exit(255); | |
| fd = open("/proc/self/ns/user", O_RDONLY | O_CLOEXEC); | |
| if (fd < 0 || send_fd(sockets[1], fd)) | |
| _exit(255); | |
| close(fd); | |
| close(sockets[1]); | |
| _exit(EXIT_SUCCESS); | |
| } | |
| close(sockets[1]); | |
| target_userns_fd = recv_fd(sockets[0]); | |
| ASSERT_LE(0, target_userns_fd); | |
| EXPECT_EQ(0, close(sockets[0])); | |
| ASSERT_EQ(target, waitpid(target, &status, 0)); | |
| ASSERT_TRUE(WIFEXITED(status)); | |
| ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); | |
| set_cap(_metadata, CAP_SYS_ADMIN); | |
| for (int sandboxed = 0; sandboxed <= 1; sandboxed++) { | |
| actor = fork(); | |
| ASSERT_LE(0, actor); | |
| if (actor == 0) { | |
| int ret, saved_errno; | |
| if (sandboxed) { | |
| int ruleset_fd = create_cap_ruleset(); | |
| if (ruleset_fd < 0 || | |
| prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) || | |
| landlock_restrict_self(ruleset_fd, 0) || | |
| close(ruleset_fd)) | |
| _exit(255); | |
| } | |
| ret = setns(target_userns_fd, CLONE_NEWUSER); | |
| saved_errno = errno; | |
| _exit(ret ? saved_errno : EXIT_SUCCESS); | |
| } | |
| ASSERT_EQ(actor, waitpid(actor, &status, 0)); | |
| ASSERT_TRUE(WIFEXITED(status)); | |
| EXPECT_EQ(sandboxed ? EPERM : EXIT_SUCCESS, | |
| WEXITSTATUS(status)); | |
| } | |
| clear_cap(_metadata, CAP_SYS_ADMIN); | |
| EXPECT_EQ(0, close(target_userns_fd)); | |
| } | |
| /* Audit tests */ | |
| static int matches_log_cap(int audit_fd, int cap_number) | |
| { | |
| static const char log_template[] = REGEX_LANDLOCK_PREFIX | |
| " blockers=capability\\.use capability=%d $"; | |
| char log_match[sizeof(log_template) + 10]; | |
| int log_match_len; | |
| log_match_len = snprintf(log_match, sizeof(log_match), log_template, | |
| cap_number); | |
| if (log_match_len >= sizeof(log_match)) | |
| return -E2BIG; | |
| return audit_match_record(audit_fd, AUDIT_LANDLOCK_ACCESS, log_match, | |
| NULL); | |
| } | |
| FIXTURE(cap_audit) | |
| { | |
| struct audit_filter audit_filter; | |
| int audit_fd; | |
| }; | |
| FIXTURE_SETUP(cap_audit) | |
| { | |
| ASSERT_TRUE(is_in_init_user_ns()); | |
| disable_caps(_metadata); | |
| /* Isolate successful sethostname() calls. */ | |
| set_cap(_metadata, CAP_SYS_ADMIN); | |
| ASSERT_EQ(0, unshare(CLONE_NEWUTS)); | |
| clear_cap(_metadata, CAP_SYS_ADMIN); | |
| 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(cap_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 capability produces the expected audit record with the | |
| * correct capability number and blocker string. | |
| */ | |
| TEST_F(cap_audit, denied) | |
| { | |
| struct audit_records records = {}; | |
| int ruleset_fd; | |
| __u64 domain_id; | |
| /* Baseline: chroot works before Landlock. */ | |
| set_cap(_metadata, CAP_SYS_CHROOT); | |
| ASSERT_EQ(0, chroot("/")); | |
| clear_cap(_metadata, CAP_SYS_CHROOT); | |
| ruleset_fd = create_cap_ruleset(); | |
| ASSERT_LE(0, ruleset_fd); | |
| /* Allow CAP_AUDIT_CONTROL for child-side audit cleanup. */ | |
| ASSERT_EQ(0, add_cap_rule(ruleset_fd, CAP_AUDIT_CONTROL)); | |
| enforce_ruleset(_metadata, ruleset_fd); | |
| EXPECT_EQ(0, close(ruleset_fd)); | |
| /* Deny CAP_SYS_CHROOT (no allow rule). */ | |
| set_cap(_metadata, CAP_SYS_CHROOT); | |
| EXPECT_EQ(-1, chroot("/")); | |
| EXPECT_EQ(EPERM, errno); | |
| clear_cap(_metadata, CAP_SYS_CHROOT); | |
| EXPECT_EQ(0, matches_log_cap(self->audit_fd, CAP_SYS_CHROOT)); | |
| /* | |
| * 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(cap_audit, allowed) | |
| { | |
| struct audit_records records = {}; | |
| int ruleset_fd; | |
| ruleset_fd = create_cap_ruleset(); | |
| ASSERT_LE(0, ruleset_fd); | |
| ASSERT_EQ(0, add_cap_rule(ruleset_fd, CAP_SYS_ADMIN)); | |
| /* Allow CAP_AUDIT_CONTROL for child-side audit cleanup. */ | |
| ASSERT_EQ(0, add_cap_rule(ruleset_fd, CAP_AUDIT_CONTROL)); | |
| enforce_ruleset(_metadata, ruleset_fd); | |
| EXPECT_EQ(0, close(ruleset_fd)); | |
| set_cap(_metadata, CAP_SYS_ADMIN); | |
| EXPECT_EQ(0, sethostname("test", 4)); | |
| 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); | |
| } | |
| /* An allowed capability remains allowed when the same rule also quiets it. */ | |
| TEST_F(cap_audit, allow_and_quiet_same_member) | |
| { | |
| struct audit_records records = {}; | |
| int ruleset_fd; | |
| ruleset_fd = create_cap_ruleset(); | |
| ASSERT_LE(0, ruleset_fd); | |
| ASSERT_EQ(0, add_cap_rule_full(ruleset_fd, | |
| (1ULL << CAP_SYS_ADMIN) | | |
| (1ULL << CAP_AUDIT_CONTROL), | |
| 1ULL << CAP_SYS_ADMIN)); | |
| enforce_ruleset(_metadata, ruleset_fd); | |
| EXPECT_EQ(0, close(ruleset_fd)); | |
| set_cap(_metadata, CAP_SYS_ADMIN); | |
| EXPECT_EQ(0, sethostname("test", 4)); | |
| clear_cap(_metadata, CAP_SYS_ADMIN); | |
| /* | |
| * Quiet is inert for the allowed member: nothing is denied or logged. | |
| */ | |
| EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); | |
| EXPECT_EQ(0, records.access); | |
| } | |
| /* | |
| * A quieted capability denial is still denied (EPERM); only its audit record is | |
| * suppressed. Quiet-only rule (empty allowed set). | |
| */ | |
| TEST_F(cap_audit, quieted) | |
| { | |
| struct audit_records records = {}; | |
| int ruleset_fd; | |
| __u64 domain_id; | |
| ruleset_fd = create_cap_ruleset(); | |
| ASSERT_LE(0, ruleset_fd); | |
| /* Allow CAP_AUDIT_CONTROL for child-side audit cleanup. */ | |
| ASSERT_EQ(0, add_cap_rule(ruleset_fd, CAP_AUDIT_CONTROL)); | |
| ASSERT_EQ(0, | |
| add_cap_rule_full(ruleset_fd, 0, (1ULL << CAP_SYS_CHROOT))); | |
| enforce_ruleset(_metadata, ruleset_fd); | |
| EXPECT_EQ(0, close(ruleset_fd)); | |
| set_cap(_metadata, CAP_SYS_CHROOT); | |
| EXPECT_EQ(-1, chroot("/")); | |
| EXPECT_EQ(EPERM, errno); | |
| clear_cap(_metadata, CAP_SYS_CHROOT); | |
| /* | |
| * Positive control: CAP_SYS_ADMIN is denied but not quieted, so its | |
| * denial IS logged. This proves quieting is per-member, not global. | |
| */ | |
| set_cap(_metadata, CAP_SYS_ADMIN); | |
| EXPECT_EQ(-1, sethostname("test", 4)); | |
| EXPECT_EQ(EPERM, errno); | |
| clear_cap(_metadata, CAP_SYS_ADMIN); | |
| /* | |
| * The quieted CAP_SYS_CHROOT denial is suppressed; the CAP_SYS_ADMIN | |
| * denial is logged. | |
| */ | |
| EXPECT_EQ(0, matches_log_cap(self->audit_fd, CAP_SYS_ADMIN)); | |
| 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); | |
| } | |
| /* | |
| * Only the youngest denying layer decides quieting: a parent cannot silence a | |
| * denial by a deeper layer that handles but does not quiet the capability. | |
| */ | |
| TEST_F(cap_audit, quiet_youngest_layer_wins) | |
| { | |
| struct audit_records records = {}; | |
| int ruleset_fd; | |
| __u64 domain_id; | |
| /* Baseline: commoncap allows chroot before Landlock. */ | |
| set_cap(_metadata, CAP_SYS_CHROOT); | |
| ASSERT_EQ(0, chroot("/")); | |
| clear_cap(_metadata, CAP_SYS_CHROOT); | |
| /* Parent layer: denies and quiets CAP_SYS_CHROOT. */ | |
| ruleset_fd = create_cap_ruleset(); | |
| ASSERT_LE(0, ruleset_fd); | |
| ASSERT_EQ(0, add_cap_rule_full(ruleset_fd, 1ULL << CAP_AUDIT_CONTROL, | |
| 1ULL << CAP_SYS_CHROOT)); | |
| enforce_ruleset(_metadata, ruleset_fd); | |
| EXPECT_EQ(0, close(ruleset_fd)); | |
| /* Child layer: denies CAP_SYS_CHROOT without quieting it. */ | |
| ruleset_fd = create_cap_ruleset(); | |
| ASSERT_LE(0, ruleset_fd); | |
| ASSERT_EQ(0, add_cap_rule(ruleset_fd, CAP_AUDIT_CONTROL)); | |
| enforce_ruleset(_metadata, ruleset_fd); | |
| EXPECT_EQ(0, close(ruleset_fd)); | |
| set_cap(_metadata, CAP_SYS_CHROOT); | |
| EXPECT_EQ(-1, chroot("/")); | |
| EXPECT_EQ(EPERM, errno); | |
| clear_cap(_metadata, CAP_SYS_CHROOT); | |
| /* | |
| * The child is the youngest denier and does not quiet: log the denial. | |
| */ | |
| EXPECT_EQ(0, matches_log_cap(self->audit_fd, CAP_SYS_CHROOT)); | |
| 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 capability bit has no effect: a rule that quiets only a | |
| * bit above CAP_LAST_CAP still logs the denial of a known capability. | |
| */ | |
| TEST_F(cap_audit, quiet_unknown_bit_no_effect) | |
| { | |
| struct audit_records records = {}; | |
| int ruleset_fd; | |
| __u64 domain_id; | |
| ruleset_fd = create_cap_ruleset(); | |
| ASSERT_LE(0, ruleset_fd); | |
| ASSERT_EQ(0, add_cap_rule(ruleset_fd, CAP_AUDIT_CONTROL)); | |
| /* Quiet only an unknown bit (bit 63 is above CAP_LAST_CAP). */ | |
| ASSERT_EQ(0, add_cap_rule_full(ruleset_fd, 0, (1ULL << 63))); | |
| enforce_ruleset(_metadata, ruleset_fd); | |
| EXPECT_EQ(0, close(ruleset_fd)); | |
| set_cap(_metadata, CAP_SYS_CHROOT); | |
| EXPECT_EQ(-1, chroot("/")); | |
| EXPECT_EQ(EPERM, errno); | |
| clear_cap(_metadata, CAP_SYS_CHROOT); | |
| /* The unknown quiet bit does not suppress the known denial. */ | |
| EXPECT_EQ(0, matches_log_cap(self->audit_fd, CAP_SYS_CHROOT)); | |
| 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 capability check made with CAP_OPT_NOAUDIT is denied by Landlock but not | |
| * logged: hook_capable() emits no Landlock audit record when the caller passes | |
| * CAP_OPT_NOAUDIT, so the denial is silent here even though CAP_SYS_ADMIN is | |
| * neither allowed nor quieted. The probe is the | |
| * ns_capable_noaudit(CAP_SYS_ADMIN) call in simple_xattr_list(), reached | |
| * through tmpfs, which decides whether trusted.* xattrs are visible to | |
| * listxattr(2). | |
| */ | |
| TEST_F(cap_audit, noaudit_probe_not_logged) | |
| { | |
| struct audit_records records = {}; | |
| int ruleset_fd, fd; | |
| __u64 domain_id; | |
| char list[256]; | |
| ssize_t len; | |
| /* | |
| * Private tmpfs holding a trusted.* xattr so its listxattr(2) | |
| * visibility depends only on the CAP_SYS_ADMIN noaudit probe. | |
| */ | |
| set_cap(_metadata, CAP_SYS_ADMIN); | |
| ASSERT_EQ(0, unshare(CLONE_NEWNS)); | |
| ASSERT_EQ(0, mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL)); | |
| ASSERT_EQ(0, mount("test", "/tmp", "tmpfs", 0, NULL)); | |
| fd = open("/tmp/f", O_CREAT | O_RDWR | O_CLOEXEC, 0600); | |
| ASSERT_LE(0, fd); | |
| ASSERT_EQ(0, fsetxattr(fd, "trusted.landlock_test", "x", 1, 0)); | |
| /* Baseline: with CAP_SYS_ADMIN, the trusted xattr is listed. */ | |
| len = flistxattr(fd, list, sizeof(list)); | |
| ASSERT_LE(0, len); | |
| ASSERT_TRUE(list_has_xattr(list, len, "trusted.landlock_test")); | |
| ruleset_fd = create_cap_ruleset(); | |
| ASSERT_LE(0, ruleset_fd); | |
| /* Allow CAP_AUDIT_CONTROL for child-side audit cleanup. */ | |
| ASSERT_EQ(0, add_cap_rule(ruleset_fd, CAP_AUDIT_CONTROL)); | |
| /* CAP_SYS_ADMIN is neither allowed nor quieted. */ | |
| enforce_ruleset(_metadata, ruleset_fd); | |
| EXPECT_EQ(0, close(ruleset_fd)); | |
| /* | |
| * CAP_SYS_ADMIN is still effective, but Landlock denies the noaudit | |
| * probe, so the trusted.* xattr is filtered out of the listing. | |
| */ | |
| len = flistxattr(fd, list, sizeof(list)); | |
| ASSERT_LE(0, len); | |
| EXPECT_FALSE(list_has_xattr(list, len, "trusted.landlock_test")); | |
| EXPECT_EQ(0, close(fd)); | |
| /* The denied noaudit probe leaves no audit record nor denial. */ | |
| EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); | |
| EXPECT_EQ(0, records.access); | |
| /* | |
| * Positive control: a normal (audited) CAP_SYS_ADMIN denial in the same | |
| * domain IS logged. This proves the empty record set above is caused | |
| * by CAP_OPT_NOAUDIT, not by an always-allow or always-silent bug. | |
| */ | |
| EXPECT_EQ(-1, sethostname("test", 4)); | |
| EXPECT_EQ(EPERM, errno); | |
| clear_cap(_metadata, CAP_SYS_ADMIN); | |
| EXPECT_EQ(0, matches_log_cap(self->audit_fd, CAP_SYS_ADMIN)); | |
| 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); | |
| } | |
| /* Trace tests */ | |
| /* clang-format off */ | |
| FIXTURE(cap_trace) { | |
| /* clang-format on */ | |
| int tracefs_ok; | |
| }; | |
| FIXTURE_SETUP(cap_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_CAPABILITY_ENABLE, | |
| true)); | |
| ASSERT_EQ(0, tracefs_enable_event( | |
| TRACEFS_DENY_PERMISSION_CAPABILITY_ENABLE, true)); | |
| ASSERT_EQ(0, tracefs_clear()); | |
| clear_cap(_metadata, CAP_SYS_ADMIN); | |
| } | |
| FIXTURE_TEARDOWN(cap_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_CAPABILITY_ENABLE, false); | |
| tracefs_enable_event(TRACEFS_DENY_PERMISSION_CAPABILITY_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(cap_trace, rule_events) | |
| { | |
| const char *const version_1 = | |
| REGEX_ADD_RULE_CAPABILITY_VERSION(TRACE_TASK, "1"); | |
| const char *const version_2 = | |
| REGEX_ADD_RULE_CAPABILITY_VERSION(TRACE_TASK, "2"); | |
| const char *const version_3 = | |
| REGEX_ADD_RULE_CAPABILITY_VERSION(TRACE_TASK, "3"); | |
| const struct landlock_capability_attr valid_attr = { | |
| .permissions = LANDLOCK_PERMISSION_CAPABILITY_USE, | |
| .allowed_capabilities = 1ULL << CAP_SYS_ADMIN, | |
| .quiet_capabilities = 1ULL << CAP_SYS_CHROOT, | |
| }; | |
| const struct landlock_capability_attr invalid_attr = { | |
| .permissions = LANDLOCK_PERMISSION_NAMESPACE_USE, | |
| .allowed_capabilities = 1ULL << CAP_SYS_ADMIN, | |
| }; | |
| 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_cap_ruleset(); | |
| ASSERT_LE(0, ruleset_fd); | |
| ASSERT_EQ(0, add_cap_rule_full(ruleset_fd, 1ULL << CAP_SYS_ADMIN, | |
| 1ULL << CAP_SYS_CHROOT)); | |
| ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY, | |
| &invalid_attr, 0)); | |
| ASSERT_EQ(EINVAL, errno); | |
| ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_CAPABILITY, | |
| &valid_attr, LANDLOCK_ADD_RULE_QUIET)); | |
| ASSERT_EQ(EINVAL, errno); | |
| ASSERT_EQ(0, add_cap_rule_full(ruleset_fd, 1ULL << CAP_SYS_ADMIN, | |
| 1ULL << CAP_SYS_CHROOT)); | |
| ASSERT_EQ(0, add_cap_rule(ruleset_fd, 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("capability.use", field); | |
| ASSERT_EQ(3, | |
| tracefs_count_matches(buf, "landlock_add_rule_capability: ")); | |
| ASSERT_EQ(3, tracefs_count_matches( | |
| buf, REGEX_ADD_RULE_CAPABILITY(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("capability.use", field); | |
| ASSERT_EQ(0, | |
| tracefs_extract_field(buf, version_1, "allowed_capabilities", | |
| field, sizeof(field))); | |
| snprintf(expected, sizeof(expected), "0x%llx", 1ULL << CAP_SYS_ADMIN); | |
| EXPECT_STREQ(expected, field); | |
| ASSERT_EQ(0, tracefs_extract_field(buf, version_1, "quiet_capabilities", | |
| field, sizeof(field))); | |
| snprintf(expected, sizeof(expected), "0x%llx", 1ULL << CAP_SYS_CHROOT); | |
| 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("capability.use", field); | |
| ASSERT_EQ(0, | |
| tracefs_extract_field(buf, version_2, "allowed_capabilities", | |
| field, sizeof(field))); | |
| snprintf(expected, sizeof(expected), "0x%llx", 1ULL << CAP_SYS_ADMIN); | |
| EXPECT_STREQ(expected, field); | |
| ASSERT_EQ(0, tracefs_extract_field(buf, version_2, "quiet_capabilities", | |
| field, sizeof(field))); | |
| snprintf(expected, sizeof(expected), "0x%llx", 1ULL << CAP_SYS_CHROOT); | |
| 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("capability.use", field); | |
| ASSERT_EQ(0, | |
| tracefs_extract_field(buf, version_3, "allowed_capabilities", | |
| field, sizeof(field))); | |
| EXPECT_STREQ("0x0", field); | |
| ASSERT_EQ(0, tracefs_extract_field(buf, version_3, "quiet_capabilities", | |
| field, sizeof(field))); | |
| EXPECT_STREQ("0x0", field); | |
| free(buf); | |
| } | |
| enum cap_trace_denial_kind { | |
| CAP_TRACE_DENY, | |
| CAP_TRACE_DENY_QUIET, | |
| CAP_TRACE_DENY_NOAUDIT, | |
| }; | |
| static void exercise_cap_trace_denial(struct __test_metadata *const _metadata, | |
| const enum cap_trace_denial_kind kind) | |
| { | |
| int ruleset_fd, fd = -1; | |
| char list[256]; | |
| ssize_t len; | |
| disable_caps(_metadata); | |
| if (kind == CAP_TRACE_DENY_NOAUDIT) { | |
| set_cap(_metadata, CAP_SYS_ADMIN); | |
| set_cap(_metadata, CAP_SYS_CHROOT); | |
| ASSERT_EQ(0, mount("test", "/tmp", "tmpfs", 0, NULL)); | |
| fd = open("/tmp/f", O_CREAT | O_RDWR | O_CLOEXEC, 0600); | |
| ASSERT_LE(0, fd); | |
| ASSERT_EQ(0, fsetxattr(fd, "trusted.landlock_test", "x", 1, 0)); | |
| /* Baseline: CAP_SYS_ADMIN exposes the trusted xattr. */ | |
| len = flistxattr(fd, list, sizeof(list)); | |
| ASSERT_LE(0, len); | |
| ASSERT_TRUE(list_has_xattr(list, len, "trusted.landlock_test")); | |
| } else { | |
| set_cap(_metadata, CAP_SYS_CHROOT); | |
| } | |
| ruleset_fd = create_cap_ruleset(); | |
| ASSERT_LE(0, ruleset_fd); | |
| if (kind == CAP_TRACE_DENY_QUIET) | |
| ASSERT_EQ(0, add_cap_rule_full(ruleset_fd, 0, | |
| 1ULL << CAP_SYS_CHROOT)); | |
| enforce_ruleset(_metadata, ruleset_fd); | |
| EXPECT_EQ(0, close(ruleset_fd)); | |
| if (kind == CAP_TRACE_DENY_NOAUDIT) { | |
| /* Probe CAP_SYS_ADMIN with CAP_OPT_NOAUDIT. */ | |
| len = flistxattr(fd, list, sizeof(list)); | |
| ASSERT_LE(0, len); | |
| EXPECT_FALSE( | |
| list_has_xattr(list, len, "trusted.landlock_test")); | |
| EXPECT_EQ(0, close(fd)); | |
| /* Control: an ordinary denial in the same domain is traced. */ | |
| EXPECT_EQ(-1, chroot("/")); | |
| EXPECT_EQ(EPERM, errno); | |
| clear_cap(_metadata, CAP_SYS_ADMIN); | |
| clear_cap(_metadata, CAP_SYS_CHROOT); | |
| } else { | |
| EXPECT_EQ(-1, chroot("/")); | |
| EXPECT_EQ(EPERM, errno); | |
| clear_cap(_metadata, CAP_SYS_CHROOT); | |
| } | |
| } | |
| static void test_cap_trace_denial(struct __test_metadata *const _metadata, | |
| const enum cap_trace_denial_kind kind, | |
| const int expected_capability, | |
| const char *const expected_logged) | |
| { | |
| char expected[16], field[64]; | |
| int status; | |
| char *buf; | |
| pid_t child; | |
| ASSERT_EQ(0, tracefs_clear_buf()); | |
| child = fork(); | |
| ASSERT_LE(0, child); | |
| if (child == 0) { | |
| exercise_cap_trace_denial(_metadata, kind); | |
| _exit(_metadata->exit_code); | |
| } | |
| ASSERT_EQ(child, waitpid(child, &status, 0)); | |
| ASSERT_TRUE(WIFEXITED(status)); | |
| ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); | |
| buf = tracefs_read_buf(); | |
| ASSERT_NE(NULL, buf); | |
| ASSERT_EQ(1, tracefs_count_matches( | |
| buf, REGEX_DENY_PERMISSION_CAPABILITY(TRACE_TASK))) | |
| { | |
| TH_LOG("Expected one capability denial event\n%s", buf); | |
| } | |
| ASSERT_EQ(0, tracefs_extract_field( | |
| buf, REGEX_DENY_PERMISSION_CAPABILITY(TRACE_TASK), | |
| "domain", field, sizeof(field))); | |
| EXPECT_STRNE("0", field); | |
| ASSERT_EQ(0, tracefs_extract_field( | |
| buf, REGEX_DENY_PERMISSION_CAPABILITY(TRACE_TASK), | |
| "same_exec", field, sizeof(field))); | |
| EXPECT_STREQ("1", field); | |
| ASSERT_EQ(0, tracefs_extract_field( | |
| buf, REGEX_DENY_PERMISSION_CAPABILITY(TRACE_TASK), | |
| "logged", field, sizeof(field))); | |
| EXPECT_STREQ(expected_logged, field); | |
| ASSERT_EQ(0, tracefs_extract_field( | |
| buf, REGEX_DENY_PERMISSION_CAPABILITY(TRACE_TASK), | |
| "blockers", field, sizeof(field))); | |
| EXPECT_STREQ("use", field); | |
| ASSERT_EQ(0, tracefs_extract_field( | |
| buf, REGEX_DENY_PERMISSION_CAPABILITY(TRACE_TASK), | |
| "capability", field, sizeof(field))); | |
| snprintf(expected, sizeof(expected), "%d", expected_capability); | |
| EXPECT_STREQ(expected, field); | |
| free(buf); | |
| } | |
| TEST_F(cap_trace, deny) | |
| { | |
| if (!self->tracefs_ok) | |
| SKIP(return, "tracefs not available"); | |
| test_cap_trace_denial(_metadata, CAP_TRACE_DENY, CAP_SYS_CHROOT, "1"); | |
| } | |
| TEST_F(cap_trace, deny_quiet) | |
| { | |
| if (!self->tracefs_ok) | |
| SKIP(return, "tracefs not available"); | |
| test_cap_trace_denial(_metadata, CAP_TRACE_DENY_QUIET, CAP_SYS_CHROOT, | |
| "0"); | |
| } | |
| /* | |
| * A CAP_OPT_NOAUDIT probe is denied without any record. The audit counterpart | |
| * checks the missing audit record; this one checks that the CAP_SYS_ADMIN probe | |
| * emits no trace event, while the CAP_SYS_CHROOT denial that follows it in the | |
| * same domain still does. | |
| */ | |
| TEST_F(cap_trace, noaudit_probe_not_traced) | |
| { | |
| if (!self->tracefs_ok) | |
| SKIP(return, "tracefs not available"); | |
| test_cap_trace_denial(_metadata, CAP_TRACE_DENY_NOAUDIT, CAP_SYS_CHROOT, | |
| "1"); | |
| } | |
| TEST_HARNESS_MAIN |