BPF List
 help / color / mirror / Atom feed
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 7/8] selftests/bpf: Add verifier tests for the __ctx_out plumbing
Date: Tue, 15 Sep 2026 17:07:38 +0200	[thread overview]
Message-ID: <20260915150739.284189-8-daniel@iogearbox.net> (raw)
In-Reply-To: <20260915150739.284189-1-daniel@iogearbox.net>

Add BPF selftests to cover what a BPF program may and may not hand
to bpf_inode_init_xattr() as the inode_init_security hook's args.

  # LDLIBS=-static PKG_CONFIG='pkg-config --static' ./vmtest.sh -- ./test_progs -t verifier_lsm_init_xattr
  [...]
  #649/1   verifier_lsm_init_xattr/reject_count_write:OK
  #649/2   verifier_lsm_init_xattr/allow_count_read:OK
  #649/3   verifier_lsm_init_xattr/reject_unchecked_xattrs:OK
  #649/4   verifier_lsm_init_xattr/reject_unchecked_qstr:OK
  #649/5   verifier_lsm_init_xattr/allow_spilled_count:OK
  #649/6   verifier_lsm_init_xattr/reject_forged_count:OK
  #649/7   verifier_lsm_init_xattr/reject_stack_count:OK
  #649/8   verifier_lsm_init_xattr/reject_other_ctx_arg:OK
  #649/9   verifier_lsm_init_xattr/reject_null_count:OK
  #649/10  verifier_lsm_init_xattr/reject_shifted_count:OK
  #649/11  verifier_lsm_init_xattr/reject_var_shifted_count:OK
  #649/12  verifier_lsm_init_xattr/reject_ctx_forged_count:OK
  #649/13  verifier_lsm_init_xattr/allow_count_via_subprog:OK
  #649/14  verifier_lsm_init_xattr/reject_shifted_xattrs:OK
  #649/15  verifier_lsm_init_xattr/reject_sleepable:OK
  #649/16  verifier_lsm_init_xattr/reject_lsm_cgroup:OK
  #649/17  verifier_lsm_init_xattr/reject_wrong_hook:OK
  #649     verifier_lsm_init_xattr:OK
  Summary: 1/17 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/bpf_kfuncs.h      |  19 +-
 .../selftests/bpf/prog_tests/verifier.c       |   2 +
 .../bpf/progs/verifier_lsm_init_xattr.c       | 245 ++++++++++++++++++
 3 files changed, 262 insertions(+), 4 deletions(-)
 create mode 100644 tools/testing/selftests/bpf/progs/verifier_lsm_init_xattr.c

diff --git a/tools/testing/selftests/bpf/bpf_kfuncs.h b/tools/testing/selftests/bpf/bpf_kfuncs.h
index ae71e9b69051..1b408fe9e0fb 100644
--- a/tools/testing/selftests/bpf/bpf_kfuncs.h
+++ b/tools/testing/selftests/bpf/bpf_kfuncs.h
@@ -78,18 +78,29 @@ extern void bpf_key_put(struct bpf_key *key) __ksym;
 extern int bpf_verify_pkcs7_signature(const struct bpf_dynptr *data_ptr,
 				      const struct bpf_dynptr *sig_ptr,
 				      struct bpf_key *trusted_keyring) __ksym;
-
-struct dentry;
-/* Description
+/*
+ * Description
  *  Returns xattr of a dentry
  * Returns
  *  Error code
  */
+struct dentry;
 extern int bpf_get_dentry_xattr(struct dentry *dentry, const char *name,
 			      struct bpf_dynptr *value_ptr) __ksym __weak;
-
 extern int bpf_set_dentry_xattr(struct dentry *dentry, const char *name__str,
 				const struct bpf_dynptr *value_p, int flags) __ksym __weak;
 extern int bpf_remove_dentry_xattr(struct dentry *dentry, const char *name__str) __ksym __weak;
 
+/*
+ * Description
+ *  Attach a xattr to an inode that is being created, from a program on the
+ *  inode_init_security LSM hook. *xattrs* and *xattr_count* must be the
+ *  hook's own arguments, passed through unmodified.
+ * Returns
+ *  0 on success, a negative value on error
+ */
+struct xattr;
+extern int bpf_inode_init_xattr(struct xattr *xattrs, int *xattr_count,
+				const char *name__str,
+				const struct bpf_dynptr *value_p) __ksym __weak;
 #endif
diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c
index 7732df9bc870..973bbeda9318 100644
--- a/tools/testing/selftests/bpf/prog_tests/verifier.c
+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c
@@ -130,6 +130,7 @@
 #include "verifier_bits_iter.skel.h"
 #include "verifier_set_retval.skel.h"
 #include "verifier_lsm.skel.h"
+#include "verifier_lsm_init_xattr.skel.h"
 #include "verifier_jit_inline.skel.h"
 #include "irq.skel.h"
 #include "verifier_ctx_ptr_param.skel.h"
