summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-10-20 20:04:07 -1000
committerLinus Torvalds <torvalds@linux-foundation.org>2026-10-20 20:04:07 -1000
commita804c5742bff224d4cb4d25dd383554d598be32b (patch)
tree9ed21d8b340428f13f26485f2d0c5085445d8cdf /tools
parent331eadd85f14bac85663c14c99228f6dedf64d87 (diff)
parent436485132d2367a50aa22dcc8a89faeee24a4cc9 (diff)
Merge tag 'lsm-pr-20261016' of git://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/lsm
Pull lsm updates from Paul Moore: - Add LSM hooks, object blobs, and audit records for namespaces Add the necessary bits to enable LSMs to enforce access controls on the management of various kernel namespaces. While this pull request does not contain any in-tree users of these hooks, the Landlock pull request for Linux v7.4 will include Landlock support and the next merge window will likely include SELinux support as well. Considering the development behind these hooks, I expect that BPF LSM developers will also be making use of the namespace hooks, if they haven't already. - Update the inode LSM hooks to include mount idmaps This allows LSMs to incorporate the mount ID mapping into their access control decisions. While there are no current in-tree users of this, it was requested by those developing BPF LSM programs and this pull request includes a BPF LSM selftest. - Preserve the full 32-bit ioctl command in LSM audit records Previously only the low 16 bits were logged, ignoring the direction and size information in the upper 16 bits. The pull request also includes a Landlock based selftest to verify the full 32-bit ioctl value. - Ensure we don't call security_backing_file_free() multiple times Thankfully we don't have a problem with the current code base, but we want to fix this behavior now before it becomes a problem. - Clarify that only the current task may access current->cred The current code is a bit misleading around safely accessing the cred field in a task struct. Through comment and code corrections, this pull request clarifies that only the task itself may access its cred field. - Add SLAB_NO_MERGE to the credential object cache Harden the credential cache by ensuring credential objects live on dedicated slab pages. - Make use of safe memory zero'ing in the LSM Rust bindings - Update the docs for the current behavior of /sys/kernel/security/lsm - Update the docs for CONFIG_LSM and the "lsm=" command line option - Remove unnecessary code * tag 'lsm-pr-20261016' of git://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/lsm: 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')
-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 6e979cef884d..fd20a2b3c0a5 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);