* [PATCH bpf-next 0/8] BPF LSM xattrs at inode creation support
@ 2026-09-15 15:07 Daniel Borkmann
2026-09-15 15:07 ` [PATCH bpf-next 1/8] ocfs2: Copy the xattr name in ocfs2_initxattrs Daniel Borkmann
` (8 more replies)
0 siblings, 9 replies; 31+ messages in thread
From: Daniel Borkmann @ 2026-09-15 15:07 UTC (permalink / raw)
To: alexei.starovoitov
Cc: brauner, dwindsor, john.fastabend, memxor, kpsingh, matt, bpf
Many in-kernel LSMs store security labels in extended file system attributes
(xattrs). For these LSMs, atomic labeling during inode creation is critical:
If the inode becomes accessible before its xattr is set, it is briefly
unlabeled, which can disrupt LSMs making policy decisions based on file
labels. Existing LSMs solve this by setting xattrs in the inode_init_security
hook, which runs before the inode becomes accessible. BPF LSM programs
currently lack this capability, and this series addresses this gap along
with BPF selftests. Thanks!
Daniel Borkmann (6):
ocfs2: Copy the xattr name in ocfs2_initxattrs
bpf, lsm: Reject writes into the BPF LSM program context
bpf, lsm: Mark the BPF LSM hook overrides noinline
selftests/bpf: Test that the BPF LSM context is read-only
selftests/bpf: Add verifier tests for the __ctx_out plumbing
selftests/bpf: Add tests for BPF LSM inode init labelling
David Windsor (2):
bpf: Support passing context output arguments to kfuncs
bpf, lsm: Let BPF LSM provide xattrs at inode creation
Documentation/bpf/kfuncs.rst | 23 +
fs/bpf_fs_kfuncs.c | 99 +++++
fs/ocfs2/namei.c | 2 +
fs/ocfs2/xattr.c | 5 +-
include/linux/bpf.h | 3 +
include/linux/bpf_lsm.h | 11 +-
kernel/bpf/bpf_lsm.c | 26 +-
kernel/bpf/bpf_lsm_proto.c | 19 +-
kernel/bpf/btf.c | 7 +-
kernel/bpf/diagnostics.c | 2 +
kernel/bpf/verifier.c | 36 +-
security/bpf/hooks.c | 1 +
tools/testing/selftests/bpf/bpf_kfuncs.h | 19 +-
tools/testing/selftests/bpf/config | 2 +
.../bpf/prog_tests/lsm_inode_init_xattr.c | 399 ++++++++++++++++++
.../selftests/bpf/prog_tests/verifier.c | 2 +
.../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 +++
.../selftests/bpf/progs/verifier_lsm.c | 13 +
.../bpf/progs/verifier_lsm_init_xattr.c | 245 +++++++++++
21 files changed, 1080 insertions(+), 16 deletions(-)
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
create mode 100644 tools/testing/selftests/bpf/progs/verifier_lsm_init_xattr.c
--
2.43.0
^ permalink raw reply [flat|nested] 31+ messages in thread
* [PATCH bpf-next 1/8] ocfs2: Copy the xattr name in ocfs2_initxattrs
2026-09-15 15:07 [PATCH bpf-next 0/8] BPF LSM xattrs at inode creation support Daniel Borkmann
@ 2026-09-15 15:07 ` Daniel Borkmann
2026-09-15 15:16 ` sashiko-bot
` (4 more replies)
2026-09-15 15:07 ` [PATCH bpf-next 2/8] bpf, lsm: Reject writes into the BPF LSM program context Daniel Borkmann
` (7 subsequent siblings)
8 siblings, 5 replies; 31+ messages in thread
From: Daniel Borkmann @ 2026-09-15 15:07 UTC (permalink / raw)
To: alexei.starovoitov
Cc: brauner, dwindsor, john.fastabend, memxor, kpsingh, matt, bpf,
Zhan Xusheng, Joseph Qi, ocfs2-devel
ocfs2_mknod() and ocfs2_symlink() ask for the security xattr up front
through a struct ocfs2_security_xattr_info, so that they can size the
transaction before setting it. ocfs2_initxattrs() duplicates the value
since the array security_inode_init_security() hands is freed on return,
but keeps the name pointer as-is, given so far every LSM stored a string
constant there. bpf_inode_init_xattr() places the name in the same
allocation as the value, which security_inode_init_security() frees on
its way out. Copy the name alongside the value and free both together.
This is the only special case wrt xattrs in the bpf_inode_init_xattr()
context.
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Cc: Zhan Xusheng <zhanxusheng@xiaomi.com>
Cc: Joseph Qi <joseph.qi@linux.alibaba.com>
Cc: ocfs2-devel@lists.linux.dev
---
fs/ocfs2/namei.c | 2 ++
fs/ocfs2/xattr.c | 5 +++--
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c
index e9c7774ccf91..e24f0e337a56 100644
--- a/fs/ocfs2/namei.c
+++ b/fs/ocfs2/namei.c
@@ -480,6 +480,7 @@ static int ocfs2_mknod(struct mnt_idmap *idmap,
brelse(new_fe_bh);
brelse(parent_fe_bh);
+ kfree(si.name);
kfree(si.value);
ocfs2_acl_init_release(&acl_state);
@@ -2068,6 +2069,7 @@ static int ocfs2_symlink(struct mnt_idmap *idmap,
brelse(new_fe_bh);
brelse(parent_fe_bh);
+ kfree(si.name);
kfree(si.value);
ocfs2_free_dir_lookup_result(&lookup);
if (inode_ac)
diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c
index 35bcbb0ff607..d83840b6bed9 100644
--- a/fs/ocfs2/xattr.c
+++ b/fs/ocfs2/xattr.c
@@ -7524,8 +7524,9 @@ static int ocfs2_initxattrs(struct inode *inode, const struct xattr *xattr_array
GFP_KERNEL);
if (!si->value)
return -ENOMEM;
-
- si->name = xattr_array->name;
+ si->name = kstrdup(xattr_array->name, GFP_KERNEL);
+ if (!si->name)
+ return -ENOMEM;
si->value_len = xattr_array->value_len;
return 0;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH bpf-next 2/8] bpf, lsm: Reject writes into the BPF LSM program context
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:07 ` Daniel Borkmann
2026-09-15 15:07 ` [PATCH bpf-next 3/8] bpf: Support passing context output arguments to kfuncs Daniel Borkmann
` (6 subsequent siblings)
8 siblings, 0 replies; 31+ messages in thread
From: Daniel Borkmann @ 2026-09-15 15:07 UTC (permalink / raw)
To: alexei.starovoitov
Cc: brauner, dwindsor, john.fastabend, memxor, kpsingh, matt, bpf
The verifier types a load from a tracing-style context by its offset
alone: btf_ctx_access() looks the offset up in the attach hook's
prototype, and check_mem_access() stamps the resulting type onto the
destination register. Nothing tracks what the slot actually holds.
That is sound only as long as the context is read-only, and for BPF LSM
it is not. lsm_verifier_ops wires ->is_valid_access() straight to
btf_ctx_access(), which never looks at enum bpf_access_type, so a store
into the context passes verification. Every other user of the helper
either goes through bpf_tracing_btf_ctx_access() or open-codes the same
type != BPF_READ test first; BPF LSM is the only one that does neither.
A program can therefore store a scalar of its choosing into a context
slot, read the slot back, and get a register the verifier believes is a
trusted pointer of the hook's argument type. Handing that to a kfunc or
helper which dereferences a trusted argument turns it into an arbitrary
kernel access. Route BPF LSM through bpf_tracing_btf_ctx_access() like
everyone else.
Fixes: fc611f47f218 ("bpf: Introduce BPF_PROG_TYPE_LSM")
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
---
kernel/bpf/bpf_lsm.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
index 82c5988417a0..2660a89fc8d7 100644
--- a/kernel/bpf/bpf_lsm.c
+++ b/kernel/bpf/bpf_lsm.c
@@ -414,8 +414,8 @@ const struct bpf_prog_ops lsm_prog_ops = {
};
const struct bpf_verifier_ops lsm_verifier_ops = {
- .get_func_proto = bpf_lsm_func_proto,
- .is_valid_access = btf_ctx_access,
+ .get_func_proto = bpf_lsm_func_proto,
+ .is_valid_access = bpf_tracing_btf_ctx_access,
};
/* hooks return 0 or 1 */
--
2.43.0
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH bpf-next 3/8] bpf: Support passing context output arguments to kfuncs
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:07 ` [PATCH bpf-next 2/8] bpf, lsm: Reject writes into the BPF LSM program context Daniel Borkmann
@ 2026-09-15 15:07 ` Daniel Borkmann
2026-09-15 15:07 ` [PATCH bpf-next 4/8] bpf, lsm: Let BPF LSM provide xattrs at inode creation Daniel Borkmann
` (5 subsequent siblings)
8 siblings, 0 replies; 31+ messages in thread
From: Daniel Borkmann @ 2026-09-15 15:07 UTC (permalink / raw)
To: alexei.starovoitov
Cc: brauner, dwindsor, john.fastabend, memxor, kpsingh, matt, bpf
From: David Windsor <dwindsor@gmail.com>
Allows programs to pass a context argument that points to a scalar
output value on to a kfunc that writes the result on the program's
behalf.
A ctx_arg_info entry can now describe a fixed-size PTR_TO_MEM context
argument through a new mem_size field, honored in btf_ctx_access().
Loading the argument yields a PTR_TO_MEM | MEM_RDONLY | PTR_TRUSTED
register of known size, readable but not writable by the program. A
kfunc argument tagged with the new "__ctx_out" suffix accepts only that
register type with a size matching the pointed-to type, so the only
value a program can pass is an output argument from its own context.
The pointer stays read-only to the program because the value is
trusted by whoever invoked the BPF program. In the first use case, the
inode_init_security LSM hook, every LSM receives a shared xattr array
and an int *xattr_count that lsm_get_xattr_slot() increments; a program
that could store a garbage count would push another LSM's write out of
bounds, so only the kfunc itself writes through it.
Suggested-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Signed-off-by: David Windsor <dwindsor@gmail.com>
Co-developed-by: Daniel Borkmann <daniel@iogearbox.net>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
---
Documentation/bpf/kfuncs.rst | 23 +++++++++++++++++++++++
include/linux/bpf.h | 3 +++
kernel/bpf/btf.c | 7 +++++--
kernel/bpf/diagnostics.c | 2 ++
kernel/bpf/verifier.c | 36 +++++++++++++++++++++++++++++++++++-
5 files changed, 68 insertions(+), 3 deletions(-)
diff --git a/Documentation/bpf/kfuncs.rst b/Documentation/bpf/kfuncs.rst
index 6c2c048dccef..3f300118a623 100644
--- a/Documentation/bpf/kfuncs.rst
+++ b/Documentation/bpf/kfuncs.rst
@@ -315,6 +315,29 @@ However, there is no obligation to prove to the verifier that such a pointer is
non-NULL before use, in-line with existing semantics of arena pointers used in
a program (or obtained from any other source).
+2.3.9 __ctx_out Annotation
+--------------------------
+
+This annotation is used to indicate that the argument is an output
+parameter of the attached hook, passed through from the program's
+context. The verifier requires the register to be a trusted read-only
+pointer to fixed-size memory, which can only be produced by loading an
+argument described by the program's ctx_arg_info from the context. The
+program itself cannot write through the pointer; the kfunc may.
+
+An example is given below::
+
+ __bpf_kfunc int bpf_inode_init_xattr(struct xattr *xattrs,
+ int *xattr_count__ctx_out,
+ ...)
+ {
+ ...
+ }
+
+In this case, a program attached to the ``inode_init_security`` LSM hook
+can pass the hook's own ``xattr_count`` argument through to the kfunc,
+which claims xattr slots by writing through it.
+
.. _BPF_kfunc_nodef:
2.4 Using an existing kernel function
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 2a5fa346aada..f72413a383ba 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -923,6 +923,7 @@ enum bpf_arg_type {
ARG_PTR_TO_TASK_WORK, /* pointer to bpf_task_work */
ARG_PTR_TO_IRQ_FLAG, /* pointer to saved IRQ flags on the stack */
ARG_PTR_TO_RES_SPIN_LOCK, /* pointer to bpf_res_spin_lock */
+ ARG_PTR_TO_CTX_OUT, /* hook output argument passed through from ctx */
ARG_PTR_TO_PROG_AUX, /* pointer to the caller's bpf_prog_aux */
ARG_IGNORE, /* argument the verifier does not check at all */
__BPF_ARG_TYPE_MAX,
@@ -1137,6 +1138,7 @@ struct bpf_insn_access_aux {
u32 ref_id;
};
};
+ u32 mem_size;
struct bpf_verifier_log *log; /* for verbose logs */
bool is_retval; /* is accessing function return value ? */
};
@@ -1715,6 +1717,7 @@ struct bpf_ctx_arg_aux {
struct btf *btf;
u32 btf_id;
u32 ref_id;
+ u32 mem_size;
bool refcounted;
};
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index 7daf4c286c9b..8bc463e31704 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -6994,8 +6994,9 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type,
}
/*
- * Check for PTR_TO_RDONLY_BUF_OR_NULL, PTR_TO_RDWR_BUF_OR_NULL or
- * PTR_TO_ARENA (both nullable and non-nullable cases).
+ * Check for PTR_TO_RDONLY_BUF_OR_NULL, PTR_TO_RDWR_BUF_OR_NULL,
+ * PTR_TO_ARENA (both nullable and non-nullable cases) or fixed-size
+ * PTR_TO_MEM.
*/
for (i = 0; i < prog->aux->ctx_arg_info_size; i++) {
const struct bpf_ctx_arg_aux *ctx_arg_info = &prog->aux->ctx_arg_info[i];
@@ -7005,8 +7006,10 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type,
flag = type_flag(ctx_arg_info->reg_type);
if (ctx_arg_info->offset == off &&
(type == PTR_TO_ARENA ||
+ type == PTR_TO_MEM ||
(type == PTR_TO_BUF && (flag & PTR_MAYBE_NULL)))) {
info->reg_type = ctx_arg_info->reg_type;
+ info->mem_size = ctx_arg_info->mem_size;
return true;
}
}
diff --git a/kernel/bpf/diagnostics.c b/kernel/bpf/diagnostics.c
index a2cac59c6639..787932f92cdf 100644
--- a/kernel/bpf/diagnostics.c
+++ b/kernel/bpf/diagnostics.c
@@ -984,6 +984,8 @@ const char *bpf_diag_arg_type_plain(enum bpf_arg_type type)
return "the address of a stack iterator object for iterator new, next, and destroy calls";
case ARG_PTR_TO_IRQ_FLAG:
return "the same stack slot used by bpf_local_irq_save() or bpf_res_spin_lock_irqsave()";
+ case ARG_PTR_TO_CTX_OUT:
+ return "the attach hook's own output argument, loaded directly from the program context";
default:
return "a value with one of the accepted pointer or scalar types for this call";
}
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 6c6b8d8520cd..cf067634d3c3 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -6579,6 +6579,8 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, struct b
regs[value_regno].btf = info.btf;
regs[value_regno].btf_id = info.btf_id;
regs[value_regno].id = info.ref_id;
+ } else if (base_type(info.reg_type) == PTR_TO_MEM) {
+ regs[value_regno].mem_size = info.mem_size;
}
if (type_may_be_null(info.reg_type) && !regs[value_regno].id)
regs[value_regno].id = ++env->id_gen;
@@ -8358,6 +8360,9 @@ static const struct bpf_reg_types arena_types = {
SCALAR_VALUE,
}
};
+static const struct bpf_reg_types ctx_out_types = {
+ .types = { PTR_TO_MEM | MEM_RDONLY | PTR_TRUSTED },
+};
static const struct bpf_reg_types alloc_obj_drop_types = {
.types = {
@@ -8429,6 +8434,7 @@ static const struct bpf_reg_types *compatible_reg_types[__BPF_ARG_TYPE_MAX] = {
[ARG_PTR_TO_TASK_WORK] = &map_value_types,
[ARG_PTR_TO_IRQ_FLAG] = &stack_ptr_types,
[ARG_PTR_TO_ARENA] = &arena_types,
+ [ARG_PTR_TO_CTX_OUT] = &ctx_out_types,
};
static void bpf_diag_call_arg(struct bpf_verifier_env *env, u32 insn_idx, argno_t argno,
@@ -9421,6 +9427,13 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg, u32 slot, u32 p
if (err < 0)
return err;
break;
+ case ARG_PTR_TO_CTX_OUT:
+ if (reg->mem_size != arg_size) {
+ verbose(env, "%s expected %u bytes of ctx-provided memory, got %u\n",
+ reg_arg_name(env, argno), arg_size, reg->mem_size);
+ return -EINVAL;
+ }
+ break;
case ARG_PTR_TO_RES_SPIN_LOCK:
{
int flags;
@@ -12137,6 +12150,11 @@ static bool is_kfunc_arg_irq_flag(const struct btf *btf, const struct btf_param
return btf_param_match_suffix(btf, arg, "__irq_flag");
}
+static bool is_kfunc_arg_ctx_out(const struct btf *btf, const struct btf_param *arg)
+{
+ return btf_param_match_suffix(btf, arg, "__ctx_out");
+}
+
static bool is_kfunc_arg_arena(const struct btf *btf, const struct btf_param *arg)
{
return btf_param_match_suffix(btf, arg, "__arena__nullable") ||
@@ -12900,7 +12918,23 @@ get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
arg_type = ARG_PTR_TO_IRQ_FLAG;
else if (is_kfunc_arg_res_spin_lock(meta->btf, &args[arg]))
arg_type = ARG_PTR_TO_RES_SPIN_LOCK;
- else if (is_kfunc_arg_callback(env, meta->btf, &args[arg]))
+ else if (is_kfunc_arg_ctx_out(meta->btf, &args[arg])) {
+ if (!btf_type_is_scalar(ref_t)) {
+ verbose(env, "%s __ctx_out argument must point to a scalar\n",
+ reg_arg_name(env, argno));
+ return -EINVAL;
+ }
+ resolve_ret = btf_resolve_size(meta->btf, ref_t, &type_size);
+ if (IS_ERR(resolve_ret)) {
+ verbose(env,
+ "%s reference type('%s %s') size cannot be determined: %ld\n",
+ reg_arg_name(env, argno), btf_type_str(ref_t),
+ ref_tname, PTR_ERR(resolve_ret));
+ return -EINVAL;
+ }
+ proto->arg_size[arg] = type_size;
+ arg_type = ARG_PTR_TO_CTX_OUT | MEM_FIXED_SIZE;
+ } else if (is_kfunc_arg_callback(env, meta->btf, &args[arg]))
arg_type = ARG_PTR_TO_FUNC;
else if (is_kfunc_arg_arena(meta->btf, &args[arg])) {
if (!bpf_jit_supports_arena_args()) {
--
2.43.0
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH bpf-next 4/8] bpf, lsm: Let BPF LSM provide xattrs at inode creation
2026-09-15 15:07 [PATCH bpf-next 0/8] BPF LSM xattrs at inode creation support Daniel Borkmann
` (2 preceding siblings ...)
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 ` Daniel Borkmann
2026-09-23 16:57 ` Paul Moore
2026-09-15 15:07 ` [PATCH bpf-next 5/8] bpf, lsm: Mark the BPF LSM hook overrides noinline Daniel Borkmann
` (4 subsequent siblings)
8 siblings, 1 reply; 31+ messages in thread
From: Daniel Borkmann @ 2026-09-15 15:07 UTC (permalink / raw)
To: alexei.starovoitov
Cc: brauner, dwindsor, john.fastabend, memxor, kpsingh, matt, bpf
From: David Windsor <dwindsor@gmail.com>
Many in-kernel LSMs (SELinux, Smack, IMA) store security labels in extended
attributes. For these LSMs, atomic labeling during inode creation is
critical: if the inode becomes accessible before its xattr is set, it is
briefly unlabeled, which can disrupt LSMs making policy decisions based
on file labels. Existing LSMs solve this by setting xattrs in the
inode_init_security hook, which runs before the inode becomes accessible.
BPF LSM programs currently lack this capability because the hook uses an
output parameter (xattr_count) that BPF programs cannot write to, and
existing kfuncs like bpf_set_dentry_xattr() require a dentry that isn't
available until after the inode is accessible.
Add a bpf_inode_init_xattr() kfunc that takes the hook's own xattrs and
xattr_count arguments, passed through from the program's context, and
claims a slot via lsm_get_xattr_slot() on the program's behalf. The
xattr_count output argument is exposed to inode_init_security programs
as trusted read-only memory, so programs can pass it to the kfunc but
cannot modify the count themselves.
Reserve BPF_LSM_INODE_INIT_XATTRS slots in bpf_lsm_blob_sizes the way
every other xattr-providing LSM does, for the life of the kernel. The
framework keys the collection off the reserved slot count, so a kernel
built with CONFIG_BPF_LSM=y now allocates the xattr array on every inode
creation, whether or not a program sits on the hook.
Give the hook a strong prototype in bpf_lsm_proto.c so that its qstr and
xattrs arguments are marked __nullable. Both can be NULL for some callers.
Without the annotation the verifier otherwise hands the program a trusted
non-NULL pointer which it dereferences. Also, keep the hook out of the
sleepable set. inode_init_security runs inside the transaction creating
the inode, with a journal handle held on ext4 and btrfs and the parent's
i_rwsem down, which is why everything on the path allocates GFP_NOFS.
Signed-off-by: David Windsor <dwindsor@gmail.com>
Co-developed-by: Daniel Borkmann <daniel@iogearbox.net>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
---
fs/bpf_fs_kfuncs.c | 99 ++++++++++++++++++++++++++++++++++++++
include/linux/bpf_lsm.h | 11 ++++-
kernel/bpf/bpf_lsm.c | 22 ++++++++-
kernel/bpf/bpf_lsm_proto.c | 15 ++++++
security/bpf/hooks.c | 1 +
5 files changed, 145 insertions(+), 3 deletions(-)
diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c
index 6cb877267978..c51c3ae8063b 100644
--- a/fs/bpf_fs_kfuncs.c
+++ b/fs/bpf_fs_kfuncs.c
@@ -11,6 +11,7 @@
#include <linux/fsnotify.h>
#include <linux/file.h>
#include <linux/kernfs.h>
+#include <linux/lsm_hooks.h>
#include <linux/mm.h>
#include <linux/net.h>
#include <linux/xattr.h>
@@ -328,6 +329,89 @@ __bpf_kfunc int bpf_remove_dentry_xattr(struct dentry *dentry, const char *name_
return ret;
}
+static int bpf_inode_init_xattrs_claimed(const struct xattr *xattrs, int xattr_count)
+{
+ const size_t suffix_len = sizeof(XATTR_BPF_LSM_SUFFIX) - 1;
+ int i, claimed = 0;
+
+ for (i = 0; i < xattr_count; i++) {
+ const char *name = xattrs[i].name;
+
+ if (name && !strncmp(name, XATTR_BPF_LSM_SUFFIX, suffix_len))
+ claimed++;
+ }
+ return claimed;
+}
+
+/**
+ * bpf_inode_init_xattr - attach a xattr to an inode that is being created
+ * @xattrs: xattr array the inode_init_security hook was handed
+ * @xattr_count__ctx_out: xattr count the inode_init_security hook was handed
+ * @name__str: name of the xattr
+ * @value_p: xattr value
+ *
+ * Claim one of the slots the BPF LSM reserved in the xattr array, so that
+ * the xattr is written out as part of the transaction creating the inode.
+ *
+ * For security reasons, only *name__str* with prefix "security.bpf." is
+ * allowed. The slot stores the name without that "security." prefix, which
+ * the filesystem's initxattrs() callback puts back.
+ *
+ * At most BPF_LSM_INODE_INIT_XATTRS xattrs can be attached to one inode,
+ * matching the number of slots the BPF LSM reserves.
+ *
+ * Return: 0 on success, a negative value on error.
+ */
+__bpf_kfunc int bpf_inode_init_xattr(struct xattr *xattrs,
+ int *xattr_count__ctx_out,
+ const char *name__str,
+ const struct bpf_dynptr *value_p)
+{
+ const struct bpf_dynptr_kern *value_ptr = (struct bpf_dynptr_kern *)value_p;
+ int *xattr_count = xattr_count__ctx_out;
+ const char *suffix;
+ struct xattr *slot;
+ const void *value;
+ size_t name_len;
+ u32 value_len;
+ char *buf;
+
+ if (!match_security_bpf_prefix(name__str))
+ return -EPERM;
+ if (bpf_inode_init_xattrs_claimed(xattrs, *xattr_count) >=
+ BPF_LSM_INODE_INIT_XATTRS)
+ return -ENOSPC;
+
+ suffix = name__str + XATTR_SECURITY_PREFIX_LEN;
+ name_len = strlen(suffix);
+ if (name_len <= sizeof(XATTR_BPF_LSM_SUFFIX) - 1 ||
+ name_len > XATTR_NAME_MAX - XATTR_SECURITY_PREFIX_LEN)
+ return -EINVAL;
+
+ value_len = __bpf_dynptr_size(value_ptr);
+ value = __bpf_dynptr_data(value_ptr, value_len);
+ if (!value)
+ return -EINVAL;
+ if (value_len > XATTR_SIZE_MAX)
+ return -E2BIG;
+
+ buf = kmalloc(value_len + name_len + 1, GFP_NOWAIT | __GFP_NOWARN);
+ if (!buf)
+ return -ENOMEM;
+ memcpy(buf, value, value_len);
+ memcpy(buf + value_len, suffix, name_len + 1);
+
+ slot = lsm_get_xattr_slot(xattrs, xattr_count);
+ if (!slot) {
+ kfree(buf);
+ return -ENOSPC;
+ }
+ slot->value = buf;
+ slot->value_len = value_len;
+ slot->name = buf + value_len;
+ return 0;
+}
+
#ifdef CONFIG_CGROUPS
/**
* bpf_cgroup_read_xattr - read xattr of a cgroup's node in cgroupfs
@@ -425,6 +509,7 @@ BTF_ID_FLAGS(func, bpf_get_file_xattr, KF_SLEEPABLE)
BTF_ID_FLAGS(func, bpf_set_dentry_xattr, KF_SLEEPABLE)
BTF_ID_FLAGS(func, bpf_remove_dentry_xattr, KF_SLEEPABLE)
BTF_ID_FLAGS(func, bpf_real_data_inode, KF_SLEEPABLE | KF_RET_NULL)
+BTF_ID_FLAGS(func, bpf_inode_init_xattr)
#ifdef CONFIG_NET
BTF_ID_FLAGS(func, bpf_sock_read_xattr, KF_RCU)
#endif
@@ -436,10 +521,24 @@ BTF_ID(func, bpf_set_dentry_xattr)
BTF_ID(func, bpf_remove_dentry_xattr)
BTF_SET_END(bpf_fs_kfunc_lsm_only_ids)
+BTF_ID_LIST_SINGLE(bpf_inode_init_xattr_ids, func, bpf_inode_init_xattr)
+
+BTF_SET_START(bpf_inode_init_xattr_hooks)
+BTF_ID(func, bpf_lsm_inode_init_security)
+BTF_SET_END(bpf_inode_init_xattr_hooks)
+
static int bpf_fs_kfuncs_filter(const struct bpf_prog *prog, u32 kfunc_id)
{
if (!btf_id_set8_contains(&bpf_fs_kfunc_set_ids, kfunc_id))
return 0;
+ if (kfunc_id == bpf_inode_init_xattr_ids[0]) {
+ if (prog->type != BPF_PROG_TYPE_LSM ||
+ prog->expected_attach_type != BPF_LSM_MAC ||
+ !btf_id_set_contains(&bpf_inode_init_xattr_hooks,
+ prog->aux->attach_btf_id))
+ return -EACCES;
+ return 0;
+ }
if (prog->type == BPF_PROG_TYPE_LSM)
return 0;
if (prog->type != BPF_PROG_TYPE_STRUCT_OPS)
diff --git a/include/linux/bpf_lsm.h b/include/linux/bpf_lsm.h
index dda272d78f01..eb4a38eef87e 100644
--- a/include/linux/bpf_lsm.h
+++ b/include/linux/bpf_lsm.h
@@ -21,6 +21,13 @@ extern bool bpf_lsm_initialized __ro_after_init;
#include <linux/lsm_hook_defs.h>
#undef LSM_HOOK
+/*
+ * Number of xattr slots the BPF LSM reserves in the array handed to
+ * security_inode_init_security(), i.e. the maximum number of labels
+ * a policy may attach to an inode while it is being created.
+ */
+#define BPF_LSM_INODE_INIT_XATTRS 2
+
struct bpf_storage_blob {
struct bpf_local_storage __rcu *storage;
};
@@ -28,7 +35,7 @@ struct bpf_storage_blob {
extern struct lsm_blob_sizes bpf_lsm_blob_sizes;
int bpf_lsm_verify_prog(struct bpf_verifier_log *vlog,
- const struct bpf_prog *prog);
+ struct bpf_prog *prog);
bool bpf_lsm_is_sleepable_hook(u32 btf_id);
bool bpf_lsm_is_trusted(const struct bpf_prog *prog);
@@ -71,7 +78,7 @@ static inline bool bpf_lsm_is_trusted(const struct bpf_prog *prog)
}
static inline int bpf_lsm_verify_prog(struct bpf_verifier_log *vlog,
- const struct bpf_prog *prog)
+ struct bpf_prog *prog)
{
return -EOPNOTSUPP;
}
diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
index 2660a89fc8d7..f58dce888ff4 100644
--- a/kernel/bpf/bpf_lsm.c
+++ b/kernel/bpf/bpf_lsm.c
@@ -116,8 +116,11 @@ void bpf_lsm_find_cgroup_shim(const struct bpf_prog *prog,
}
#endif
+BTF_ID_LIST_SINGLE(bpf_lsm_inode_init_security_btf_id, func,
+ bpf_lsm_inode_init_security)
+
int bpf_lsm_verify_prog(struct bpf_verifier_log *vlog,
- const struct bpf_prog *prog)
+ struct bpf_prog *prog)
{
u32 btf_id = prog->aux->attach_btf_id;
const char *func_name = prog->aux->attach_func_name;
@@ -140,6 +143,23 @@ int bpf_lsm_verify_prog(struct bpf_verifier_log *vlog,
return -EINVAL;
}
+ if (btf_id == bpf_lsm_inode_init_security_btf_id[0]) {
+ /*
+ * inode, dir, qstr, xattrs, xattr_count
+ *
+ * The trusted pointer this hands the program is only as
+ * trustworthy as the context it is loaded from, which
+ * lsm_verifier_ops keeps read-only.
+ */
+ static const struct bpf_ctx_arg_aux xattr_count_arg_info = {
+ .offset = 4 * sizeof(u64),
+ .reg_type = PTR_TO_MEM | MEM_RDONLY | PTR_TRUSTED,
+ .mem_size = sizeof(int),
+ };
+
+ return bpf_prog_ctx_arg_info_init(prog, &xattr_count_arg_info, 1);
+ }
+
return 0;
}
diff --git a/kernel/bpf/bpf_lsm_proto.c b/kernel/bpf/bpf_lsm_proto.c
index 44a54fd8045e..4a776df76cbb 100644
--- a/kernel/bpf/bpf_lsm_proto.c
+++ b/kernel/bpf/bpf_lsm_proto.c
@@ -4,6 +4,7 @@
*/
#include <linux/fs.h>
+#include <linux/xattr.h>
#include <linux/bpf_lsm.h>
/*
@@ -17,3 +18,17 @@ int bpf_lsm_mmap_file(struct file *file__nullable, unsigned long reqprot,
{
return 0;
}
+
+/*
+ * Strong definition of the inode_init_security() BPF LSM hook. Both the
+ * qstr and the xattr array are NULL for some callers, so the __nullable
+ * suffix marks it as PTR_MAYBE_NULL. BPF LSM programs have to check before
+ * dereferencing them or handing them to bpf_inode_init_xattr().
+ */
+int bpf_lsm_inode_init_security(struct inode *inode, struct inode *dir,
+ const struct qstr *qstr__nullable,
+ struct xattr *xattrs__nullable,
+ int *xattr_count)
+{
+ return -EOPNOTSUPP;
+}
diff --git a/security/bpf/hooks.c b/security/bpf/hooks.c
index 7b98f5d1e2be..8f8c3de3035f 100644
--- a/security/bpf/hooks.c
+++ b/security/bpf/hooks.c
@@ -33,6 +33,7 @@ static int __init bpf_lsm_init(void)
struct lsm_blob_sizes bpf_lsm_blob_sizes __ro_after_init = {
.lbs_inode = sizeof(struct bpf_storage_blob),
+ .lbs_xattr_count = BPF_LSM_INODE_INIT_XATTRS,
};
DEFINE_LSM(bpf) = {
--
2.43.0
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH bpf-next 5/8] bpf, lsm: Mark the BPF LSM hook overrides noinline
2026-09-15 15:07 [PATCH bpf-next 0/8] BPF LSM xattrs at inode creation support Daniel Borkmann
` (3 preceding siblings ...)
2026-09-15 15:07 ` [PATCH bpf-next 4/8] bpf, lsm: Let BPF LSM provide xattrs at inode creation Daniel Borkmann
@ 2026-09-15 15:07 ` 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
` (3 subsequent siblings)
8 siblings, 0 replies; 31+ messages in thread
From: Daniel Borkmann @ 2026-09-15 15:07 UTC (permalink / raw)
To: alexei.starovoitov
Cc: brauner, dwindsor, john.fastabend, memxor, kpsingh, matt, bpf
The stubs that bpf_lsm_proto.c overrides are generated as:
#define LSM_HOOK(RET, DEFAULT, NAME, ...) \
__weak noinline RET bpf_lsm_##NAME(__VA_ARGS__) \
{ \
return DEFAULT; \
}
Mark the overrides noinline as well so they hold up the same contract
as the stubs they replace.
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
---
kernel/bpf/bpf_lsm_proto.c | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/kernel/bpf/bpf_lsm_proto.c b/kernel/bpf/bpf_lsm_proto.c
index 4a776df76cbb..b81179ae2e08 100644
--- a/kernel/bpf/bpf_lsm_proto.c
+++ b/kernel/bpf/bpf_lsm_proto.c
@@ -13,8 +13,8 @@
* explicitly enforces that BPF LSM programs check for NULL before attempting to
* dereference it.
*/
-int bpf_lsm_mmap_file(struct file *file__nullable, unsigned long reqprot,
- unsigned long prot, unsigned long flags)
+noinline int bpf_lsm_mmap_file(struct file *file__nullable, unsigned long reqprot,
+ unsigned long prot, unsigned long flags)
{
return 0;
}
@@ -25,10 +25,10 @@ int bpf_lsm_mmap_file(struct file *file__nullable, unsigned long reqprot,
* suffix marks it as PTR_MAYBE_NULL. BPF LSM programs have to check before
* dereferencing them or handing them to bpf_inode_init_xattr().
*/
-int bpf_lsm_inode_init_security(struct inode *inode, struct inode *dir,
- const struct qstr *qstr__nullable,
- struct xattr *xattrs__nullable,
- int *xattr_count)
+noinline int bpf_lsm_inode_init_security(struct inode *inode, struct inode *dir,
+ const struct qstr *qstr__nullable,
+ struct xattr *xattrs__nullable,
+ int *xattr_count)
{
return -EOPNOTSUPP;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH bpf-next 6/8] selftests/bpf: Test that the BPF LSM context is read-only
2026-09-15 15:07 [PATCH bpf-next 0/8] BPF LSM xattrs at inode creation support Daniel Borkmann
` (4 preceding siblings ...)
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 ` Daniel Borkmann
2026-09-15 15:07 ` [PATCH bpf-next 7/8] selftests/bpf: Add verifier tests for the __ctx_out plumbing Daniel Borkmann
` (2 subsequent siblings)
8 siblings, 0 replies; 31+ messages in thread
From: Daniel Borkmann @ 2026-09-15 15:07 UTC (permalink / raw)
To: alexei.starovoitov
Cc: brauner, dwindsor, john.fastabend, memxor, kpsingh, matt, bpf
Add a new BPF selftest asserting that a store into the context of
an LSM program must be rejected.
# LDLIBS=-static PKG_CONFIG='pkg-config --static' ./vmtest.sh -- ./test_progs -a verifier_lsm
[...]
#648/1 verifier_lsm/lsm bpf prog with -4095~0 retval. test 1:OK
#648/2 verifier_lsm/lsm bpf prog with -4095~0 retval. test 2:OK
#648/3 verifier_lsm/lsm bpf prog with -4095~0 retval. test 4:OK
#648/4 verifier_lsm/lsm bpf prog with -4095~0 retval. test 5:OK
#648/5 verifier_lsm/lsm bpf prog with -4095~0 retval. test 6:OK
#648/6 verifier_lsm/lsm retval load must reset stale register bounds:OK
#648/7 verifier_lsm/lsm ctx is read-only:OK
#648/8 verifier_lsm/lsm bpf prog with bool retval. test 1:OK
#648/9 verifier_lsm/lsm bpf prog with bool retval. test 2:OK
#648/10 verifier_lsm/lsm bpf prog with bool retval. test 3:OK
#648/11 verifier_lsm/lsm bpf prog with bool retval. test 4:OK
#648/12 verifier_lsm/lsm bpf prog with void retval. test 1:OK
#648/13 verifier_lsm/lsm bpf prog with void retval. test 2:OK
#648/14 verifier_lsm/lsm disabled hook: getprocattr:OK
#648/15 verifier_lsm/lsm disabled hook: setprocattr:OK
#648/16 verifier_lsm/lsm disabled hook: ismaclabel:OK
#648/17 verifier_lsm/not null checking nullable pointer in bpf_lsm_mmap_file:OK
#648/18 verifier_lsm/null checking nullable pointer in bpf_lsm_mmap_file:OK
#648/19 verifier_lsm/sleepable lsm_cgroup program is rejected:OK
#648 verifier_lsm:OK
Summary: 1/19 PASSED, 0 SKIPPED, 0/0 FAILED
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
---
tools/testing/selftests/bpf/progs/verifier_lsm.c | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/tools/testing/selftests/bpf/progs/verifier_lsm.c b/tools/testing/selftests/bpf/progs/verifier_lsm.c
index c724bf389f5c..8fbf7862265f 100644
--- a/tools/testing/selftests/bpf/progs/verifier_lsm.c
+++ b/tools/testing/selftests/bpf/progs/verifier_lsm.c
@@ -212,4 +212,17 @@ __naked int retval_load_resets_bounds(void *ctx)
::: __clobber_all);
}
+SEC("lsm/file_mprotect")
+__description("lsm ctx is read-only")
+__failure __msg("invalid bpf_context access")
+__naked int reject_ctx_write(void *ctx)
+{
+ asm volatile (
+ "r6 = 0;"
+ "*(u64 *)(r1 + 0) = r6;"
+ "r0 = 0;"
+ "exit;"
+ ::: __clobber_all);
+}
+
char _license[] SEC("license") = "GPL";
--
2.43.0
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH bpf-next 7/8] selftests/bpf: Add verifier tests for the __ctx_out plumbing
2026-09-15 15:07 [PATCH bpf-next 0/8] BPF LSM xattrs at inode creation support Daniel Borkmann
` (5 preceding siblings ...)
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
2026-09-15 16:26 ` 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
8 siblings, 1 reply; 31+ messages in thread
From: Daniel Borkmann @ 2026-09-15 15:07 UTC (permalink / raw)
To: alexei.starovoitov
Cc: brauner, dwindsor, john.fastabend, memxor, kpsingh, matt, bpf
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
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH bpf-next 8/8] selftests/bpf: Add tests for BPF LSM inode init labelling
2026-09-15 15:07 [PATCH bpf-next 0/8] BPF LSM xattrs at inode creation support Daniel Borkmann
` (6 preceding siblings ...)
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 15:07 ` Daniel Borkmann
2026-09-19 19:10 ` [PATCH bpf-next 0/8] BPF LSM xattrs at inode creation support patchwork-bot+netdevbpf
8 siblings, 0 replies; 31+ messages in thread
From: Daniel Borkmann @ 2026-09-15 15:07 UTC (permalink / raw)
To: alexei.starovoitov
Cc: brauner, dwindsor, john.fastabend, memxor, kpsingh, matt, bpf
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
^ permalink raw reply related [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 1/8] ocfs2: Copy the xattr name in ocfs2_initxattrs
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
` (3 subsequent siblings)
4 siblings, 0 replies; 31+ messages in thread
From: sashiko-bot @ 2026-09-15 15:16 UTC (permalink / raw)
To: Daniel Borkmann; +Cc: Joseph Qi, bpf, Joel Becker, Mark Fasheh
> ocfs2_mknod() and ocfs2_symlink() ask for the security xattr up front
> through a struct ocfs2_security_xattr_info, so that they can size the
> transaction before setting it. ocfs2_initxattrs() duplicates the value
> since the array security_inode_init_security() hands is freed on return,
> but keeps the name pointer as-is, given so far every LSM stored a string
> constant there. bpf_inode_init_xattr() places the name in the same
> allocation as the value, which security_inode_init_security() frees on
> its way out. Copy the name alongside the value and free both together.
> This is the only special case wrt xattrs in the bpf_inode_init_xattr()
> context.
>
> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260915150739.284189-1-daniel@iogearbox.net?part=1
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 1/8] ocfs2: Copy the xattr name in ocfs2_initxattrs
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
` (2 subsequent siblings)
4 siblings, 0 replies; 31+ messages in thread
From: bot+bpf-ci @ 2026-09-15 16:26 UTC (permalink / raw)
To: daniel, alexei.starovoitov
Cc: brauner, dwindsor, john.fastabend, memxor, kpsingh, matt, bpf,
zhanxusheng, joseph.qi, ocfs2-devel, ast, andrii, daniel,
martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 1229 bytes --]
> ocfs2: Copy the xattr name in ocfs2_initxattrs
>
> ocfs2_mknod() and ocfs2_symlink() ask for the security xattr up front
> through a struct ocfs2_security_xattr_info, so that they can size the
> transaction before setting it. ocfs2_initxattrs() duplicates the value
> since the array security_inode_init_security() hands is freed on return,
> but keeps the name pointer as-is, given so far every LSM stored a string
> constant there. bpf_inode_init_xattr() places the name in the same
> allocation as the value, which security_inode_init_security() frees on
> its way out. Copy the name alongside the value and free both together.
> This is the only special case wrt xattrs in the bpf_inode_init_xattr()
> context.
>
> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
> Cc: Zhan Xusheng <zhanxusheng@xiaomi.com>
> Cc: Joseph Qi <joseph.qi@linux.alibaba.com>
> Cc: ocfs2-devel@lists.linux.dev
Should this include:
Fixes: 9722955b54307 ("bpf: Add simple xattr support to bpffs")
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/34989961168
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 7/8] selftests/bpf: Add verifier tests for the __ctx_out plumbing
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
0 siblings, 0 replies; 31+ messages in thread
From: bot+bpf-ci @ 2026-09-15 16:26 UTC (permalink / raw)
To: daniel, alexei.starovoitov
Cc: brauner, dwindsor, john.fastabend, memxor, kpsingh, matt, bpf,
ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, mason,
ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 3588 bytes --]
> diff --git a/tools/testing/selftests/bpf/bpf_kfuncs.h b/tools/testing/selftests/bpf/bpf_kfuncs.h
> index ae71e9b690518..1b408fe9e0fbc 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;
This isn't a bug, but are the blank-line removals and the reflow of the
bpf_get_dentry_xattr() comment needed for the new declaration? The rest
of the file keeps blank lines between declaration groups, and the six
other comment blocks use the old form. Could these changes be left out,
or moved to a separate cleanup that converts all the blocks in this
header at once?
>
> +/*
> + * 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;
This isn't a bug, but would it read better to keep the kernel's
xattr_count__ctx_out name here? The rest of the declarations in this
header keep their __str/__sz/__uninit suffixes, but this one drops the
__ctx_out marker that tells a BPF author the argument is special.
[ ... ]
> 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 0000000000000..b706bf17b5560
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/verifier_lsm_init_xattr.c
[ ... ]
> +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;
> +}
This isn't a bug, but does allow_spilled_count actually exercise a
spill/fill of the __ctx_out pointer? Since r6-r9 are callee-saved,
clang can keep saved in a register across the two calls, in which case
this duplicates the plain register-propagation case. Would forcing the
spill with explicit inline asm (as verifier_value_adj_spill.c does), or
renaming the test to reflect a copied-register case, describe the
coverage more accurately?
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/34989961168
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 1/8] ocfs2: Copy the xattr name in ocfs2_initxattrs
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 ` Joseph Qi
2026-09-16 7:29 ` Heming Zhao
4 siblings, 1 reply; 31+ messages in thread
From: Heming Zhao @ 2026-09-16 2:47 UTC (permalink / raw)
To: Daniel Borkmann
Cc: alexei.starovoitov, brauner, dwindsor, john.fastabend, memxor,
kpsingh, matt, bpf, Zhan Xusheng, Joseph Qi, ocfs2-devel
On Tue, Sep 15, 2026 at 05:07:32PM +0200, Daniel Borkmann wrote:
> ocfs2_mknod() and ocfs2_symlink() ask for the security xattr up front
> through a struct ocfs2_security_xattr_info, so that they can size the
> transaction before setting it. ocfs2_initxattrs() duplicates the value
> since the array security_inode_init_security() hands is freed on return,
> but keeps the name pointer as-is, given so far every LSM stored a string
> constant there. bpf_inode_init_xattr() places the name in the same
Typo? I am not familiar with BPF, but I only found inode_init_security in
include/linux/lsm_hook_defs.h.
> allocation as the value, which security_inode_init_security() frees on
> its way out. Copy the name alongside the value and free both together.
> This is the only special case wrt xattrs in the bpf_inode_init_xattr()
> context.
>
> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
> Cc: Zhan Xusheng <zhanxusheng@xiaomi.com>
> Cc: Joseph Qi <joseph.qi@linux.alibaba.com>
> Cc: ocfs2-devel@lists.linux.dev
> ---
> fs/ocfs2/namei.c | 2 ++
> fs/ocfs2/xattr.c | 5 +++--
> 2 files changed, 5 insertions(+), 2 deletions(-)
>
> diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c
> index e9c7774ccf91..e24f0e337a56 100644
> --- a/fs/ocfs2/namei.c
> +++ b/fs/ocfs2/namei.c
> @@ -480,6 +480,7 @@ static int ocfs2_mknod(struct mnt_idmap *idmap,
>
> brelse(new_fe_bh);
> brelse(parent_fe_bh);
> + kfree(si.name);
> kfree(si.value);
>
> ocfs2_acl_init_release(&acl_state);
> @@ -2068,6 +2069,7 @@ static int ocfs2_symlink(struct mnt_idmap *idmap,
>
> brelse(new_fe_bh);
> brelse(parent_fe_bh);
> + kfree(si.name);
> kfree(si.value);
> ocfs2_free_dir_lookup_result(&lookup);
> if (inode_ac)
> diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c
> index 35bcbb0ff607..d83840b6bed9 100644
> --- a/fs/ocfs2/xattr.c
> +++ b/fs/ocfs2/xattr.c
> @@ -7524,8 +7524,9 @@ static int ocfs2_initxattrs(struct inode *inode, const struct xattr *xattr_array
> GFP_KERNEL);
> if (!si->value)
> return -ENOMEM;
> -
> - si->name = xattr_array->name;
> + si->name = kstrdup(xattr_array->name, GFP_KERNEL);
> + if (!si->name)
> + return -ENOMEM;
The ->name is a constant string (i.e., XATTR_NAME_SELINUX). We can refer to
ext4_initxattrs() => ext4_xattr_set_handle(), which also uses '=' to assign ->name.
Thanks,
Heming
> si->value_len = xattr_array->value_len;
> return 0;
> }
> --
> 2.43.0
>
>
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 1/8] ocfs2: Copy the xattr name in ocfs2_initxattrs
2026-09-16 2:47 ` Heming Zhao
@ 2026-09-16 7:07 ` Daniel Borkmann
2026-09-16 7:28 ` Heming Zhao
0 siblings, 1 reply; 31+ messages in thread
From: Daniel Borkmann @ 2026-09-16 7:07 UTC (permalink / raw)
To: Heming Zhao
Cc: alexei.starovoitov, brauner, dwindsor, john.fastabend, memxor,
kpsingh, matt, bpf, Zhan Xusheng, Joseph Qi, ocfs2-devel
On 9/16/26 4:47 AM, Heming Zhao wrote:
> On Tue, Sep 15, 2026 at 05:07:32PM +0200, Daniel Borkmann wrote:
>> ocfs2_mknod() and ocfs2_symlink() ask for the security xattr up front
>> through a struct ocfs2_security_xattr_info, so that they can size the
>> transaction before setting it. ocfs2_initxattrs() duplicates the value
>> since the array security_inode_init_security() hands is freed on return,
>> but keeps the name pointer as-is, given so far every LSM stored a string
>> constant there. bpf_inode_init_xattr() places the name in the same
>
> Typo? I am not familiar with BPF, but I only found inode_init_security in
> include/linux/lsm_hook_defs.h.
Its part of the series here; should have added "upcoming" in front:
https://lore.kernel.org/bpf/20260915150739.284189-1-daniel@iogearbox.net/
>> allocation as the value, which security_inode_init_security() frees on
>> its way out. Copy the name alongside the value and free both together.
>> This is the only special case wrt xattrs in the bpf_inode_init_xattr()
>> context.
>>
>> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
>> Cc: Zhan Xusheng <zhanxusheng@xiaomi.com>
>> Cc: Joseph Qi <joseph.qi@linux.alibaba.com>
>> Cc: ocfs2-devel@lists.linux.dev
>> ---
>> fs/ocfs2/namei.c | 2 ++
>> fs/ocfs2/xattr.c | 5 +++--
>> 2 files changed, 5 insertions(+), 2 deletions(-)
>>
>> diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c
>> index e9c7774ccf91..e24f0e337a56 100644
>> --- a/fs/ocfs2/namei.c
>> +++ b/fs/ocfs2/namei.c
>> @@ -480,6 +480,7 @@ static int ocfs2_mknod(struct mnt_idmap *idmap,
>>
>> brelse(new_fe_bh);
>> brelse(parent_fe_bh);
>> + kfree(si.name);
>> kfree(si.value);
>>
>> ocfs2_acl_init_release(&acl_state);
>> @@ -2068,6 +2069,7 @@ static int ocfs2_symlink(struct mnt_idmap *idmap,
>>
>> brelse(new_fe_bh);
>> brelse(parent_fe_bh);
>> + kfree(si.name);
>> kfree(si.value);
>> ocfs2_free_dir_lookup_result(&lookup);
>> if (inode_ac)
>> diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c
>> index 35bcbb0ff607..d83840b6bed9 100644
>> --- a/fs/ocfs2/xattr.c
>> +++ b/fs/ocfs2/xattr.c
>> @@ -7524,8 +7524,9 @@ static int ocfs2_initxattrs(struct inode *inode, const struct xattr *xattr_array
>> GFP_KERNEL);
>> if (!si->value)
>> return -ENOMEM;
>> -
>> - si->name = xattr_array->name;
>> + si->name = kstrdup(xattr_array->name, GFP_KERNEL);
>> + if (!si->name)
>> + return -ENOMEM;
>
> The ->name is a constant string (i.e., XATTR_NAME_SELINUX). We can refer to
> ext4_initxattrs() => ext4_xattr_set_handle(), which also uses '=' to assign ->name.
ext4 is not affected since nothing is used outside security_inode_init_security
callback. ocfs2 is the only case in the tree affected, see this sashiko report
which this small patch is addressing:
https://lore.kernel.org/bpf/CAEXv5_gTJcP5BkSysZujLxiUcjbbBNi_NNbAnOJsQQVLyO9HoQ@mail.gmail.com/
Thanks,
Daniel
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 1/8] ocfs2: Copy the xattr name in ocfs2_initxattrs
2026-09-16 7:07 ` Daniel Borkmann
@ 2026-09-16 7:28 ` Heming Zhao
0 siblings, 0 replies; 31+ messages in thread
From: Heming Zhao @ 2026-09-16 7:28 UTC (permalink / raw)
To: Daniel Borkmann
Cc: alexei.starovoitov, brauner, dwindsor, john.fastabend, memxor,
kpsingh, matt, bpf, Zhan Xusheng, Joseph Qi, ocfs2-devel
On Wed, Sep 16, 2026 at 09:07:13AM +0200, Daniel Borkmann wrote:
> On 9/16/26 4:47 AM, Heming Zhao wrote:
> > On Tue, Sep 15, 2026 at 05:07:32PM +0200, Daniel Borkmann wrote:
> > > ocfs2_mknod() and ocfs2_symlink() ask for the security xattr up front
> > > through a struct ocfs2_security_xattr_info, so that they can size the
> > > transaction before setting it. ocfs2_initxattrs() duplicates the value
> > > since the array security_inode_init_security() hands is freed on return,
> > > but keeps the name pointer as-is, given so far every LSM stored a string
> > > constant there. bpf_inode_init_xattr() places the name in the same
> >
> > Typo? I am not familiar with BPF, but I only found inode_init_security in
> > include/linux/lsm_hook_defs.h.
>
> Its part of the series here; should have added "upcoming" in front:
>
> https://lore.kernel.org/bpf/20260915150739.284189-1-daniel@iogearbox.net/
>
> > > allocation as the value, which security_inode_init_security() frees on
> > > its way out. Copy the name alongside the value and free both together.
> > > This is the only special case wrt xattrs in the bpf_inode_init_xattr()
> > > context.
> > >
> > > Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
> > > Cc: Zhan Xusheng <zhanxusheng@xiaomi.com>
> > > Cc: Joseph Qi <joseph.qi@linux.alibaba.com>
> > > Cc: ocfs2-devel@lists.linux.dev
> > > ---
> > > fs/ocfs2/namei.c | 2 ++
> > > fs/ocfs2/xattr.c | 5 +++--
> > > 2 files changed, 5 insertions(+), 2 deletions(-)
> > >
> > > diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c
> > > index e9c7774ccf91..e24f0e337a56 100644
> > > --- a/fs/ocfs2/namei.c
> > > +++ b/fs/ocfs2/namei.c
> > > @@ -480,6 +480,7 @@ static int ocfs2_mknod(struct mnt_idmap *idmap,
> > > brelse(new_fe_bh);
> > > brelse(parent_fe_bh);
> > > + kfree(si.name);
> > > kfree(si.value);
> > > ocfs2_acl_init_release(&acl_state);
> > > @@ -2068,6 +2069,7 @@ static int ocfs2_symlink(struct mnt_idmap *idmap,
> > > brelse(new_fe_bh);
> > > brelse(parent_fe_bh);
> > > + kfree(si.name);
> > > kfree(si.value);
> > > ocfs2_free_dir_lookup_result(&lookup);
> > > if (inode_ac)
> > > diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c
> > > index 35bcbb0ff607..d83840b6bed9 100644
> > > --- a/fs/ocfs2/xattr.c
> > > +++ b/fs/ocfs2/xattr.c
> > > @@ -7524,8 +7524,9 @@ static int ocfs2_initxattrs(struct inode *inode, const struct xattr *xattr_array
> > > GFP_KERNEL);
> > > if (!si->value)
> > > return -ENOMEM;
> > > -
> > > - si->name = xattr_array->name;
> > > + si->name = kstrdup(xattr_array->name, GFP_KERNEL);
> > > + if (!si->name)
> > > + return -ENOMEM;
> >
> > The ->name is a constant string (i.e., XATTR_NAME_SELINUX). We can refer to
> > ext4_initxattrs() => ext4_xattr_set_handle(), which also uses '=' to assign ->name.
> ext4 is not affected since nothing is used outside security_inode_init_security
> callback. ocfs2 is the only case in the tree affected, see this sashiko report
> which this small patch is addressing:
>
> https://lore.kernel.org/bpf/CAEXv5_gTJcP5BkSysZujLxiUcjbbBNi_NNbAnOJsQQVLyO9HoQ@mail.gmail.com/
>
> Thanks,
> Daniel
Got it, the patch looks good to me.
Thanks,
Heming
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 1/8] ocfs2: Copy the xattr name in ocfs2_initxattrs
2026-09-15 15:07 ` [PATCH bpf-next 1/8] ocfs2: Copy the xattr name in ocfs2_initxattrs Daniel Borkmann
` (2 preceding siblings ...)
2026-09-16 2:47 ` Heming Zhao
@ 2026-09-16 7:28 ` Joseph Qi
2026-09-16 7:37 ` Daniel Borkmann
2026-09-16 7:29 ` Heming Zhao
4 siblings, 1 reply; 31+ messages in thread
From: Joseph Qi @ 2026-09-16 7:28 UTC (permalink / raw)
To: Daniel Borkmann, alexei.starovoitov
Cc: brauner, dwindsor, john.fastabend, memxor, kpsingh, matt, bpf,
Zhan Xusheng, ocfs2-devel
Hi,
I have reworked security xattrs in local, which addresses a review
comments from sashiko.
The fix will allocate an array in ocfs2_initxattrs(), copy the names
and values, account for all of them in the credit calculations, and
write each one in ocfs2_init_security_set().
It seems if with it, your patch is no longer needed. I'll send out
later.
Thanks,
Joseph
On 9/15/26 11:07 PM, Daniel Borkmann wrote:
> ocfs2_mknod() and ocfs2_symlink() ask for the security xattr up front
> through a struct ocfs2_security_xattr_info, so that they can size the
> transaction before setting it. ocfs2_initxattrs() duplicates the value
> since the array security_inode_init_security() hands is freed on return,
> but keeps the name pointer as-is, given so far every LSM stored a string
> constant there. bpf_inode_init_xattr() places the name in the same
> allocation as the value, which security_inode_init_security() frees on
> its way out. Copy the name alongside the value and free both together.
> This is the only special case wrt xattrs in the bpf_inode_init_xattr()
> context.
>
> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
> Cc: Zhan Xusheng <zhanxusheng@xiaomi.com>
> Cc: Joseph Qi <joseph.qi@linux.alibaba.com>
> Cc: ocfs2-devel@lists.linux.dev
> ---
> fs/ocfs2/namei.c | 2 ++
> fs/ocfs2/xattr.c | 5 +++--
> 2 files changed, 5 insertions(+), 2 deletions(-)
>
> diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c
> index e9c7774ccf91..e24f0e337a56 100644
> --- a/fs/ocfs2/namei.c
> +++ b/fs/ocfs2/namei.c
> @@ -480,6 +480,7 @@ static int ocfs2_mknod(struct mnt_idmap *idmap,
>
> brelse(new_fe_bh);
> brelse(parent_fe_bh);
> + kfree(si.name);
> kfree(si.value);
>
> ocfs2_acl_init_release(&acl_state);
> @@ -2068,6 +2069,7 @@ static int ocfs2_symlink(struct mnt_idmap *idmap,
>
> brelse(new_fe_bh);
> brelse(parent_fe_bh);
> + kfree(si.name);
> kfree(si.value);
> ocfs2_free_dir_lookup_result(&lookup);
> if (inode_ac)
> diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c
> index 35bcbb0ff607..d83840b6bed9 100644
> --- a/fs/ocfs2/xattr.c
> +++ b/fs/ocfs2/xattr.c
> @@ -7524,8 +7524,9 @@ static int ocfs2_initxattrs(struct inode *inode, const struct xattr *xattr_array
> GFP_KERNEL);
> if (!si->value)
> return -ENOMEM;
> -
> - si->name = xattr_array->name;
> + si->name = kstrdup(xattr_array->name, GFP_KERNEL);
> + if (!si->name)
> + return -ENOMEM;
> si->value_len = xattr_array->value_len;
> return 0;
> }
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 1/8] ocfs2: Copy the xattr name in ocfs2_initxattrs
2026-09-15 15:07 ` [PATCH bpf-next 1/8] ocfs2: Copy the xattr name in ocfs2_initxattrs Daniel Borkmann
` (3 preceding siblings ...)
2026-09-16 7:28 ` Joseph Qi
@ 2026-09-16 7:29 ` Heming Zhao
4 siblings, 0 replies; 31+ messages in thread
From: Heming Zhao @ 2026-09-16 7:29 UTC (permalink / raw)
To: Daniel Borkmann, joseph.qi
Cc: alexei.starovoitov, brauner, dwindsor, john.fastabend, memxor,
kpsingh, matt, bpf, Zhan Xusheng, ocfs2-devel
On Tue, Sep 15, 2026 at 05:07:32PM +0200, Daniel Borkmann wrote:
> ocfs2_mknod() and ocfs2_symlink() ask for the security xattr up front
> through a struct ocfs2_security_xattr_info, so that they can size the
> transaction before setting it. ocfs2_initxattrs() duplicates the value
> since the array security_inode_init_security() hands is freed on return,
> but keeps the name pointer as-is, given so far every LSM stored a string
> constant there. bpf_inode_init_xattr() places the name in the same
> allocation as the value, which security_inode_init_security() frees on
> its way out. Copy the name alongside the value and free both together.
> This is the only special case wrt xattrs in the bpf_inode_init_xattr()
> context.
>
> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
LGTM.
Reviewed-by: Heming Zhao <heming.zhao@suse.com>
> Cc: Zhan Xusheng <zhanxusheng@xiaomi.com>
> Cc: Joseph Qi <joseph.qi@linux.alibaba.com>
> Cc: ocfs2-devel@lists.linux.dev
> ---
> fs/ocfs2/namei.c | 2 ++
> fs/ocfs2/xattr.c | 5 +++--
> 2 files changed, 5 insertions(+), 2 deletions(-)
>
> diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c
> index e9c7774ccf91..e24f0e337a56 100644
> --- a/fs/ocfs2/namei.c
> +++ b/fs/ocfs2/namei.c
> @@ -480,6 +480,7 @@ static int ocfs2_mknod(struct mnt_idmap *idmap,
>
> brelse(new_fe_bh);
> brelse(parent_fe_bh);
> + kfree(si.name);
> kfree(si.value);
>
> ocfs2_acl_init_release(&acl_state);
> @@ -2068,6 +2069,7 @@ static int ocfs2_symlink(struct mnt_idmap *idmap,
>
> brelse(new_fe_bh);
> brelse(parent_fe_bh);
> + kfree(si.name);
> kfree(si.value);
> ocfs2_free_dir_lookup_result(&lookup);
> if (inode_ac)
> diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c
> index 35bcbb0ff607..d83840b6bed9 100644
> --- a/fs/ocfs2/xattr.c
> +++ b/fs/ocfs2/xattr.c
> @@ -7524,8 +7524,9 @@ static int ocfs2_initxattrs(struct inode *inode, const struct xattr *xattr_array
> GFP_KERNEL);
> if (!si->value)
> return -ENOMEM;
> -
> - si->name = xattr_array->name;
> + si->name = kstrdup(xattr_array->name, GFP_KERNEL);
> + if (!si->name)
> + return -ENOMEM;
> si->value_len = xattr_array->value_len;
> return 0;
> }
> --
> 2.43.0
>
>
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 1/8] ocfs2: Copy the xattr name in ocfs2_initxattrs
2026-09-16 7:28 ` Joseph Qi
@ 2026-09-16 7:37 ` Daniel Borkmann
2026-09-16 7:49 ` Joseph Qi
0 siblings, 1 reply; 31+ messages in thread
From: Daniel Borkmann @ 2026-09-16 7:37 UTC (permalink / raw)
To: Joseph Qi, alexei.starovoitov
Cc: brauner, dwindsor, john.fastabend, memxor, kpsingh, matt, bpf,
Zhan Xusheng, ocfs2-devel
Hi Joseph,
On 9/16/26 9:28 AM, Joseph Qi wrote:
> Hi,
> I have reworked security xattrs in local, which addresses a review
> comments from sashiko.
>
> The fix will allocate an array in ocfs2_initxattrs(), copy the names
> and values, account for all of them in the credit calculations, and
> write each one in ocfs2_init_security_set().
>
> It seems if with it, your patch is no longer needed. I'll send out
> later.
Ah perfect! I guess this means you'd be routing this rework via your
tree, we'll just drop this fix on our side, and eventually both sides
would meet at Linus' tree? If so, that sounds fine to me and would avoid
a merge conflict.
Thanks,
Daniel
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 1/8] ocfs2: Copy the xattr name in ocfs2_initxattrs
2026-09-16 7:37 ` Daniel Borkmann
@ 2026-09-16 7:49 ` Joseph Qi
0 siblings, 0 replies; 31+ messages in thread
From: Joseph Qi @ 2026-09-16 7:49 UTC (permalink / raw)
To: Daniel Borkmann, alexei.starovoitov
Cc: brauner, dwindsor, john.fastabend, memxor, kpsingh, matt, bpf,
Zhan Xusheng, ocfs2-devel
On 9/16/26 3:37 PM, Daniel Borkmann wrote:
> Hi Joseph,
>
> On 9/16/26 9:28 AM, Joseph Qi wrote:
>> Hi,
>> I have reworked security xattrs in local, which addresses a review
>> comments from sashiko.
>>
>> The fix will allocate an array in ocfs2_initxattrs(), copy the names
>> and values, account for all of them in the credit calculations, and
>> write each one in ocfs2_init_security_set().
>>
>> It seems if with it, your patch is no longer needed. I'll send out
>> later.
>
> Ah perfect! I guess this means you'd be routing this rework via your
> tree, we'll just drop this fix on our side, and eventually both sides
> would meet at Linus' tree? If so, that sounds fine to me and would avoid
> a merge conflict.
>
Yes, I think so. It will go to mm-tree first and then linux-next.
I'll cc you as well when send out the patch.
Thanks,
Joseph
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 0/8] BPF LSM xattrs at inode creation support
2026-09-15 15:07 [PATCH bpf-next 0/8] BPF LSM xattrs at inode creation support Daniel Borkmann
` (7 preceding siblings ...)
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 ` patchwork-bot+netdevbpf
8 siblings, 0 replies; 31+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-09-19 19:10 UTC (permalink / raw)
To: Daniel Borkmann
Cc: alexei.starovoitov, brauner, dwindsor, john.fastabend, memxor,
kpsingh, matt, bpf
Hello:
This series was applied to bpf/bpf-next.git (master)
by Alexei Starovoitov <ast@kernel.org>:
On Tue, 15 Sep 2026 17:07:31 +0200 you wrote:
> Many in-kernel LSMs store security labels in extended file system attributes
> (xattrs). For these LSMs, atomic labeling during inode creation is critical:
> If the inode becomes accessible before its xattr is set, it is briefly
> unlabeled, which can disrupt LSMs making policy decisions based on file
> labels. Existing LSMs solve this by setting xattrs in the inode_init_security
> hook, which runs before the inode becomes accessible. BPF LSM programs
> currently lack this capability, and this series addresses this gap along
> with BPF selftests. Thanks!
>
> [...]
Here is the summary with links:
- [bpf-next,1/8] ocfs2: Copy the xattr name in ocfs2_initxattrs
(no matching commit)
- [bpf-next,2/8] bpf, lsm: Reject writes into the BPF LSM program context
https://git.kernel.org/bpf/bpf-next/c/c42da1d3ba9d
- [bpf-next,3/8] bpf: Support passing context output arguments to kfuncs
https://git.kernel.org/bpf/bpf-next/c/806fc431e0a3
- [bpf-next,4/8] bpf, lsm: Let BPF LSM provide xattrs at inode creation
https://git.kernel.org/bpf/bpf-next/c/ff0d1c0915d1
- [bpf-next,5/8] bpf, lsm: Mark the BPF LSM hook overrides noinline
https://git.kernel.org/bpf/bpf-next/c/3479e396eef4
- [bpf-next,6/8] selftests/bpf: Test that the BPF LSM context is read-only
https://git.kernel.org/bpf/bpf-next/c/e5d960cfe1d3
- [bpf-next,7/8] selftests/bpf: Add verifier tests for the __ctx_out plumbing
https://git.kernel.org/bpf/bpf-next/c/2757e9e53900
- [bpf-next,8/8] selftests/bpf: Add tests for BPF LSM inode init labelling
https://git.kernel.org/bpf/bpf-next/c/90dd01b6c1f3
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 4/8] bpf, lsm: Let BPF LSM provide xattrs at inode creation
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 19:14 ` David Windsor
0 siblings, 2 replies; 31+ messages in thread
From: Paul Moore @ 2026-09-23 16:57 UTC (permalink / raw)
To: Daniel Borkmann, dwindsor
Cc: alexei.starovoitov, brauner, john.fastabend, memxor, kpsingh,
matt, bpf, linux-security-module, linux-kernel
On Tue, Sep 15, 2026 at 11:07 AM Daniel Borkmann <daniel@iogearbox.net> wrote:
>
> From: David Windsor <dwindsor@gmail.com>
>
> Many in-kernel LSMs (SELinux, Smack, IMA) store security labels in extended
> attributes. For these LSMs, atomic labeling during inode creation is
> critical: if the inode becomes accessible before its xattr is set, it is
> briefly unlabeled, which can disrupt LSMs making policy decisions based
> on file labels. Existing LSMs solve this by setting xattrs in the
> inode_init_security hook, which runs before the inode becomes accessible.
> BPF LSM programs currently lack this capability because the hook uses an
> output parameter (xattr_count) that BPF programs cannot write to, and
> existing kfuncs like bpf_set_dentry_xattr() require a dentry that isn't
> available until after the inode is accessible.
>
> Add a bpf_inode_init_xattr() kfunc that takes the hook's own xattrs and
> xattr_count arguments, passed through from the program's context, and
> claims a slot via lsm_get_xattr_slot() on the program's behalf. The
> xattr_count output argument is exposed to inode_init_security programs
> as trusted read-only memory, so programs can pass it to the kfunc but
> cannot modify the count themselves.
>
> Reserve BPF_LSM_INODE_INIT_XATTRS slots in bpf_lsm_blob_sizes the way
> every other xattr-providing LSM does, for the life of the kernel. The
> framework keys the collection off the reserved slot count, so a kernel
> built with CONFIG_BPF_LSM=y now allocates the xattr array on every inode
> creation, whether or not a program sits on the hook.
>
> Give the hook a strong prototype in bpf_lsm_proto.c so that its qstr and
> xattrs arguments are marked __nullable. Both can be NULL for some callers.
> Without the annotation the verifier otherwise hands the program a trusted
> non-NULL pointer which it dereferences. Also, keep the hook out of the
> sleepable set. inode_init_security runs inside the transaction creating
> the inode, with a journal handle held on ext4 and btrfs and the parent's
> i_rwsem down, which is why everything on the path allocates GFP_NOFS.
>
> Signed-off-by: David Windsor <dwindsor@gmail.com>
> Co-developed-by: Daniel Borkmann <daniel@iogearbox.net>
> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
> ---
> fs/bpf_fs_kfuncs.c | 99 ++++++++++++++++++++++++++++++++++++++
> include/linux/bpf_lsm.h | 11 ++++-
> kernel/bpf/bpf_lsm.c | 22 ++++++++-
> kernel/bpf/bpf_lsm_proto.c | 15 ++++++
> security/bpf/hooks.c | 1 +
> 5 files changed, 145 insertions(+), 3 deletions(-)
@Daniel, you were CC'd on David's previous patches, so I'm guessing
you saw my objection[1], but just in case you hadn't please look at my
comments where I requested that David's proposed LSM kfunc be located
in security/bpf_lsm_kfuncs.c as opposed to fs/bpf_fs_kfuncs.c. It
would be really nice if we could sort this out now and avoid having
this drag out or escalate.
@David, simply for my own understanding, did you ask Daniel to do
this, or was Daniel operating on his own with this patchset?
[1] https://lore.kernel.org/linux-security-module/CAHC9VhTS7rSnBqg00ZxNkcZyh_=EeJmn_4z3CTCCxreEEDtTtg@mail.gmail.com/
--
paul-moore.com
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 4/8] bpf, lsm: Let BPF LSM provide xattrs at inode creation
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
1 sibling, 1 reply; 31+ messages in thread
From: Daniel Borkmann @ 2026-09-23 19:11 UTC (permalink / raw)
To: Paul Moore, dwindsor
Cc: alexei.starovoitov, brauner, john.fastabend, memxor, kpsingh,
matt, bpf, linux-security-module, linux-kernel
On 9/23/26 6:57 PM, Paul Moore wrote:
> On Tue, Sep 15, 2026 at 11:07 AM Daniel Borkmann <daniel@iogearbox.net> wrote:
>> From: David Windsor <dwindsor@gmail.com>
>>
>> Many in-kernel LSMs (SELinux, Smack, IMA) store security labels in extended
>> attributes. For these LSMs, atomic labeling during inode creation is
>> critical: if the inode becomes accessible before its xattr is set, it is
>> briefly unlabeled, which can disrupt LSMs making policy decisions based
>> on file labels. Existing LSMs solve this by setting xattrs in the
>> inode_init_security hook, which runs before the inode becomes accessible.
>> BPF LSM programs currently lack this capability because the hook uses an
>> output parameter (xattr_count) that BPF programs cannot write to, and
>> existing kfuncs like bpf_set_dentry_xattr() require a dentry that isn't
>> available until after the inode is accessible.
>>
>> Add a bpf_inode_init_xattr() kfunc that takes the hook's own xattrs and
>> xattr_count arguments, passed through from the program's context, and
>> claims a slot via lsm_get_xattr_slot() on the program's behalf. The
>> xattr_count output argument is exposed to inode_init_security programs
>> as trusted read-only memory, so programs can pass it to the kfunc but
>> cannot modify the count themselves.
>>
>> Reserve BPF_LSM_INODE_INIT_XATTRS slots in bpf_lsm_blob_sizes the way
>> every other xattr-providing LSM does, for the life of the kernel. The
>> framework keys the collection off the reserved slot count, so a kernel
>> built with CONFIG_BPF_LSM=y now allocates the xattr array on every inode
>> creation, whether or not a program sits on the hook.
>>
>> Give the hook a strong prototype in bpf_lsm_proto.c so that its qstr and
>> xattrs arguments are marked __nullable. Both can be NULL for some callers.
>> Without the annotation the verifier otherwise hands the program a trusted
>> non-NULL pointer which it dereferences. Also, keep the hook out of the
>> sleepable set. inode_init_security runs inside the transaction creating
>> the inode, with a journal handle held on ext4 and btrfs and the parent's
>> i_rwsem down, which is why everything on the path allocates GFP_NOFS.
>>
>> Signed-off-by: David Windsor <dwindsor@gmail.com>
>> Co-developed-by: Daniel Borkmann <daniel@iogearbox.net>
>> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
>> ---
>> fs/bpf_fs_kfuncs.c | 99 ++++++++++++++++++++++++++++++++++++++
>> include/linux/bpf_lsm.h | 11 ++++-
>> kernel/bpf/bpf_lsm.c | 22 ++++++++-
>> kernel/bpf/bpf_lsm_proto.c | 15 ++++++
>> security/bpf/hooks.c | 1 +
>> 5 files changed, 145 insertions(+), 3 deletions(-)
>
> @Daniel, you were CC'd on David's previous patches, so I'm guessing
> you saw my objection[1], but just in case you hadn't please look at my
> comments where I requested that David's proposed LSM kfunc be located
> in security/bpf_lsm_kfuncs.c as opposed to fs/bpf_fs_kfuncs.c. It
> would be really nice if we could sort this out now and avoid having
> this drag out or escalate.
Paul, I reached out to David recently asking whether I could offer some
help with the BPF bits, added some bug fixes and a lot more BPF selftests
as I think the inode xattr init is valuable work and something we need as
well. I just reread this whole thread below given its quite a while back
and didn't follow in too much detail back then.. the location as it is is
perfectly fine, I see no reason to change it, and I guess that makes three
of us then including the VFS folks [0]. In that file there are a number of
other kfuncs as well already related to xattr in context of dentry, files,
etc. I don't see a point at all on endless bike shedding on this, its
perfectly reasonably where this is located. In case you have some technical
comment or found a bug, let me know, happy to address.
[0] https://lore.kernel.org/bpf/20260625-schnabel-rennmaschine-parieren-bcb352c3cf59@brauner/
> @David, simply for my own understanding, did you ask Daniel to do
> this, or was Daniel operating on his own with this patchset?
>
> [1] https://lore.kernel.org/linux-security-module/CAHC9VhTS7rSnBqg00ZxNkcZyh_=EeJmn_4z3CTCCxreEEDtTtg@mail.gmail.com/
>
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 4/8] bpf, lsm: Let BPF LSM provide xattrs at inode creation
2026-09-23 16:57 ` Paul Moore
2026-09-23 19:11 ` Daniel Borkmann
@ 2026-09-23 19:14 ` David Windsor
2026-09-23 20:56 ` Paul Moore
` (2 more replies)
1 sibling, 3 replies; 31+ messages in thread
From: David Windsor @ 2026-09-23 19:14 UTC (permalink / raw)
To: Paul Moore
Cc: Daniel Borkmann, alexei.starovoitov, brauner, john.fastabend,
memxor, kpsingh, matt, bpf, linux-security-module, linux-kernel
On Wed, Sep 23, 2026 at 12:57 PM Paul Moore <paul@paul-moore.com> wrote:
> @David, simply for my own understanding, did you ask Daniel to do
> this, or was Daniel operating on his own with this patchset?
>
Daniel and I work together and both have things written on top of this
kfunc. He reached out to collaborate, I agreed. We're also going to
send bpf_set_file_xattr shortly.
This implementation was chosen due to its immediate mergeability (it
only touches security/bpf), but was actually suggested by Kumar in v1
or so of my original series.
That said, sorry for any confusion about this appearing as a new
series rather than as v7 of my previous one.
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 4/8] bpf, lsm: Let BPF LSM provide xattrs at inode creation
2026-09-23 19:11 ` Daniel Borkmann
@ 2026-09-23 20:51 ` Paul Moore
0 siblings, 0 replies; 31+ messages in thread
From: Paul Moore @ 2026-09-23 20:51 UTC (permalink / raw)
To: Daniel Borkmann
Cc: dwindsor, alexei.starovoitov, brauner, john.fastabend, memxor,
kpsingh, matt, bpf, linux-security-module, linux-kernel
On Wed, Sep 23, 2026 at 3:11 PM Daniel Borkmann <daniel@iogearbox.net> wrote:
> On 9/23/26 6:57 PM, Paul Moore wrote:
> > On Tue, Sep 15, 2026 at 11:07 AM Daniel Borkmann <daniel@iogearbox.net> wrote:
> >> From: David Windsor <dwindsor@gmail.com>
> >>
> >> Many in-kernel LSMs (SELinux, Smack, IMA) store security labels in extended
> >> attributes. For these LSMs, atomic labeling during inode creation is
> >> critical: if the inode becomes accessible before its xattr is set, it is
> >> briefly unlabeled, which can disrupt LSMs making policy decisions based
> >> on file labels. Existing LSMs solve this by setting xattrs in the
> >> inode_init_security hook, which runs before the inode becomes accessible.
> >> BPF LSM programs currently lack this capability because the hook uses an
> >> output parameter (xattr_count) that BPF programs cannot write to, and
> >> existing kfuncs like bpf_set_dentry_xattr() require a dentry that isn't
> >> available until after the inode is accessible.
> >>
> >> Add a bpf_inode_init_xattr() kfunc that takes the hook's own xattrs and
> >> xattr_count arguments, passed through from the program's context, and
> >> claims a slot via lsm_get_xattr_slot() on the program's behalf. The
> >> xattr_count output argument is exposed to inode_init_security programs
> >> as trusted read-only memory, so programs can pass it to the kfunc but
> >> cannot modify the count themselves.
> >>
> >> Reserve BPF_LSM_INODE_INIT_XATTRS slots in bpf_lsm_blob_sizes the way
> >> every other xattr-providing LSM does, for the life of the kernel. The
> >> framework keys the collection off the reserved slot count, so a kernel
> >> built with CONFIG_BPF_LSM=y now allocates the xattr array on every inode
> >> creation, whether or not a program sits on the hook.
> >>
> >> Give the hook a strong prototype in bpf_lsm_proto.c so that its qstr and
> >> xattrs arguments are marked __nullable. Both can be NULL for some callers.
> >> Without the annotation the verifier otherwise hands the program a trusted
> >> non-NULL pointer which it dereferences. Also, keep the hook out of the
> >> sleepable set. inode_init_security runs inside the transaction creating
> >> the inode, with a journal handle held on ext4 and btrfs and the parent's
> >> i_rwsem down, which is why everything on the path allocates GFP_NOFS.
> >>
> >> Signed-off-by: David Windsor <dwindsor@gmail.com>
> >> Co-developed-by: Daniel Borkmann <daniel@iogearbox.net>
> >> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
> >> ---
> >> fs/bpf_fs_kfuncs.c | 99 ++++++++++++++++++++++++++++++++++++++
> >> include/linux/bpf_lsm.h | 11 ++++-
> >> kernel/bpf/bpf_lsm.c | 22 ++++++++-
> >> kernel/bpf/bpf_lsm_proto.c | 15 ++++++
> >> security/bpf/hooks.c | 1 +
> >> 5 files changed, 145 insertions(+), 3 deletions(-)
> >
> > @Daniel, you were CC'd on David's previous patches, so I'm guessing
> > you saw my objection[1], but just in case you hadn't please look at my
> > comments where I requested that David's proposed LSM kfunc be located
> > in security/bpf_lsm_kfuncs.c as opposed to fs/bpf_fs_kfuncs.c. It
> > would be really nice if we could sort this out now and avoid having
> > this drag out or escalate.
>
> Paul, I reached out to David recently asking whether I could offer some
> help with the BPF bits, added some bug fixes and a lot more BPF selftests
> as I think the inode xattr init is valuable work and something we need as
> well. I just reread this whole thread below given its quite a while back
> and didn't follow in too much detail back then.. the location as it is is
> perfectly fine, I see no reason to change it, and I guess that makes three
> of us then including the VFS folks [0].
I didn't think there were any doubts that the BPF and VFS folks wanted
the kfunc in fs/bpf_fs_kfuncs.c, but I thought I made my objections
clear in the previous revisions. While you've changed things slightly
from David's last patchset, essentially folding the proposed
security_lsmxattr_add() helper into the bpf_inode_init_xattr(), it
does appear that the basic purpose and context around the kfunc
remains the same: the proposed kfunc is called from a LSM
inode_init_security callback, it populates a LSM framework managed
buffer, and then when the LSM callback returns the LSM framework code
in security_inode_init_security() does the xattr init based on the
buffers populated by all of the configured LSMs (the BPF LSM as well
as others). As there are no direct calls to the VFS layer in the
kfunc, but there are direct calls into LSM internal APIs (e.g.
lsm_get_xattr_slot(), as well as the LSM state and calling context
mentioned above, from my perspective this really does need to be
located in a LSM framework BPF kfunc file (e.g.
security/bpf_lsm_kfuncs.c). I've mentioned several times in David's
previous postings that I'm happy to work with you and the BPF devs to
set that up. If you are open to that let's sort it out now so we can
get everything in place before the merge window. If that isn't
something you are able to do, that would also be good to know.
--
paul-moore.com
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 4/8] bpf, lsm: Let BPF LSM provide xattrs at inode creation
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
2 siblings, 0 replies; 31+ messages in thread
From: Paul Moore @ 2026-09-23 20:56 UTC (permalink / raw)
To: David Windsor
Cc: Daniel Borkmann, alexei.starovoitov, brauner, john.fastabend,
memxor, kpsingh, matt, bpf, linux-security-module, linux-kernel
On Wed, Sep 23, 2026 at 3:14 PM David Windsor <dwindsor@gmail.com> wrote:
> On Wed, Sep 23, 2026 at 12:57 PM Paul Moore <paul@paul-moore.com> wrote:
> > @David, simply for my own understanding, did you ask Daniel to do
> > this, or was Daniel operating on his own with this patchset?
> >
>
> Daniel and I work together and both have things written on top of this
> kfunc. He reached out to collaborate, I agreed. We're also going to
> send bpf_set_file_xattr shortly.
>
> This implementation was chosen due to its immediate mergeability (it
> only touches security/bpf), but was actually suggested by Kumar in v1
> or so of my original series.
>
> That said, sorry for any confusion about this appearing as a new
> series rather than as v7 of my previous one.
Thanks for your perspective.
To be perfectly honest with you, seeing this patchset land in
linux-next without it being posted to the LSM list, or at least CC'ing
me, after all the discussions on the previous iterations seemed a bit
suspect, especially considering the rather rocky relations between the
BPF and LSM communities at the moment. Regardless, hopefully we can
get this sorted out.
--
paul-moore.com
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 4/8] bpf, lsm: Let BPF LSM provide xattrs at inode creation
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
2 siblings, 0 replies; 31+ messages in thread
From: Paul Moore @ 2026-09-23 21:07 UTC (permalink / raw)
To: David Windsor
Cc: Daniel Borkmann, alexei.starovoitov, brauner, john.fastabend,
memxor, kpsingh, matt, bpf, linux-security-module, linux-kernel
On Wed, Sep 23, 2026 at 3:14 PM David Windsor <dwindsor@gmail.com> wrote:
>
> This implementation was chosen due to its immediate mergeability (it
> only touches security/bpf) ...
Ooops, I hit send on my reply to you too soon.
It's worth mentioning that sometimes maintainer objections/NACKs can
cross subsystem boundaries. You may not have followed it, but a while
ago a LSM was proposed that implemented BPF program load access
controls and it was successfully NACK'd by the BPF devs, despite it
not touching any BPF code. An argument was made that the proposed LSM
manipulated internal BPF state (there were arguments on both sides of
that topic), similar to what the proposed BPF kfunc is doing with the
LSM framework.
Hopefully we won't have a repeat situation here, but it is worth
knowing that code location alone doesn't necessarily equate to
"immediate mergeability" based on recent history.
--
paul-moore.com
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 4/8] bpf, lsm: Let BPF LSM provide xattrs at inode creation
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
2 siblings, 1 reply; 31+ messages in thread
From: Justin Suess @ 2026-09-24 16:14 UTC (permalink / raw)
To: David Windsor
Cc: Paul Moore, Daniel Borkmann, alexei.starovoitov, brauner,
john.fastabend, memxor, kpsingh, matt, bpf, linux-security-module,
linux-kernel
On Wed, Sep 23, 2026 at 03:14:28PM -0400, David Windsor wrote:
> On Wed, Sep 23, 2026 at 12:57 PM Paul Moore <paul@paul-moore.com> wrote:
> > @David, simply for my own understanding, did you ask Daniel to do
> > this, or was Daniel operating on his own with this patchset?
> >
>
> Daniel and I work together and both have things written on top of this
> kfunc. He reached out to collaborate, I agreed. We're also going to
> send bpf_set_file_xattr shortly.
>
> This implementation was chosen due to its immediate mergeability (it
> only touches security/bpf), but was actually suggested by Kumar in v1
> or so of my original series.
>
> That said, sorry for any confusion about this appearing as a new
> series rather than as v7 of my previous one.
>
Howdy all,
Hope you all are doing well and having a good Thursday.
These patches are excellent and useful, and have been in the pipeline
for a while.
In the interest of moving forward:
Would you both be able to live with the following: provide a security hook
for lsm_get_xattr_slot or another proper interface with the necessary
abstraction, and keep the kfunc where it is in fs/?
This addresses the primary concern about calling into LSM internals by
providing a blessed interface. I'd be happy to detail what I had in mind.
I think the end users care extremely little about what directory the
source code ends up in. They care about: getting useful features.
We all work in the same kernel and it's easier to walk when both legs
are going in the same direction. I think that this hill would be an
unfitting, uninteresting place for these useful patches to die on :)
Let me know if this is something you both could stomach.
Sincerely,
Justin
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 4/8] bpf, lsm: Let BPF LSM provide xattrs at inode creation
2026-09-24 16:14 ` Justin Suess
@ 2026-09-24 16:23 ` Paul Moore
2026-09-24 18:37 ` Justin Suess
0 siblings, 1 reply; 31+ messages in thread
From: Paul Moore @ 2026-09-24 16:23 UTC (permalink / raw)
To: Justin Suess
Cc: David Windsor, Daniel Borkmann, alexei.starovoitov, brauner,
john.fastabend, memxor, kpsingh, matt, bpf, linux-security-module,
linux-kernel
On Thu, Sep 24, 2026 at 12:15 PM Justin Suess <utilityemal77@gmail.com> wrote:
> On Wed, Sep 23, 2026 at 03:14:28PM -0400, David Windsor wrote:
> > On Wed, Sep 23, 2026 at 12:57 PM Paul Moore <paul@paul-moore.com> wrote:
> > > @David, simply for my own understanding, did you ask Daniel to do
> > > this, or was Daniel operating on his own with this patchset?
> > >
> >
> > Daniel and I work together and both have things written on top of this
> > kfunc. He reached out to collaborate, I agreed. We're also going to
> > send bpf_set_file_xattr shortly.
> >
> > This implementation was chosen due to its immediate mergeability (it
> > only touches security/bpf), but was actually suggested by Kumar in v1
> > or so of my original series.
> >
> > That said, sorry for any confusion about this appearing as a new
> > series rather than as v7 of my previous one.
>
> Howdy all,
>
> Hope you all are doing well and having a good Thursday.
>
> These patches are excellent and useful, and have been in the pipeline
> for a while.
>
> In the interest of moving forward:
>
> Would you both be able to live with the following: provide a security hook
> for lsm_get_xattr_slot or another proper interface with the necessary
> abstraction, and keep the kfunc where it is in fs/?
That still doesn't change the fundamentals around the kfunc: it is
really only a valid to call it from within the LSM inode_init_security
callback, it populates a LSM framework managed buffer, and that buffer
is then used to by the LSM framework code in
security_inode_init_security() to do the xattr initialization using
the values from the BPF LSM as well as all of the other configured
LSMs. It's very hard to see this as anything other than an LSM kfunc.
I worked with David over several revisions of his patchset to review
the code and get it in a good place, I'm supportive of the basic
ideas, but this really needs to be located in
security/bpf_lsm_kfuncs.c as it is an LSM kfunc. As we've seen there
is precedence for subsystem specific kfuncs located in the associated
subsystem's directory, things should be no different here.
--
paul-moore.com
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 4/8] bpf, lsm: Let BPF LSM provide xattrs at inode creation
2026-09-24 16:23 ` Paul Moore
@ 2026-09-24 18:37 ` Justin Suess
2026-09-24 19:33 ` Paul Moore
0 siblings, 1 reply; 31+ messages in thread
From: Justin Suess @ 2026-09-24 18:37 UTC (permalink / raw)
To: Paul Moore
Cc: David Windsor, Daniel Borkmann, alexei.starovoitov, brauner,
john.fastabend, memxor, kpsingh, matt, bpf, linux-security-module,
linux-kernel
On Thu, Sep 24, 2026 at 12:23:13PM -0400, Paul Moore wrote:
> On Thu, Sep 24, 2026 at 12:15 PM Justin Suess <utilityemal77@gmail.com> wrote:
> > On Wed, Sep 23, 2026 at 03:14:28PM -0400, David Windsor wrote:
> > > On Wed, Sep 23, 2026 at 12:57 PM Paul Moore <paul@paul-moore.com> wrote:
> > > > @David, simply for my own understanding, did you ask Daniel to do
> > > > this, or was Daniel operating on his own with this patchset?
> > > >
> > >
> > > Daniel and I work together and both have things written on top of this
> > > kfunc. He reached out to collaborate, I agreed. We're also going to
> > > send bpf_set_file_xattr shortly.
> > >
> > > This implementation was chosen due to its immediate mergeability (it
> > > only touches security/bpf), but was actually suggested by Kumar in v1
> > > or so of my original series.
> > >
> > > That said, sorry for any confusion about this appearing as a new
> > > series rather than as v7 of my previous one.
> >
> > Howdy all,
> >
> > Hope you all are doing well and having a good Thursday.
> >
> > These patches are excellent and useful, and have been in the pipeline
> > for a while.
> >
> > In the interest of moving forward:
> >
> > Would you both be able to live with the following: provide a security hook
> > for lsm_get_xattr_slot or another proper interface with the necessary
> > abstraction, and keep the kfunc where it is in fs/?
>
> That still doesn't change the fundamentals around the kfunc: it is
> really only a valid to call it from within the LSM inode_init_security
> callback, it populates a LSM framework managed buffer, and that buffer
> is then used to by the LSM framework code in
> security_inode_init_security() to do the xattr initialization using
> the values from the BPF LSM as well as all of the other configured
> LSMs. It's very hard to see this as anything other than an LSM kfunc.
> I worked with David over several revisions of his patchset to review
> the code and get it in a good place, I'm supportive of the basic
> ideas, but this really needs to be located in
> security/bpf_lsm_kfuncs.c as it is an LSM kfunc. As we've seen there
> is precedence for subsystem specific kfuncs located in the associated
> subsystem's directory, things should be no different here.
>
Hello,
I'm less arguing for any particular placement:
More so that the concrete security interest of just getting the kfunc
*somewhere* is much more important. I have no doubt that the function
would be equally well stewarded in either directory.
The xattr kfunc in security/ question can be fought best in a different
venue where it's not holding back good contributions. So I think
swallowing this bitter pill for now will be best for cooperation and
good faith if nothing else, otherwise users and contributors are left
footing the bill indefinitely.
Admittedly I'm biased and am toying with a project that this kfunc
would be really nice in, so take me with a grain of salt :)
Thanks,
Justin
> --
> paul-moore.com
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 4/8] bpf, lsm: Let BPF LSM provide xattrs at inode creation
2026-09-24 18:37 ` Justin Suess
@ 2026-09-24 19:33 ` Paul Moore
2026-09-24 19:34 ` Paul Moore
0 siblings, 1 reply; 31+ messages in thread
From: Paul Moore @ 2026-09-24 19:33 UTC (permalink / raw)
To: Justin Suess
Cc: David Windsor, Daniel Borkmann, alexei.starovoitov, brauner,
john.fastabend, memxor, kpsingh, matt, bpf, linux-security-module,
linux-kernel
On Thu, Sep 24, 2026 at 2:38 PM Justin Suess <utilityemal77@gmail.com> wrote:
> On Thu, Sep 24, 2026 at 12:23:13PM -0400, Paul Moore wrote:
> > On Thu, Sep 24, 2026 at 12:15 PM Justin Suess <utilityemal77@gmail.com> wrote:
> > > On Wed, Sep 23, 2026 at 03:14:28PM -0400, David Windsor wrote:
> > > > On Wed, Sep 23, 2026 at 12:57 PM Paul Moore <paul@paul-moore.com> wrote:
> > > > > @David, simply for my own understanding, did you ask Daniel to do
> > > > > this, or was Daniel operating on his own with this patchset?
> > > > >
> > > >
> > > > Daniel and I work together and both have things written on top of this
> > > > kfunc. He reached out to collaborate, I agreed. We're also going to
> > > > send bpf_set_file_xattr shortly.
> > > >
> > > > This implementation was chosen due to its immediate mergeability (it
> > > > only touches security/bpf), but was actually suggested by Kumar in v1
> > > > or so of my original series.
> > > >
> > > > That said, sorry for any confusion about this appearing as a new
> > > > series rather than as v7 of my previous one.
> > >
> > > Howdy all,
> > >
> > > Hope you all are doing well and having a good Thursday.
> > >
> > > These patches are excellent and useful, and have been in the pipeline
> > > for a while.
> > >
> > > In the interest of moving forward:
> > >
> > > Would you both be able to live with the following: provide a security hook
> > > for lsm_get_xattr_slot or another proper interface with the necessary
> > > abstraction, and keep the kfunc where it is in fs/?
> >
> > That still doesn't change the fundamentals around the kfunc: it is
> > really only a valid to call it from within the LSM inode_init_security
> > callback, it populates a LSM framework managed buffer, and that buffer
> > is then used to by the LSM framework code in
> > security_inode_init_security() to do the xattr initialization using
> > the values from the BPF LSM as well as all of the other configured
> > LSMs. It's very hard to see this as anything other than an LSM kfunc.
> > I worked with David over several revisions of his patchset to review
> > the code and get it in a good place, I'm supportive of the basic
> > ideas, but this really needs to be located in
> > security/bpf_lsm_kfuncs.c as it is an LSM kfunc. As we've seen there
> > is precedence for subsystem specific kfuncs located in the associated
> > subsystem's directory, things should be no different here.
> >
> Hello,
>
> I'm less arguing for any particular placement:
>
> More so that the concrete security interest of just getting the kfunc
> *somewhere* is much more important. I have no doubt that the function
> would be equally well stewarded in either directory.
>
> The xattr kfunc in security/ question can be fought best in a different
> venue where it's not holding back good contributions.
This (the mailing list) is *the* venue, and sorting out
cross-subsystem issues isn't a foundational thing, not something that
can be cast aside because it is inconvenient. In this particular
case, resolving this before it lands in Linus' tree is that much more
important since cooperation has been "strained" to put it mildly.
--
paul-moore.com
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH bpf-next 4/8] bpf, lsm: Let BPF LSM provide xattrs at inode creation
2026-09-24 19:33 ` Paul Moore
@ 2026-09-24 19:34 ` Paul Moore
0 siblings, 0 replies; 31+ messages in thread
From: Paul Moore @ 2026-09-24 19:34 UTC (permalink / raw)
To: Justin Suess
Cc: David Windsor, Daniel Borkmann, alexei.starovoitov, brauner,
john.fastabend, memxor, kpsingh, matt, bpf, linux-security-module,
linux-kernel
On Thu, Sep 24, 2026 at 3:33 PM Paul Moore <paul@paul-moore.com> wrote:
> On Thu, Sep 24, 2026 at 2:38 PM Justin Suess <utilityemal77@gmail.com> wrote:
> > On Thu, Sep 24, 2026 at 12:23:13PM -0400, Paul Moore wrote:
> > > On Thu, Sep 24, 2026 at 12:15 PM Justin Suess <utilityemal77@gmail.com> wrote:
> > > > On Wed, Sep 23, 2026 at 03:14:28PM -0400, David Windsor wrote:
> > > > > On Wed, Sep 23, 2026 at 12:57 PM Paul Moore <paul@paul-moore.com> wrote:
> > > > > > @David, simply for my own understanding, did you ask Daniel to do
> > > > > > this, or was Daniel operating on his own with this patchset?
> > > > > >
> > > > >
> > > > > Daniel and I work together and both have things written on top of this
> > > > > kfunc. He reached out to collaborate, I agreed. We're also going to
> > > > > send bpf_set_file_xattr shortly.
> > > > >
> > > > > This implementation was chosen due to its immediate mergeability (it
> > > > > only touches security/bpf), but was actually suggested by Kumar in v1
> > > > > or so of my original series.
> > > > >
> > > > > That said, sorry for any confusion about this appearing as a new
> > > > > series rather than as v7 of my previous one.
> > > >
> > > > Howdy all,
> > > >
> > > > Hope you all are doing well and having a good Thursday.
> > > >
> > > > These patches are excellent and useful, and have been in the pipeline
> > > > for a while.
> > > >
> > > > In the interest of moving forward:
> > > >
> > > > Would you both be able to live with the following: provide a security hook
> > > > for lsm_get_xattr_slot or another proper interface with the necessary
> > > > abstraction, and keep the kfunc where it is in fs/?
> > >
> > > That still doesn't change the fundamentals around the kfunc: it is
> > > really only a valid to call it from within the LSM inode_init_security
> > > callback, it populates a LSM framework managed buffer, and that buffer
> > > is then used to by the LSM framework code in
> > > security_inode_init_security() to do the xattr initialization using
> > > the values from the BPF LSM as well as all of the other configured
> > > LSMs. It's very hard to see this as anything other than an LSM kfunc.
> > > I worked with David over several revisions of his patchset to review
> > > the code and get it in a good place, I'm supportive of the basic
> > > ideas, but this really needs to be located in
> > > security/bpf_lsm_kfuncs.c as it is an LSM kfunc. As we've seen there
> > > is precedence for subsystem specific kfuncs located in the associated
> > > subsystem's directory, things should be no different here.
> > >
> > Hello,
> >
> > I'm less arguing for any particular placement:
> >
> > More so that the concrete security interest of just getting the kfunc
> > *somewhere* is much more important. I have no doubt that the function
> > would be equally well stewarded in either directory.
> >
> > The xattr kfunc in security/ question can be fought best in a different
> > venue where it's not holding back good contributions.
>
> This (the mailing list) is *the* venue, and sorting out
> cross-subsystem issues isn't a foundational thing, not something that
Sorry, typo.
/isn't/is/
Sorting out cross-subsystem issues *is* a foundational thing.
> can be cast aside because it is inconvenient. In this particular
> case, resolving this before it lands in Linus' tree is that much more
> important since cooperation has been "strained" to put it mildly.
--
paul-moore.com
^ permalink raw reply [flat|nested] 31+ messages in thread
end of thread, other threads:[~2026-09-24 19:34 UTC | newest]
Thread overview: 31+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 ` [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
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox