blob: 3d7fd02e21ed9f2de07dd28fb7e3d1cee6c832da [file] [edit]
// 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