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
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 ` 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