summaryrefslogtreecommitdiff
path: root/tools/testing
diff options
context:
space:
mode:
authorPaul Moore <paul@paul-moore.com>2026-10-16 17:01:58 -0400
committerPaul Moore <paul@paul-moore.com>2026-10-16 17:01:58 -0400
commit436485132d2367a50aa22dcc8a89faeee24a4cc9 (patch)
treef4c7cafad0ec42898880ea53ab0ecd2a7b147cde /tools/testing
parentfd73f4a6659897191fa0d40695fe370925dd3780 (diff)
parent881f19c2ffbc73f351b257e09c71e6059939b770 (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.c231
-rw-r--r--tools/testing/selftests/bpf/progs/lsm.c79
-rw-r--r--tools/testing/selftests/landlock/fs_test.c10
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);