@@ -294,6 +295,7 @@ void test_verifier_xdp_direct_packet_access(void) { RUN(verifier_xdp_direct_pack
 void test_verifier_bits_iter(void) { RUN(verifier_bits_iter); }
 void test_verifier_set_retval(void)            { RUN(verifier_set_retval); }
 void test_verifier_lsm(void)                  { RUN(verifier_lsm); }
+void test_verifier_lsm_init_xattr(void)       { RUN(verifier_lsm_init_xattr); }
 void test_irq(void)			      { RUN(irq); }
 void test_verifier_mtu(void)		      { RUN(verifier_mtu); }
 void test_verifier_jit_inline(void)               { RUN(verifier_jit_inline); }
diff --git a/tools/testing/selftests/bpf/progs/verifier_lsm_init_xattr.c b/tools/testing/selftests/bpf/progs/verifier_lsm_init_xattr.c
new file mode 100644
index 000000000000..b706bf17b556
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_lsm_init_xattr.c
@@ -0,0 +1,245 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_kfuncs.h"
+#include "bpf_misc.h"
+
+char _license[] SEC("license") = "GPL";
+
+const char xattr_zone[] = "security.bpf.zone";
+char value_buf[8] = "z";
+int scratch_count;
+
+SEC("lsm/inode_init_security")
+__failure __msg("cannot write into rdonly_trusted_mem")
+int BPF_PROG(reject_count_write, struct inode *inode, struct inode *dir,
+	     const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
+{
+	*xattr_count = 0;
+	return 0;
+}
+
+SEC("lsm/inode_init_security")
+__success
+int BPF_PROG(allow_count_read, struct inode *inode, struct inode *dir,
+	     const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
+{
+	scratch_count = *xattr_count;
+	return 0;
+}
+
+SEC("lsm/inode_init_security")
+__failure __msg("Possibly NULL pointer passed to trusted")
+int BPF_PROG(reject_unchecked_xattrs, struct inode *inode, struct inode *dir,
+	     const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
+{
+	struct bpf_dynptr value;
+
+	bpf_dynptr_from_mem(value_buf, sizeof(value_buf), 0, &value);
+	bpf_inode_init_xattr(xattrs, xattr_count, xattr_zone, &value);
+	return 0;
+}
+
+SEC("lsm/inode_init_security")
+__failure __msg("invalid mem access 'trusted_ptr_or_null_'")
+int BPF_PROG(reject_unchecked_qstr, struct inode *inode, struct inode *dir,
+	     const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
+{
+	scratch_count = qstr->len;
+	return 0;
+}
+
+SEC("lsm/inode_init_security")
+__success
+int BPF_PROG(allow_spilled_count, struct inode *inode, struct inode *dir,
+	     const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
+{
+	struct bpf_dynptr value;
+	int *saved = xattr_count;
+
+	if (!xattrs)
+		return 0;
+
+	bpf_dynptr_from_mem(value_buf, sizeof(value_buf), 0, &value);
+	bpf_inode_init_xattr(xattrs, saved, xattr_zone, &value);
+	return 0;
+}
+
+SEC("lsm/inode_init_security")
+__failure __msg("expected=rdonly_trusted_mem")
+int BPF_PROG(reject_forged_count, struct inode *inode, struct inode *dir,
+	     const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
+{
+	struct bpf_dynptr value;
+
+	if (!xattrs)
+		return 0;
+
+	bpf_dynptr_from_mem(value_buf, sizeof(value_buf), 0, &value);
+	bpf_inode_init_xattr(xattrs, &scratch_count, xattr_zone, &value);
+	return 0;
+}
+
+SEC("lsm/inode_init_security")
+__failure __msg("expected=rdonly_trusted_mem")
+int BPF_PROG(reject_stack_count, struct inode *inode, struct inode *dir,
+	     const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
+{
+	struct bpf_dynptr value;
+	int local = 0;
+
+	if (!xattrs)
+		return 0;
+
+	bpf_dynptr_from_mem(value_buf, sizeof(value_buf), 0, &value);
+	bpf_inode_init_xattr(xattrs, &local, xattr_zone, &value);
+	return 0;
+}
+
+SEC("lsm/inode_init_security")
+__failure __msg("expected=rdonly_trusted_mem")
+int BPF_PROG(reject_other_ctx_arg, struct inode *inode, struct inode *dir,
+	     const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
+{
+	struct bpf_dynptr value;
+
+	if (!xattrs)
+		return 0;
+
+	bpf_dynptr_from_mem(value_buf, sizeof(value_buf), 0, &value);
+	bpf_inode_init_xattr(xattrs, (int *)xattrs, xattr_zone, &value);
+	return 0;
+}
+
+SEC("lsm/inode_init_security")
+__failure __msg("Possibly NULL pointer passed to trusted")
+int BPF_PROG(reject_null_count, struct inode *inode, struct inode *dir,
+	     const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
+{
+	struct bpf_dynptr value;
+
+	if (!xattrs)
+		return 0;
+
+	bpf_dynptr_from_mem(value_buf, sizeof(value_buf), 0, &value);
+	bpf_inode_init_xattr(xattrs, NULL, xattr_zone, &value);
+	return 0;
+}
+
+SEC("lsm/inode_init_security")
+__failure __msg("dereference of modified rdonly_trusted_mem ptr")
+int BPF_PROG(reject_shifted_count, struct inode *inode, struct inode *dir,
+	     const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
+{
+	struct bpf_dynptr value;
+
+	if (!xattrs)
+		return 0;
+
+	bpf_dynptr_from_mem(value_buf, sizeof(value_buf), 0, &value);
+	bpf_inode_init_xattr(xattrs, xattr_count + 1, xattr_zone, &value);
+	return 0;
+}
+
+SEC("lsm/inode_init_security")
+__failure __msg("variable rdonly_trusted_mem access var_off=")
+int BPF_PROG(reject_var_shifted_count, struct inode *inode, struct inode *dir,
+	     const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
+{
+	struct bpf_dynptr value;
+
+	if (!xattrs)
+		return 0;
+
+	bpf_dynptr_from_mem(value_buf, sizeof(value_buf), 0, &value);
+	bpf_inode_init_xattr(xattrs, xattr_count + (scratch_count & 1),
+			     xattr_zone, &value);
+	return 0;
+}
+
+SEC("lsm/inode_init_security")
+__failure __msg("invalid bpf_context access")
+int reject_ctx_forged_count(unsigned long long *ctx)
+{
+	volatile unsigned long long *slot = ctx;
+	struct bpf_dynptr value;
+
+	bpf_dynptr_from_mem(value_buf, sizeof(value_buf), 0, &value);
+	slot[4] = 0;
+	bpf_inode_init_xattr((struct xattr *)(long)slot[3],
+			     (int *)(long)slot[4], xattr_zone, &value);
+	return 0;
+}
+
+static __noinline int claim_via_subprog(struct xattr *xattrs, int *xattr_count,
+					struct bpf_dynptr *value)
+{
+	return bpf_inode_init_xattr(xattrs, xattr_count, xattr_zone, value);
+}
+
+SEC("lsm/inode_init_security")
+__success
+int BPF_PROG(allow_count_via_subprog, struct inode *inode, struct inode *dir,
+	     const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
+{
+	struct bpf_dynptr value;
+
+	if (!xattrs)
+		return 0;
+
+	bpf_dynptr_from_mem(value_buf, sizeof(value_buf), 0, &value);
+	claim_via_subprog(xattrs, xattr_count, &value);
+	return 0;
+}
+
+SEC("lsm/inode_init_security")
+__failure __msg("access beyond struct xattr at off 24")
+int BPF_PROG(reject_shifted_xattrs, struct inode *inode, struct inode *dir,
+	     const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
+{
+	struct bpf_dynptr value;
+
+	if (!xattrs)
+		return 0;
+
+	bpf_dynptr_from_mem(value_buf, sizeof(value_buf), 0, &value);
+	bpf_inode_init_xattr(xattrs + 1, xattr_count, xattr_zone, &value);
+	return 0;
+}
+
+SEC("lsm.s/inode_init_security")
+__failure __msg("bpf_lsm_inode_init_security is not sleepable")
+int BPF_PROG(reject_sleepable, struct inode *inode, struct inode *dir,
+	     const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
+{
+	return 0;
+}
+
+SEC("lsm_cgroup/inode_init_security")
+__failure __msg("calling kernel function bpf_inode_init_xattr is not allowed")
+int BPF_PROG(reject_lsm_cgroup, struct inode *inode, struct inode *dir,
+	     const struct qstr *qstr, struct xattr *xattrs, int *xattr_count)
+{
+	struct bpf_dynptr value;
+
+	if (!xattrs)
+		return 0;
+
+	bpf_dynptr_from_mem(value_buf, sizeof(value_buf), 0, &value);
+	bpf_inode_init_xattr(xattrs, xattr_count, xattr_zone, &value);
+	return 0;
+}
+
+SEC("lsm/inode_setxattr")
+__failure __msg("calling kernel function bpf_inode_init_xattr is not allowed")
+int BPF_PROG(reject_wrong_hook, struct mnt_idmap *idmap, struct dentry *dentry,
+	     const char *name, const void *value, size_t size, int flags)
+{
+	struct bpf_dynptr val;
+
+	bpf_dynptr_from_mem(value_buf, sizeof(value_buf), 0, &val);
+	bpf_inode_init_xattr(NULL, &scratch_count, xattr_zone, &val);
+	return 0;
+}
-- 
2.43.0


  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 ` Daniel Borkmann [this message]
2026-09-15 16:26   ` [PATCH bpf-next 7/8] selftests/bpf: Add verifier tests for the __ctx_out plumbing bot+bpf-ci
2026-09-15 15:07 ` [PATCH bpf-next 8/8] selftests/bpf: Add tests for BPF LSM inode init labelling Daniel Borkmann
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-8-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