From: Daniel Borkmann <daniel@iogearbox.net>
To: alexei.starovoitov@gmail.com
Cc: brauner@kernel.org, dwindsor@gmail.com, john.fastabend@gmail.com,
memxor@gmail.com, kpsingh@kernel.org, matt@bobrowski.net,
bpf@vger.kernel.org
Subject: [PATCH bpf-next 8/8] selftests/bpf: Add tests for BPF LSM inode init labelling
Date: Tue, 15 Sep 2026 17:07:39 +0200 [thread overview]
Message-ID: <20260915150739.284189-9-daniel@iogearbox.net> (raw)
In-Reply-To: <20260915150739.284189-1-daniel@iogearbox.net>
Exercise bpf_inode_init_xattr() in combination with a policy example
via BPF LSM. A program on the inode_init_security hook labels new files
and directories, inherits a zone label from the parent directory, and
has claims refused for names outside the security.bpf. prefix and for
a name that would exceed XATTR_NAME_MAX once the prefix is put back as
well as other corner case tests that should get rejected.
# LDLIBS=-static PKG_CONFIG='pkg-config --static' ./vmtest.sh -- ./test_progs -t lsm_inode_init_xattr
[...]
#226/1 lsm_inode_init_xattr/init_labels:OK
#226/2 lsm_inode_init_xattr/inherit_from_parent:OK
#226/3 lsm_inode_init_xattr/refused_claims:OK
#226/4 lsm_inode_init_xattr/null_xattrs:OK
#226/5 lsm_inode_init_xattr/shared_budget:OK
#226/6 lsm_inode_init_xattr/value_shapes:OK
#226 lsm_inode_init_xattr:OK
Summary: 1/6 PASSED, 0 SKIPPED, 0/0 FAILED
Co-developed-by: David Windsor <dwindsor@gmail.com>
Signed-off-by: David Windsor <dwindsor@gmail.com>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
---
tools/testing/selftests/bpf/config | 2 +
.../bpf/prog_tests/lsm_inode_init_xattr.c | 399 ++++++++++++++++++
.../bpf/progs/lsm_inode_init_xattr.c | 98 +++++
.../bpf/progs/lsm_inode_init_xattr_budget.c | 37 ++
.../bpf/progs/lsm_inode_init_xattr_value.c | 47 +++
5 files changed, 583 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/lsm_inode_init_xattr.c
create mode 100644 tools/testing/selftests/bpf/progs/lsm_inode_init_xattr.c
create mode 100644 tools/testing/selftests/bpf/progs/lsm_inode_init_xattr_budget.c
create mode 100644 tools/testing/selftests/bpf/progs/lsm_inode_init_xattr_value.c
diff --git a/tools/testing/selftests/bpf/config b/tools/testing/selftests/bpf/config
index 2f79688dcf7c..d292cb60a5a4 100644
--- a/tools/testing/selftests/bpf/config
+++ b/tools/testing/selftests/bpf/config
@@ -124,6 +124,8 @@ CONFIG_SECURITY=y
CONFIG_SECURITYFS=y
CONFIG_SYN_COOKIES=y
CONFIG_TEST_BPF=m
+CONFIG_TMPFS=y
+CONFIG_TMPFS_XATTR=y
CONFIG_UDMABUF=y
CONFIG_USERFAULTFD=y
CONFIG_VSOCKETS=y
diff --git a/tools/testing/selftests/bpf/prog_tests/lsm_inode_init_xattr.c b/tools/testing/selftests/bpf/prog_tests/lsm_inode_init_xattr.c
new file mode 100644
index 000000000000..b91c5c659542
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/lsm_inode_init_xattr.c
@@ -0,0 +1,399 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/xattr.h>
+#include <test_progs.h>
+
+#include "lsm_inode_init_xattr.skel.h"
+#include "lsm_inode_init_xattr_budget.skel.h"
+#include "lsm_inode_init_xattr_value.skel.h"
+
+#define INIT_XATTRS_MAX 2
+#define VALUE_SIZE_MAX (64 * 1024)
+#define TESTDIR "/tmp/test_progs_lsm_init_xattr"
+#define RAMFSDIR "/tmp/test_progs_lsm_init_xattr_ramfs"
+
+static bool testdir_mounted;
+static bool ramfsdir_mounted;
+
+static int testdir_setup(void)
+{
+ if (mkdir(TESTDIR, 0755) && errno != EEXIST)
+ return -errno;
+ if (mount("tmpfs", TESTDIR, "tmpfs", 0, NULL))
+ return -errno;
+ testdir_mounted = true;
+ if (mkdir(RAMFSDIR, 0755) && errno != EEXIST)
+ return -errno;
+ if (mount("ramfs", RAMFSDIR, "ramfs", 0, NULL))
+ return -errno;
+ ramfsdir_mounted = true;
+ return 0;
+}
+
+static void testdir_cleanup(void)
+{
+ if (ramfsdir_mounted)
+ umount(RAMFSDIR);
+ rmdir(RAMFSDIR);
+ if (testdir_mounted)
+ umount(TESTDIR);
+ rmdir(TESTDIR);
+}
+
+static bool lsm_is_active(const char *name)
+{
+ char buf[512], *tok;
+ int fd, len;
+
+ fd = open("/sys/kernel/security/lsm", O_RDONLY);
+ if (fd < 0)
+ return true;
+ len = read(fd, buf, sizeof(buf) - 1);
+ close(fd);
+ if (len <= 0)
+ return true;
+ buf[len] = '\0';
+ for (tok = strtok(buf, ",\n"); tok; tok = strtok(NULL, ",\n"))
+ if (!strcmp(tok, name))
+ return true;
+ return false;
+}
+
+static int read_label(const char *path, const char *name, char *buf, size_t sz)
+{
+ int ret = getxattr(path, name, buf, sz);
+
+ return ret < 0 ? -errno : ret;
+}
+
+static void assert_label(const char *path, const char *name, const char *want)
+{
+ char buf[64] = {};
+ int ret;
+
+ ret = read_label(path, name, buf, sizeof(buf));
+ if (!ASSERT_EQ(ret, (int)strlen(want) + 1, name))
+ return;
+ ASSERT_STREQ(buf, want, name);
+}
+
+static struct lsm_inode_init_xattr *policy_attach(void)
+{
+ struct lsm_inode_init_xattr *skel;
+
+ skel = lsm_inode_init_xattr__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "skel_open_and_load"))
+ return NULL;
+
+ skel->bss->monitored_pid = getpid();
+ if (!ASSERT_OK(lsm_inode_init_xattr__attach(skel), "skel_attach")) {
+ lsm_inode_init_xattr__destroy(skel);
+ return NULL;
+ }
+ return skel;
+}
+
+static void test_init_labels(void)
+{
+ struct lsm_inode_init_xattr *skel;
+ const char *file = TESTDIR "/file";
+ const char *subdir = TESTDIR "/subdir";
+ int fd = -1;
+
+ skel = policy_attach();
+ if (!skel)
+ return;
+
+ fd = open(file, O_CREAT | O_RDWR, 0644);
+ if (!ASSERT_GE(fd, 0, "create_file"))
+ goto out;
+
+ if (!ASSERT_TRUE(skel->bss->hook_ran, "hook_ran"))
+ goto out;
+
+ ASSERT_OK(skel->data->zone_err, "zone_err");
+ ASSERT_OK(skel->data->origin_err, "origin_err");
+ assert_label(file, "security.bpf.zone", "default");
+ assert_label(file, "security.bpf.origin", "created");
+
+ if (!ASSERT_OK(mkdir(subdir, 0755), "mkdir"))
+ goto out;
+ assert_label(subdir, "security.bpf.zone", "default");
+ assert_label(subdir, "security.bpf.origin", "created");
+ rmdir(subdir);
+out:
+ if (fd >= 0)
+ close(fd);
+ remove(file);
+ lsm_inode_init_xattr__destroy(skel);
+}
+
+static void test_inherit_from_parent(void)
+{
+ const char *zonedir = TESTDIR "/zoned";
+ const char *file = TESTDIR "/zoned/file";
+ struct lsm_inode_init_xattr *skel;
+ int dirfd = -1, fd = -1, err;
+ struct {
+ char v[32];
+ __u32 len;
+ } label = {};
+
+ if (!ASSERT_OK(mkdir(zonedir, 0755), "mkdir_zoned"))
+ return;
+
+ skel = policy_attach();
+ if (!skel)
+ goto out_dir;
+
+ dirfd = open(zonedir, O_RDONLY | O_DIRECTORY);
+ if (!ASSERT_GE(dirfd, 0, "open_zoned"))
+ goto out;
+
+ strncpy(label.v, "restricted", sizeof(label.v) - 1);
+ label.len = strlen("restricted") + 1;
+
+ err = bpf_map_update_elem(bpf_map__fd(skel->maps.inode_zone), &dirfd,
+ &label, BPF_ANY);
+ if (!ASSERT_OK(err, "seed_parent_zone"))
+ goto out;
+
+ fd = open(file, O_CREAT | O_RDWR, 0644);
+ if (!ASSERT_GE(fd, 0, "create_file"))
+ goto out;
+
+ if (!ASSERT_TRUE(skel->bss->hook_ran, "hook_ran"))
+ goto out;
+
+ ASSERT_EQ(skel->bss->inherited, 1, "inherited");
+ ASSERT_OK(skel->data->zone_err, "zone_err");
+ assert_label(file, "security.bpf.zone", "restricted");
+ assert_label(file, "security.bpf.origin", "created");
+out:
+ if (fd >= 0)
+ close(fd);
+ if (dirfd >= 0)
+ close(dirfd);
+ remove(file);
+ lsm_inode_init_xattr__destroy(skel);
+out_dir:
+ rmdir(zonedir);
+}
+
+static void test_refused_claims(void)
+{
+ const char *file = TESTDIR "/refused";
+ struct lsm_inode_init_xattr *skel;
+ char buf[64];
+ int fd = -1;
+
+ skel = policy_attach();
+ if (!skel)
+ return;
+
+ fd = open(file, O_CREAT | O_RDWR, 0644);
+ if (!ASSERT_GE(fd, 0, "create_file"))
+ goto out;
+
+ if (!ASSERT_TRUE(skel->bss->hook_ran, "hook_ran"))
+ goto out;
+
+ ASSERT_EQ(skel->data->overflow_err, -ENOSPC, "overflow_err");
+ ASSERT_EQ(read_label(file, "security.bpf.overflow", buf, sizeof(buf)),
+ -ENODATA, "overflow_absent");
+
+ ASSERT_EQ(skel->data->toolong_err, -EINVAL, "toolong_err");
+
+ ASSERT_EQ(skel->data->selinux_err, -EPERM, "selinux_err");
+ ASSERT_EQ(skel->data->user_err, -EPERM, "user_err");
+
+ if (!lsm_is_active("selinux"))
+ ASSERT_EQ(read_label(file, "security.selinux", buf, sizeof(buf)),
+ -ENODATA, "selinux_absent");
+ ASSERT_EQ(read_label(file, "user.zone", buf, sizeof(buf)),
+ -ENODATA, "user_absent");
+out:
+ if (fd >= 0)
+ close(fd);
+ remove(file);
+ lsm_inode_init_xattr__destroy(skel);
+}
+
+static void test_null_xattrs(void)
+{
+ const char *file = RAMFSDIR "/file";
+ struct lsm_inode_init_xattr *skel;
+ int fd = -1;
+
+ skel = policy_attach();
+ if (!skel)
+ return;
+
+ fd = open(file, O_CREAT | O_RDWR, 0644);
+ if (!ASSERT_GE(fd, 0, "create_file"))
+ goto out;
+
+ if (!ASSERT_TRUE(skel->bss->hook_ran, "hook_ran"))
+ goto out;
+ ASSERT_EQ(skel->data->zone_err, -EOPNOTSUPP, "zone_err");
+out:
+ if (fd >= 0)
+ close(fd);
+ remove(file);
+ lsm_inode_init_xattr__destroy(skel);
+}
+
+static void test_shared_budget(void)
+{
+ struct lsm_inode_init_xattr_budget *skel[INIT_XATTRS_MAX + 1] = {};
+ struct bpf_link *link[INIT_XATTRS_MAX + 1] = {};
+ const char *file = TESTDIR "/budget";
+ int claimed = 0, refused = 0;
+ int i, fd = -1;
+
+ for (i = 0; i <= INIT_XATTRS_MAX; i++) {
+ skel[i] = lsm_inode_init_xattr_budget__open();
+ if (!ASSERT_OK_PTR(skel[i], "skel_open"))
+ goto out;
+
+ snprintf(skel[i]->rodata->xattr_name,
+ sizeof(skel[i]->rodata->xattr_name),
+ "security.bpf.slot%d", i);
+
+ if (!ASSERT_OK(lsm_inode_init_xattr_budget__load(skel[i]),
+ "skel_load"))
+ goto out;
+
+ skel[i]->bss->monitored_pid = getpid();
+
+ link[i] = bpf_program__attach_lsm(skel[i]->progs.claim_one);
+ if (!ASSERT_OK_PTR(link[i], "attach"))
+ goto out;
+ }
+
+ fd = open(file, O_CREAT | O_RDWR, 0644);
+ if (!ASSERT_GE(fd, 0, "create_file"))
+ goto out;
+
+ for (i = 0; i <= INIT_XATTRS_MAX; i++) {
+ int err = skel[i]->data->claim_err;
+
+ ASSERT_TRUE(skel[i]->bss->hook_ran, "hook_ran");
+ if (!err)
+ claimed++;
+ else if (ASSERT_EQ(err, -ENOSPC, "claim_err"))
+ refused++;
+ }
+
+ ASSERT_EQ(claimed, INIT_XATTRS_MAX, "claimed");
+ ASSERT_EQ(refused, 1, "refused");
+out:
+ if (fd >= 0)
+ close(fd);
+ remove(file);
+ for (i = 0; i <= INIT_XATTRS_MAX; i++) {
+ bpf_link__destroy(link[i]);
+ lsm_inode_init_xattr_budget__destroy(skel[i]);
+ }
+}
+
+static int claim_value(const char *file, __u32 len, __u64 flags,
+ int hook_retval, int *open_errno)
+{
+ struct lsm_inode_init_xattr_value *skel;
+ struct bpf_link *link = NULL;
+ int err = 1, fd;
+
+ skel = lsm_inode_init_xattr_value__open();
+ if (!ASSERT_OK_PTR(skel, "skel_open"))
+ return 1;
+
+ skel->rodata->value_len = len;
+ skel->rodata->dynptr_flags = flags;
+ skel->rodata->hook_retval = hook_retval;
+
+ if (!ASSERT_OK(lsm_inode_init_xattr_value__load(skel), "skel_load"))
+ goto out;
+
+ skel->bss->monitored_pid = getpid();
+
+ link = bpf_program__attach_lsm(skel->progs.claim_value);
+ if (!ASSERT_OK_PTR(link, "attach"))
+ goto out;
+
+ fd = open(file, O_CREAT | O_RDWR | O_EXCL, 0644);
+ *open_errno = fd < 0 ? errno : 0;
+ if (fd >= 0)
+ close(fd);
+
+ if (ASSERT_TRUE(skel->bss->hook_ran, "hook_ran"))
+ err = skel->data->claim_err;
+out:
+ bpf_link__destroy(link);
+ lsm_inode_init_xattr_value__destroy(skel);
+ return err;
+}
+
+static void test_value_shapes(void)
+{
+ const char *file = TESTDIR "/value";
+ char buf[64];
+ int err, open_errno;
+
+ remove(file);
+
+ err = claim_value(file, 0, 0, 0, &open_errno);
+ ASSERT_OK(err, "empty_value_err");
+ ASSERT_OK(open_errno, "empty_value_open");
+ ASSERT_EQ(read_label(file, "security.bpf.value", buf, sizeof(buf)),
+ 0, "empty_value_len");
+ remove(file);
+
+ err = claim_value(file, sizeof(buf), 1, 0, &open_errno);
+ ASSERT_EQ(err, -EINVAL, "bad_dynptr_err");
+ ASSERT_EQ(read_label(file, "security.bpf.value", buf, sizeof(buf)),
+ -ENODATA, "bad_dynptr_absent");
+ remove(file);
+
+ err = claim_value(file, VALUE_SIZE_MAX + 1, 0, 0, &open_errno);
+ ASSERT_EQ(err, -E2BIG, "oversized_err");
+ ASSERT_EQ(read_label(file, "security.bpf.value", buf, sizeof(buf)),
+ -ENODATA, "oversized_absent");
+ remove(file);
+
+ err = claim_value(file, 8, 0, -EPERM, &open_errno);
+ ASSERT_OK(err, "denied_claim_err");
+ ASSERT_EQ(open_errno, EPERM, "denied_open");
+ ASSERT_EQ(read_label(file, "security.bpf.value", buf, sizeof(buf)),
+ -ENOENT, "denied_absent");
+ remove(file);
+}
+
+void test_lsm_inode_init_xattr(void)
+{
+ if (!ASSERT_OK(testdir_setup(), "testdir_setup"))
+ goto out;
+
+ if (test__start_subtest("init_labels"))
+ test_init_labels();
+ if (test__start_subtest("inherit_from_parent"))
+ test_inherit_from_parent();
+ if (test__start_subtest("refused_claims"))
+ test_refused_claims();
+ if (test__start_subtest("null_xattrs"))
+ test_null_xattrs();
+ if (test__start_subtest("shared_budget"))
+ test_shared_budget();
+ if (test__start_subtest("value_shapes"))
+ test_value_shapes();
+out:
+ testdir_cleanup();
+}
diff --git a/tools/testing/selftests/bpf/progs/lsm_inode_init_xattr.c b/tools/testing/selftests/bpf/progs/lsm_inode_init_xattr.c
new file mode 100644
index 000000000000..77b37cf6165a
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/lsm_inode_init_xattr.c
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+#include <errno.h>
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_kfuncs.h"
+
+char _license[] SEC("license") = "GPL";
+
+#define LABEL_MAX 32
+
+struct label {
+ char v[LABEL_MAX];
+ __u32 len;
+};
+
+struct {
+ __uint(type, BPF_MAP_TYPE_INODE_STORAGE);
+ __uint(map_flags, BPF_F_NO_PREALLOC);
+ __type(key, int);
+ __type(value, struct label);
+} inode_zone SEC(".maps");
+
+__u32 monitored_pid;
+
+const char xattr_zone[] = "security.bpf.zone";
+const char xattr_origin[] = "security.bpf.origin";
+const char xattr_overflow[] = "security.bpf.overflow";
+/* One byte over XATTR_NAME_MAX once "security." is prepended again. */
+const char xattr_toolong[] = "security.bpf." "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+const char xattr_selinux[] = "security.selinux";
+const char xattr_user[] = "user.zone";
+
+char zone_scratch[LABEL_MAX];
+char origin_value[] = "created";
+
+__u32 hook_ran;
+__s32 zone_err = 1;
+__s32 origin_err = 1;
+__s32 overflow_err = 1;
+__s32 toolong_err = 1;
+__s32 selinux_err = 1;
+__s32 user_err = 1;
+__u32 inherited;
+
+SEC("lsm/inode_init_security")
+int BPF_PROG(init_label, struct inode *inode, struct inode *dir,
+ const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
+{
+ struct bpf_dynptr value;
+ struct label *parent;
+ int len = 0;
+
+ if ((bpf_get_current_pid_tgid() >> 32) != monitored_pid)
+ return 0;
+
+ hook_ran = 1;
+ if (!xattrs) {
+ /* The filesystem takes no xattrs at inode creation. */
+ zone_err = -EOPNOTSUPP;
+ return 0;
+ }
+ __builtin_memset(zone_scratch, 0, LABEL_MAX);
+
+ if (dir) {
+ parent = bpf_inode_storage_get(&inode_zone, dir, 0, 0);
+ if (parent && parent->len > 0 && parent->len <= LABEL_MAX) {
+ len = parent->len;
+ __builtin_memcpy(zone_scratch, parent->v, LABEL_MAX);
+ inherited = 1;
+ }
+ }
+ if (!len) {
+ __builtin_memcpy(zone_scratch, "default", sizeof("default"));
+ len = sizeof("default");
+ }
+
+ bpf_dynptr_from_mem(zone_scratch, len, 0, &value);
+ /* Refused before a slot is claimed, so the budget below is intact. */
+ toolong_err = bpf_inode_init_xattr(xattrs, xattr_count, xattr_toolong,
+ &value);
+ zone_err = bpf_inode_init_xattr(xattrs, xattr_count, xattr_zone,
+ &value);
+
+ bpf_dynptr_from_mem(origin_value, sizeof(origin_value), 0, &value);
+ origin_err = bpf_inode_init_xattr(xattrs, xattr_count, xattr_origin,
+ &value);
+
+ overflow_err = bpf_inode_init_xattr(xattrs, xattr_count,
+ xattr_overflow, &value);
+
+ selinux_err = bpf_inode_init_xattr(xattrs, xattr_count, xattr_selinux,
+ &value);
+ user_err = bpf_inode_init_xattr(xattrs, xattr_count, xattr_user,
+ &value);
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/lsm_inode_init_xattr_budget.c b/tools/testing/selftests/bpf/progs/lsm_inode_init_xattr_budget.c
new file mode 100644
index 000000000000..08dd93a1db11
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/lsm_inode_init_xattr_budget.c
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+#include <errno.h>
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_kfuncs.h"
+
+char _license[] SEC("license") = "GPL";
+
+const char xattr_name[32] = "security.bpf.slot";
+
+__u32 monitored_pid;
+__u32 hook_ran;
+__s32 claim_err = 1;
+
+char claim_value[] = "v";
+
+SEC("lsm/inode_init_security")
+int BPF_PROG(claim_one, struct inode *inode, struct inode *dir,
+ const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
+{
+ struct bpf_dynptr value;
+
+ if ((bpf_get_current_pid_tgid() >> 32) != monitored_pid)
+ return 0;
+
+ hook_ran = 1;
+ if (!xattrs) {
+ claim_err = -EOPNOTSUPP;
+ return 0;
+ }
+ bpf_dynptr_from_mem(claim_value, sizeof(claim_value), 0, &value);
+ claim_err = bpf_inode_init_xattr(xattrs, xattr_count, xattr_name,
+ &value);
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/lsm_inode_init_xattr_value.c b/tools/testing/selftests/bpf/progs/lsm_inode_init_xattr_value.c
new file mode 100644
index 000000000000..6a879a758601
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/lsm_inode_init_xattr_value.c
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+#include <errno.h>
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_kfuncs.h"
+
+char _license[] SEC("license") = "GPL";
+
+#define VALUE_MAX (64 * 1024 + 1)
+
+const char xattr_value[] = "security.bpf.value";
+
+/* Shape of the value handed to the kfunc, and what the hook returns after. */
+const volatile __u32 value_len;
+const volatile __u64 dynptr_flags;
+const volatile __s32 hook_retval;
+
+char value_src[VALUE_MAX];
+
+__u32 monitored_pid;
+__u32 hook_ran;
+__s32 claim_err = 1;
+
+SEC("lsm/inode_init_security")
+int BPF_PROG(claim_value, struct inode *inode, struct inode *dir,
+ const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
+{
+ struct bpf_dynptr value;
+ __u32 len = value_len;
+
+ if ((bpf_get_current_pid_tgid() >> 32) != monitored_pid)
+ return 0;
+ if (len > VALUE_MAX)
+ return 0;
+
+ hook_ran = 1;
+ if (!xattrs) {
+ claim_err = -EOPNOTSUPP;
+ return 0;
+ }
+ bpf_dynptr_from_mem(value_src, len, dynptr_flags, &value);
+ claim_err = bpf_inode_init_xattr(xattrs, xattr_count, xattr_value,
+ &value);
+ return hook_retval;
+}
--
2.43.0
next prev parent reply other threads:[~2026-09-15 15:07 UTC|newest]
Thread overview: 31+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-15 15:07 [PATCH bpf-next 0/8] BPF LSM xattrs at inode creation support Daniel Borkmann
2026-09-15 15:07 ` [PATCH bpf-next 1/8] ocfs2: Copy the xattr name in ocfs2_initxattrs Daniel Borkmann
2026-09-15 15:16 ` sashiko-bot
2026-09-15 16:26 ` bot+bpf-ci
2026-09-16 2:47 ` Heming Zhao
2026-09-16 7:07 ` Daniel Borkmann
2026-09-16 7:28 ` Heming Zhao
2026-09-16 7:28 ` Joseph Qi
2026-09-16 7:37 ` Daniel Borkmann
2026-09-16 7:49 ` Joseph Qi
2026-09-16 7:29 ` Heming Zhao
2026-09-15 15:07 ` [PATCH bpf-next 2/8] bpf, lsm: Reject writes into the BPF LSM program context Daniel Borkmann
2026-09-15 15:07 ` [PATCH bpf-next 3/8] bpf: Support passing context output arguments to kfuncs Daniel Borkmann
2026-09-15 15:07 ` [PATCH bpf-next 4/8] bpf, lsm: Let BPF LSM provide xattrs at inode creation Daniel Borkmann
2026-09-23 16:57 ` Paul Moore
2026-09-23 19:11 ` Daniel Borkmann
2026-09-23 20:51 ` Paul Moore
2026-09-23 19:14 ` David Windsor
2026-09-23 20:56 ` Paul Moore
2026-09-23 21:07 ` Paul Moore
2026-09-24 16:14 ` Justin Suess
2026-09-24 16:23 ` Paul Moore
2026-09-24 18:37 ` Justin Suess
2026-09-24 19:33 ` Paul Moore
2026-09-24 19:34 ` Paul Moore
2026-09-15 15:07 ` [PATCH bpf-next 5/8] bpf, lsm: Mark the BPF LSM hook overrides noinline Daniel Borkmann
2026-09-15 15:07 ` [PATCH bpf-next 6/8] selftests/bpf: Test that the BPF LSM context is read-only Daniel Borkmann
2026-09-15 15:07 ` [PATCH bpf-next 7/8] selftests/bpf: Add verifier tests for the __ctx_out plumbing Daniel Borkmann
2026-09-15 16:26 ` bot+bpf-ci
2026-09-15 15:07 ` Daniel Borkmann [this message]
2026-09-19 19:10 ` [PATCH bpf-next 0/8] BPF LSM xattrs at inode creation support patchwork-bot+netdevbpf
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260915150739.284189-9-daniel@iogearbox.net \
--to=daniel@iogearbox.net \
--cc=alexei.starovoitov@gmail.com \
--cc=bpf@vger.kernel.org \
--cc=brauner@kernel.org \
--cc=dwindsor@gmail.com \
--cc=john.fastabend@gmail.com \
--cc=kpsingh@kernel.org \
--cc=matt@bobrowski.net \
--cc=memxor@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox