diff options
| author | Paul Moore <paul@paul-moore.com> | 2026-10-16 17:01:58 -0400 |
|---|---|---|
| committer | Paul Moore <paul@paul-moore.com> | 2026-10-16 17:01:58 -0400 |
| commit | 436485132d2367a50aa22dcc8a89faeee24a4cc9 (patch) | |
| tree | f4c7cafad0ec42898880ea53ab0ecd2a7b147cde /tools/testing | |
| parent | fd73f4a6659897191fa0d40695fe370925dd3780 (diff) | |
| parent | 881f19c2ffbc73f351b257e09c71e6059939b770 (diff) | |
Automated merge of 'lsm/dev' into 'stable-7.4'
Manual merge fixup in ns_common_types.h:ns_common to work around a conflict
with the nsfs RCU grace period fix.
* lsm/dev:
lsm: remove redundant NULL check in security_init()
selftests/landlock: Check full ioctl commands in audit records
lsm: Preserve full ioctl commands in audit records
doc: fix LSM ordering description for /sys/kernel/security/lsm
doc: describe CONFIG_LSM and lsm= as the selection mechanism
selftests/bpf: verify mount idmaps reach inode hooks
lsm: expose mount idmaps to inode hooks
rust: lsm: replace `core::mem::zeroed` with `pin_init::zeroed`
cred: prevent slab cache merging for cred_jar
lsm: don't call security_backing_file_free() multiple times
lsm: delete duplicate assignment in lsm_prepare()
lsm: update the BUILD_BUG_ON() in audit_log_lsm_data()
lsm: add LSM_AUDIT_DATA_NS for namespace audit records
lsm: add LSM blob and hooks for namespaces
ns: Free anonymous mount namespaces via ns_common_free()
cred: clarify that task_struct::cred is only for the current task
Diffstat (limited to 'tools/testing')
| -rw-r--r-- | tools/testing/selftests/bpf/prog_tests/test_lsm.c | 231 | ||||
| -rw-r--r-- | tools/testing/selftests/bpf/progs/lsm.c | 79 | ||||
| -rw-r--r-- | tools/testing/selftests/landlock/fs_test.c | 10 |
3 files changed, 317 insertions, 3 deletions
diff --git a/tools/testing/selftests/bpf/prog_tests/test_lsm.c b/tools/testing/selftests/bpf/prog_tests/test_lsm.c index d7495efd4a56..c0ae813698ae 100644 --- a/tools/testing/selftests/bpf/prog_tests/test_lsm.c +++ b/tools/testing/selftests/bpf/prog_tests/test_lsm.c @@ -1,18 +1,30 @@ // SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE /* * Copyright (C) 2020 Google LLC. */ #include <test_progs.h> +#include <sched.h> +#include <signal.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/syscall.h> #include <sys/wait.h> #include <unistd.h> +#include <linux/mount.h> + #include "lsm.skel.h" #include "lsm_tailcall.skel.h" char *CMD_ARGS[] = {"true", NULL}; +enum { + INODE_IDMAP_ALL = (1U << 6) - 1, +}; + int exec_cmd(int *monitored_pid) { int child_pid, child_status; @@ -30,6 +42,219 @@ int exec_cmd(int *monitored_pid) return -EINVAL; } +static ssize_t write_nointr(int fd, const void *buf, size_t count) +{ + ssize_t ret; + + do { + ret = write(fd, buf, count); + } while (ret < 0 && errno == EINTR); + + return ret; +} + +static int write_file(const char *path, const char *value) +{ + size_t len = strlen(value); + int fd, saved_errno = 0; + ssize_t ret; + + fd = open(path, O_WRONLY | O_CLOEXEC | O_NOCTTY | O_NOFOLLOW); + if (fd < 0) + return -1; + + ret = write_nointr(fd, value, len); + if (ret < 0) + saved_errno = errno; + else if ((size_t)ret != len) + saved_errno = EIO; + close(fd); + if (saved_errno) { + errno = saved_errno; + return -1; + } + return 0; +} + +static int write_userns_file(pid_t pid, const char *name, const char *value) +{ + char path[64]; + int len; + + len = snprintf(path, sizeof(path), "/proc/%d/%s", pid, name); + if (len < 0 || (size_t)len >= sizeof(path)) { + errno = EOVERFLOW; + return -1; + } + + return write_file(path, value); +} + +static int create_userns_fd(void) +{ + char path[64]; + pid_t pid, waited; + int fd = -1, len, saved_errno, status; + + pid = fork(); + if (pid < 0) + return -1; + if (pid == 0) { + if (unshare(CLONE_NEWUSER)) + _exit(1); + raise(SIGSTOP); + _exit(0); + } + + do { + waited = waitpid(pid, &status, WUNTRACED); + } while (waited < 0 && errno == EINTR); + if (waited != pid) + goto out; + if (!WIFSTOPPED(status)) { + pid = -1; + goto out; + } + + /* A one-entry map is identity for root but remains distinct from nop_mnt_idmap. */ + if (write_userns_file(pid, "setgroups", "deny") && errno != ENOENT) + goto out; + if (write_userns_file(pid, "uid_map", "0 0 1") || + write_userns_file(pid, "gid_map", "0 0 1")) + goto out; + + len = snprintf(path, sizeof(path), "/proc/%d/ns/user", pid); + if (len < 0 || (size_t)len >= sizeof(path)) { + errno = EOVERFLOW; + goto out; + } + fd = open(path, O_RDONLY | O_CLOEXEC); + +out: + saved_errno = errno; + if (pid > 0) { + kill(pid, SIGKILL); + do { + waited = waitpid(pid, NULL, 0); + } while (waited < 0 && errno == EINTR); + } + errno = saved_errno; + return fd; +} + +static int create_idmapped_tmpfs(void) +{ + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + int fsfd = -1, mntfd = -1, saved_errno, userns_fd = -1; + + userns_fd = create_userns_fd(); + if (userns_fd < 0) + goto out; + + /* A detached tmpfs avoids relying on the host test directory supporting idmaps. */ + fsfd = syscall(__NR_fsopen, "tmpfs", FSOPEN_CLOEXEC); + if (fsfd < 0) + goto out; + if (syscall(__NR_fsconfig, fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0)) + goto out; + + mntfd = syscall(__NR_fsmount, fsfd, FSMOUNT_CLOEXEC, 0); + if (mntfd < 0) + goto out; + + attr.userns_fd = userns_fd; + if (syscall(__NR_mount_setattr, mntfd, "", AT_EMPTY_PATH, &attr, + sizeof(attr))) { + close(mntfd); + mntfd = -1; + } + +out: + saved_errno = errno; + if (fsfd >= 0) + close(fsfd); + if (userns_fd >= 0) + close(userns_fd); + errno = saved_errno; + return mntfd; +} + +static int exercise_inode_idmap_hooks(int dirfd) +{ + int fd = -1, ret = -1; + + fd = openat(dirfd, "file", O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, + 0600); + if (!ASSERT_GE(fd, 0, "create")) + goto out; + close(fd); + fd = -1; + + if (!ASSERT_OK(mkdirat(dirfd, "dir", 0700), "mkdir")) + goto out; + if (!ASSERT_OK(symlinkat("target", dirfd, "symlink"), "symlink")) + goto out; + if (!ASSERT_OK(linkat(dirfd, "file", dirfd, "link", 0), "link")) + goto out; + if (!ASSERT_OK(mkfifoat(dirfd, "fifo", 0600), "mknod")) + goto out; + + ret = 0; +out: + if (fd >= 0) + close(fd); + unlinkat(dirfd, "link", 0); + unlinkat(dirfd, "fifo", 0); + unlinkat(dirfd, "symlink", 0); + unlinkat(dirfd, "file", 0); + unlinkat(dirfd, "dir", AT_REMOVEDIR); + return ret; +} + +static int test_lsm_inode_idmap(struct lsm *skel) +{ + char tmpdir[] = "/var/tmp/test_lsm_idmap.XXXXXX"; + __u32 expected = INODE_IDMAP_ALL; + int dirfd = -1, idmapped_dirfd = -1; + int ret = -1; + + if (!ASSERT_OK_PTR(mkdtemp(tmpdir), "mkdtemp")) + return -1; + + dirfd = open(tmpdir, O_RDONLY | O_DIRECTORY | O_CLOEXEC); + if (!ASSERT_GE(dirfd, 0, "open_tmpdir")) + goto out; + + idmapped_dirfd = create_idmapped_tmpfs(); + if (!ASSERT_GE(idmapped_dirfd, 0, "create_idmapped_tmpfs")) + goto out; + + skel->bss->inode_identity_idmap_seen = 0; + skel->bss->inode_idmapped_mount_seen = 0; + + if (!ASSERT_OK(exercise_inode_idmap_hooks(dirfd), "identity_idmap")) + goto out; + if (!ASSERT_OK(exercise_inode_idmap_hooks(idmapped_dirfd), + "idmapped_mount")) + goto out; + + if (!ASSERT_EQ(skel->bss->inode_identity_idmap_seen, expected, + "inode_identity_idmap_seen")) + goto out; + ret = ASSERT_EQ(skel->bss->inode_idmapped_mount_seen, expected, + "inode_idmapped_mount_seen") ? 0 : -1; + +out: + if (idmapped_dirfd >= 0) + close(idmapped_dirfd); + if (dirfd >= 0) + close(dirfd); + rmdir(tmpdir); + return ret; +} + static int test_lsm(struct lsm *skel) { struct bpf_link *link; @@ -53,6 +278,10 @@ static int test_lsm(struct lsm *skel) skel->bss->monitored_pid = getpid(); + err = test_lsm_inode_idmap(skel); + if (!ASSERT_OK(err, "test_lsm_inode_idmap")) + return err; + err = stack_mprotect(); if (!ASSERT_EQ(err, -1, "stack_mprotect") || !ASSERT_EQ(errno, EPERM, "stack_mprotect")) @@ -71,6 +300,8 @@ static int test_lsm(struct lsm *skel) skel->bss->copy_test = 0; skel->bss->bprm_count = 0; skel->bss->mprotect_count = 0; + skel->bss->inode_identity_idmap_seen = 0; + skel->bss->inode_idmapped_mount_seen = 0; return 0; } diff --git a/tools/testing/selftests/bpf/progs/lsm.c b/tools/testing/selftests/bpf/progs/lsm.c index 7de173daf27b..7e32fbddbfad 100644 --- a/tools/testing/selftests/bpf/progs/lsm.c +++ b/tools/testing/selftests/bpf/progs/lsm.c @@ -84,6 +84,85 @@ char _license[] SEC("license") = "GPL"; int monitored_pid = 0; int mprotect_count = 0; int bprm_count = 0; +__u32 inode_identity_idmap_seen = 0; +__u32 inode_idmapped_mount_seen = 0; + +enum { + INODE_IDMAP_CREATE = 1U << 0, + INODE_IDMAP_LINK = 1U << 1, + INODE_IDMAP_SYMLINK = 1U << 2, + INODE_IDMAP_MKDIR = 1U << 3, + INODE_IDMAP_MKNOD = 1U << 4, + INODE_IDMAP_PERMISSION = 1U << 5, +}; + +static __always_inline bool is_monitored_idmap(struct mnt_idmap *idmap) +{ + __u32 pid = bpf_get_current_pid_tgid() >> 32; + + return monitored_pid == pid && idmap; +} + +static __always_inline bool is_identity_idmap(struct mnt_idmap *idmap) +{ + return idmap->uid_map.nr_extents == 0 && + idmap->gid_map.nr_extents == 0; +} + +static __always_inline int record_inode_idmap(struct mnt_idmap *idmap, + __u32 hook, int ret) +{ + if (ret || !is_monitored_idmap(idmap)) + return ret; + if (is_identity_idmap(idmap)) + inode_identity_idmap_seen |= hook; + else + inode_idmapped_mount_seen |= hook; + return 0; +} + +SEC("lsm/inode_create") +int BPF_PROG(test_inode_create, struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, int ret) +{ + return record_inode_idmap(idmap, INODE_IDMAP_CREATE, ret); +} + +SEC("lsm/inode_link") +int BPF_PROG(test_inode_link, struct mnt_idmap *idmap, + struct dentry *old_dentry, struct inode *dir, + struct dentry *new_dentry, int ret) +{ + return record_inode_idmap(idmap, INODE_IDMAP_LINK, ret); +} + +SEC("lsm/inode_symlink") +int BPF_PROG(test_inode_symlink, struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, const char *old_name, int ret) +{ + return record_inode_idmap(idmap, INODE_IDMAP_SYMLINK, ret); +} + +SEC("lsm/inode_mkdir") +int BPF_PROG(test_inode_mkdir, struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, int ret) +{ + return record_inode_idmap(idmap, INODE_IDMAP_MKDIR, ret); +} + +SEC("lsm/inode_mknod") +int BPF_PROG(test_inode_mknod, struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, dev_t dev, int ret) +{ + return record_inode_idmap(idmap, INODE_IDMAP_MKNOD, ret); +} + +SEC("lsm/inode_permission") +int BPF_PROG(test_inode_permission, struct mnt_idmap *idmap, + struct inode *inode, int mask, int ret) +{ + return record_inode_idmap(idmap, INODE_IDMAP_PERMISSION, ret); +} SEC("lsm/file_mprotect") int BPF_PROG(test_int_hook, struct vm_area_struct *vma, diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c index 18dbdb99aeba..1abdcc9ee784 100644 --- a/tools/testing/selftests/landlock/fs_test.c +++ b/tools/testing/selftests/landlock/fs_test.c @@ -4146,6 +4146,9 @@ TEST_F_FORK(layout1, o_path_ftruncate_and_ioctl) ASSERT_EQ(0, close(fd)); } +/* Arbitrary command with nonzero bits in both 16-bit halves. */ +static const unsigned int unknown_ioctl_cmd = 0xc00ffeee; + /* * ioctl_error - generically call the given ioctl with a pointer to a * sufficiently large zeroed-out memory region. @@ -4249,7 +4252,7 @@ TEST_F_FORK(layout1, blanket_permitted_ioctls) EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_ZERO_RANGE)); /* Default case is also blocked. */ - EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, 0xc00ffeee)); + EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, unknown_ioctl_cmd)); ASSERT_EQ(0, close(fd)); } @@ -7943,6 +7946,7 @@ TEST_F(audit_layout1, truncate) EXPECT_EQ(1, records.domain); } +/* Checks that audit records preserve every ioctl command bit. */ TEST_F(audit_layout1, ioctl_dev) { struct audit_records records; @@ -7952,10 +7956,10 @@ TEST_F(audit_layout1, ioctl_dev) fd = open("/dev/null", O_RDONLY | O_CLOEXEC); ASSERT_LE(0, fd); - EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FIONREAD)); + EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, unknown_ioctl_cmd)); EXPECT_EQ(0, matches_log_fs_extra(_metadata, self->audit_fd, "fs\\.ioctl_dev", "/dev/null", - " ioctlcmd=0x541b")); + " ioctlcmd=0xc00ffeee")); EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); EXPECT_EQ(0, records.access); |
