Re: [PATCH v2 1/2] bpf: add bpf_init_inode_xattr kfunc for atomic inode labeling
Paul Moore <[email protected]>
| Newsgroups | org.kernel.vger.linux-integrity,org.kernel.vger.bpf,org.kernel.vger.linux-fsdevel,org.kernel.vger.linux-kernel,org.kernel.vger.linux-security-module,org.kernel.vger.selinux |
|---|---|
| Message-ID | <CAHC9VhSy5K5nQTtFUE4BScy1Ur61v7eZW067vTcUYDQeJb13Bw@mail.gmail.com> |
On Sun, May 3, 2026 at 5:18 PM David Windsor <[email protected]> wrote: > > Add bpf_init_inode_xattr() kfunc for BPF LSM programs to atomically set > xattrs via the inode_init_security hook using lsm_get_xattr_slot(). > > The inode_init_security hook previously took the xattr array and count > as two separate output parameters (struct xattr *xattrs, int > *xattr_count), which BPF programs cannot write to. Pass the xattr state > as a single context object (struct lsm_xattr_ctx) instead, and have > bpf_init_inode_xattr() take that context directly. Update the existing > in-tree callers of inode_init_security to take and forward the new > lsm_xattr_ctx. > > Because we rely on the hook-specific ctx layout, the kfunc is > restricted to lsm/inode_init_security. Restrict the xattr names that > may be set via this kfunc to the bpf.* namespace. > > Suggested-by: Song Liu <[email protected]> > Signed-off-by: David Windsor <[email protected]> > --- > fs/bpf_fs_kfuncs.c | 106 +++++++++++++++++++++++++++++- > include/linux/bpf_lsm.h | 3 + > include/linux/evm.h | 9 +-- > include/linux/lsm_hook_defs.h | 4 +- > include/linux/lsm_hooks.h | 16 ++--- > include/linux/security.h | 5 ++ > kernel/bpf/bpf_lsm.c | 1 + > security/bpf/hooks.c | 1 + > security/integrity/evm/evm_main.c | 8 ++- > security/security.c | 7 +- > security/selinux/hooks.c | 4 +- > security/smack/smack_lsm.c | 13 ++-- > 12 files changed, 147 insertions(+), 30 deletions(-) Comments below ... > diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c > index 9d27be058494..193accc00796 100644 > --- a/fs/bpf_fs_kfuncs.c > +++ b/fs/bpf_fs_kfuncs.c > @@ -10,6 +10,7 @@ > #include <linux/fsnotify.h> > #include <linux/file.h> > #include <linux/kernfs.h> > +#include <linux/lsm_hooks.h> > #include <linux/mm.h> > #include <linux/xattr.h> > > @@ -353,6 +354,97 @@ __bpf_kfunc int bpf_cgroup_read_xattr(struct cgroup *cgroup, const char *name__s > } > #endif /* CONFIG_CGROUPS */ > > +static int bpf_xattrs_used(const struct lsm_xattr_ctx *ctx) > +{ > + const size_t prefix_len = sizeof(XATTR_BPF_LSM_SUFFIX) - 1; > + int i, n = 0; > + > + for (i = 0; i < *ctx->xattr_count; i++) { > + const char *name = ctx->xattrs[i].name; > + > + if (name && !strncmp(name, XATTR_BPF_LSM_SUFFIX, prefix_len)) > + n++; > + } > + return n; > +} > + > +static int __bpf_init_inode_xattr(struct lsm_xattr_ctx *xattr_ctx, > + const char *name__str, > + const struct bpf_dynptr *value_p) > +{ > + struct bpf_dynptr_kern *value_ptr = (struct bpf_dynptr_kern *)value_p; > + size_t name_len; > + void *xattr_value; > + struct xattr *xattr; > + struct xattr *xattrs; > + int *xattr_count; > + const void *value; > + u32 value_len; > + > + if (!xattr_ctx || !name__str) > + return -EINVAL; > + > + xattrs = xattr_ctx->xattrs; > + xattr_count = xattr_ctx->xattr_count; > + if (!xattrs || !xattr_count) > + return -EINVAL; > + if (bpf_xattrs_used(xattr_ctx) >= BPF_LSM_INODE_INIT_XATTRS) > + return -ENOSPC; > + > + name_len = strlen(name__str); > + if (name_len == 0 || name_len > XATTR_NAME_MAX) > + return -EINVAL; > + if (strncmp(name__str, XATTR_BPF_LSM_SUFFIX, > + sizeof(XATTR_BPF_LSM_SUFFIX) - 1)) > + return -EPERM; > + > + value_len = __bpf_dynptr_size(value_ptr); > + if (value_len == 0 || value_len > XATTR_SIZE_MAX) > + return -EINVAL; > + > + value = __bpf_dynptr_data(value_ptr, value_len); > + if (!value) > + return -EINVAL; > + > + /* Combine xattr value + name into one allocation. */ > + xattr_value = kmalloc(value_len + name_len + 1, GFP_KERNEL); > + if (!xattr_value) > + return -ENOMEM; > + > + memcpy(xattr_value, value, value_len); > + memcpy(xattr_value + value_len, name__str, name_len); > + ((char *)xattr_value)[value_len + name_len] = '\0'; > + > + xattr = lsm_get_xattr_slot(xattr_ctx); > + if (!xattr) { > + kfree(xattr_value); > + return -ENOSPC; > + } > + > + xattr->value = xattr_value; > + xattr->name = (const char *)xattr_value + value_len; > + xattr->value_len = value_len; > + > + return 0; > +} > + > +/** > + * bpf_init_inode_xattr - set an xattr on a new inode from inode_init_security > + * @xattr_ctx: inode_init_security xattr state from the hook context > + * @name__str: xattr name (e.g., "bpf.file_label") > + * @value_p: dynptr containing the xattr value > + * > + * Only callable from lsm/inode_init_security programs. > + * > + * Return: 0 on success, negative error on failure. > + */ > +__bpf_kfunc int bpf_init_inode_xattr(struct lsm_xattr_ctx *xattr_ctx, > + const char *name__str, > + const struct bpf_dynptr *value_p) > +{ > + return __bpf_init_inode_xattr(xattr_ctx, name__str, value_p); > +} > + > __bpf_kfunc_end_defs(); > > BTF_KFUNCS_START(bpf_fs_kfunc_set_ids) > @@ -363,13 +455,25 @@ BTF_ID_FLAGS(func, bpf_get_dentry_xattr, KF_SLEEPABLE) > 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_init_inode_xattr, KF_SLEEPABLE) > BTF_KFUNCS_END(bpf_fs_kfunc_set_ids) > > +BTF_ID_LIST(bpf_lsm_inode_init_security_btf_ids) > +BTF_ID(func, bpf_lsm_inode_init_security) > + > +BTF_ID_LIST(bpf_init_inode_xattr_btf_ids) > +BTF_ID(func, bpf_init_inode_xattr) > + > 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) || > - prog->type == BPF_PROG_TYPE_LSM) > + prog->type == BPF_PROG_TYPE_LSM) { > + /* bpf_init_inode_xattr only attaches to inode_init_security. */ > + if (kfunc_id == bpf_init_inode_xattr_btf_ids[0] && > + prog->aux->attach_btf_id != bpf_lsm_inode_init_security_btf_ids[0]) > + return -EACCES; > return 0; > + } > return -EACCES; > } Perhaps I'm simply not seeing it, but is there a check to ensure that there is only one BPF LSM calling into security_inode_init_security() at any given time? With the BPF LSM only reserving a single xattr slot, multiple loaded BPF LSM programs providing security_inode_init_security() callbacks will be a problem. > diff --git a/include/linux/security.h b/include/linux/security.h > index 41d7367cf403..a2fc72e63ada 100644 > --- a/include/linux/security.h > +++ b/include/linux/security.h > @@ -68,6 +68,11 @@ struct watch; > struct watch_notification; > struct lsm_ctx; > > +struct lsm_xattr_ctx { > + struct xattr *xattrs; > + int *xattr_count; > +}; I'd prefer this to be simply "struct lsm_xattrs" as "ctx" is an overloaded term in the LSM space. > diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c > index 97801966bf32..dca81a22bf83 100644 > --- a/security/selinux/hooks.c > +++ b/security/selinux/hooks.c > @@ -2962,11 +2962,11 @@ static int selinux_dentry_create_files_as(struct dentry *dentry, int mode, > > static int selinux_inode_init_security(struct inode *inode, struct inode *dir, > const struct qstr *qstr, > - struct xattr *xattrs, int *xattr_count) > + struct lsm_xattr_ctx *xattr_ctx) > { > const struct cred_security_struct *crsec = selinux_cred(current_cred()); > struct superblock_security_struct *sbsec; > - struct xattr *xattr = lsm_get_xattr_slot(xattrs, xattr_count); > + struct xattr *xattr = lsm_get_xattr_slot(xattr_ctx); > u32 newsid, clen; > u16 newsclass; > int rc; In case you didn't see it, your fix for the above lsm_get_xattr_slot() usage is now in Linus' tree. It's a trivial bit of merge fuzz, but you might want to rebase your next revision. -- paul-moore.com