Linux Integrity Measurement development
 help / color / mirror / Atom feed
From: "Alexei Starovoitov" <alexei.starovoitov@gmail.com>
To: "David Windsor" <dwindsor@gmail.com>, <viro@zeniv.linux.org.uk>,
	<brauner@kernel.org>, <jack@suse.cz>, <ast@kernel.org>,
	<daniel@iogearbox.net>, <john.fastabend@gmail.com>,
	<andrii@kernel.org>, <eddyz87@gmail.com>, <memxor@gmail.com>,
	<martin.lau@linux.dev>, <song@kernel.org>,
	<yonghong.song@linux.dev>, <jolsa@kernel.org>,
	<emil@etsalapatis.com>, <kpsingh@kernel.org>,
	<mattbobrowski@google.com>, <paul@paul-moore.com>,
	<jmorris@namei.org>, <serge@hallyn.com>, <zohar@linux.ibm.com>,
	<roberto.sassu@huawei.com>, <dmitry.kasatkin@gmail.com>,
	<eric.snowberg@oracle.com>, <stephen.smalley.work@gmail.com>,
	<omosnace@redhat.com>, <casey@schaufler-ca.com>,
	<shuah@kernel.org>
Cc: <linux-kernel@vger.kernel.org>, <linux-fsdevel@vger.kernel.org>,
	<bpf@vger.kernel.org>, <linux-security-module@vger.kernel.org>,
	<linux-integrity@vger.kernel.org>, <selinux@vger.kernel.org>,
	<linux-kselftest@vger.kernel.org>
Subject: Re: [PATCH bpf-next v3 1/2] bpf: add bpf_init_inode_xattr kfunc for atomic inode labeling
Date: Mon, 22 Jun 2026 16:57:08 -0700	[thread overview]
Message-ID: <DJFZGYZFMN73.1799LMXW49WZO@gmail.com> (raw)
In-Reply-To: <20260618203411.73917-2-dwindsor@gmail.com>

On Thu Jun 18, 2026 at 1:34 PM PDT, David Windsor wrote:
> +
> +static int __bpf_init_inode_xattr(struct 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;

This check is good to have, but it's enough. No need to duplicate it.
More below.

> +
> +	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 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)
> @@ -385,13 +477,25 @@ 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_inode, KF_SLEEPABLE | KF_RET_NULL)
> +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;

This is unnecessary. Only one hook will have xattr_ctx type.
The normal verifier type enforcement will do its work.

>  		return 0;
> +	}
>  	return -EACCES;
>  }
>  
> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> index 7719f6528445..f14bfcda78db 100644
> --- a/include/linux/bpf.h
> +++ b/include/linux/bpf.h
> @@ -1752,6 +1752,7 @@ struct bpf_prog_aux {
>  	u32 real_func_cnt; /* includes hidden progs, only used for JIT and freeing progs */
>  	u32 func_idx; /* 0 for non-func prog, the index in func array for func prog */
>  	u32 attach_btf_id; /* in-kernel BTF type id to attach to */
> +	u32 attach_limit; /* max concurrent attachments (0 = unlimited) */

no need.

>  	u32 attach_st_ops_member_off;
>  	u32 ctx_arg_info_size;
>  	u32 max_rdonly_access;
> diff --git a/include/linux/bpf_lsm.h b/include/linux/bpf_lsm.h
> index 143775a27a2a..b655c708818e 100644
> --- a/include/linux/bpf_lsm.h
> +++ b/include/linux/bpf_lsm.h
> @@ -19,6 +19,9 @@
>  #include <linux/lsm_hook_defs.h>
>  #undef LSM_HOOK
>  
> +/* max bpf xattrs per inode */
> +#define BPF_LSM_INODE_INIT_XATTRS 4
> +
>  struct bpf_storage_blob {
>  	struct bpf_local_storage __rcu *storage;
>  };
> diff --git a/include/linux/evm.h b/include/linux/evm.h
> index 913f4573b203..0aa151288b36 100644
> --- a/include/linux/evm.h
> +++ b/include/linux/evm.h
> @@ -12,6 +12,8 @@
>  #include <linux/integrity.h>
>  #include <linux/xattr.h>
>  
> +struct xattr_ctx;
> +
>  #ifdef CONFIG_EVM
>  extern int evm_set_key(void *key, size_t keylen);
>  extern enum integrity_status evm_verifyxattr(struct dentry *dentry,
> @@ -21,8 +23,8 @@ extern enum integrity_status evm_verifyxattr(struct dentry *dentry,
>  int evm_fix_hmac(struct dentry *dentry, const char *xattr_name,
>  		 const char *xattr_value, size_t xattr_value_len);
>  int evm_inode_init_security(struct inode *inode, struct inode *dir,
> -			    const struct qstr *qstr, struct xattr *xattrs,
> -			    int *xattr_count);
> +			    const struct qstr *qstr,
> +			    struct xattr_ctx *xattr_ctx);
>  extern bool evm_revalidate_status(const char *xattr_name);
>  extern int evm_protected_xattr_if_enabled(const char *req_xattr_name);
>  extern int evm_read_protected_xattrs(struct dentry *dentry, u8 *buffer,
> @@ -63,8 +65,7 @@ static inline int evm_fix_hmac(struct dentry *dentry, const char *xattr_name,
>  
>  static inline int evm_inode_init_security(struct inode *inode, struct inode *dir,
>  					  const struct qstr *qstr,
> -					  struct xattr *xattrs,
> -					  int *xattr_count)
> +					  struct xattr_ctx *xattr_ctx)
>  {
>  	return 0;
>  }
> diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
> index 65c9609ec207..f62780fbeb9e 100644
> --- a/include/linux/lsm_hook_defs.h
> +++ b/include/linux/lsm_hook_defs.h
> @@ -116,8 +116,8 @@ LSM_HOOK(int, 0, inode_alloc_security, struct inode *inode)
>  LSM_HOOK(void, LSM_RET_VOID, inode_free_security, struct inode *inode)
>  LSM_HOOK(void, LSM_RET_VOID, inode_free_security_rcu, void *inode_security)
>  LSM_HOOK(int, -EOPNOTSUPP, inode_init_security, struct inode *inode,
> -	 struct inode *dir, const struct qstr *qstr, struct xattr *xattrs,
> -	 int *xattr_count)
> +	 struct inode *dir, const struct qstr *qstr,
> +	 struct xattr_ctx *xattr_ctx)
>  LSM_HOOK(int, 0, inode_init_security_anon, struct inode *inode,
>  	 const struct qstr *name, const struct inode *context_inode)
>  LSM_HOOK(int, 0, inode_create, struct inode *dir, struct dentry *dentry,
> diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
> index b4f8cad53ddb..710e48caaeba 100644
> --- a/include/linux/lsm_hooks.h
> +++ b/include/linux/lsm_hooks.h
> @@ -200,20 +200,18 @@ extern struct lsm_static_calls_table static_calls_table __ro_after_init;
>  
>  /**
>   * lsm_get_xattr_slot - Return the next available slot and increment the index
> - * @xattrs: array storing LSM-provided xattrs
> - * @xattr_count: number of already stored xattrs (updated)
> + * @ctx: xattr state shared by inode_init_security hooks
>   *
> - * Retrieve the first available slot in the @xattrs array to fill with an xattr,
> - * and increment @xattr_count.
> + * Retrieve the first available slot in the @ctx->xattrs array to fill with an
> + * xattr, and increment @ctx->xattr_count.
>   *
> - * Return: The slot to fill in @xattrs if non-NULL, NULL otherwise.
> + * Return: The slot to fill in @ctx->xattrs if non-NULL, NULL otherwise.
>   */
> -static inline struct xattr *lsm_get_xattr_slot(struct xattr *xattrs,
> -					       int *xattr_count)
> +static inline struct xattr *lsm_get_xattr_slot(struct xattr_ctx *ctx)
>  {
> -	if (unlikely(!xattrs))
> +	if (unlikely(!ctx || !ctx->xattrs || !ctx->xattr_count))
>  		return NULL;
> -	return &xattrs[(*xattr_count)++];
> +	return &ctx->xattrs[(*ctx->xattr_count)++];
>  }
>  
>  #endif /* ! __LINUX_LSM_HOOKS_H */
> diff --git a/include/linux/security.h b/include/linux/security.h
> index 153e9043058f..1f8e84e7dd7e 100644
> --- a/include/linux/security.h
> +++ b/include/linux/security.h
> @@ -68,6 +68,11 @@ struct watch;
>  struct watch_notification;
>  struct lsm_ctx;
>  
> +struct xattr_ctx {
> +	struct xattr *xattrs;
> +	int *xattr_count;
> +};
> +
>  /* Default (no) options for the capable function */
>  #define CAP_OPT_NONE 0x0
>  /* If capable should audit the security request */
> diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c
> index 564071a92d7d..86a8e188b900 100644
> --- a/kernel/bpf/bpf_lsm.c
> +++ b/kernel/bpf/bpf_lsm.c
> @@ -113,6 +113,9 @@ void bpf_lsm_find_cgroup_shim(const struct bpf_prog *prog,
>  }
>  #endif
>  
> +BTF_ID_LIST_SINGLE(bpf_lsm_inode_init_security_btf_ids, func,
> +		   bpf_lsm_inode_init_security)
> +
>  int bpf_lsm_verify_prog(struct bpf_verifier_log *vlog,
>  			const struct bpf_prog *prog)
>  {
> @@ -137,6 +140,12 @@ int bpf_lsm_verify_prog(struct bpf_verifier_log *vlog,
>  		return -EINVAL;
>  	}
>  
> +	/* bpf reserves a fixed number of xattr slots for itself.
> +	 * Set the attach limit so the trampoline rejects excess attaches.
> +	 */
> +	if (btf_id == bpf_lsm_inode_init_security_btf_ids[0])
> +		prog->aux->attach_limit = BPF_LSM_INODE_INIT_XATTRS;
> +
>  	return 0;
>  }
>  
> @@ -315,6 +324,7 @@ BTF_ID(func, bpf_lsm_inode_create)
>  BTF_ID(func, bpf_lsm_inode_free_security)
>  BTF_ID(func, bpf_lsm_inode_getattr)
>  BTF_ID(func, bpf_lsm_inode_getxattr)
> +BTF_ID(func, bpf_lsm_inode_init_security)
>  BTF_ID(func, bpf_lsm_inode_mknod)
>  BTF_ID(func, bpf_lsm_inode_need_killpriv)
>  BTF_ID(func, bpf_lsm_inode_post_setxattr)
> diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c
> index 1a721fc4bef5..b41b02173e24 100644
> --- a/kernel/bpf/trampoline.c
> +++ b/kernel/bpf/trampoline.c
> @@ -859,6 +859,9 @@ static int bpf_trampoline_add_prog(struct bpf_trampoline *tr,
>  	}
>  	if (cnt >= BPF_MAX_TRAMP_LINKS)
>  		return -E2BIG;
> +	if (node->link->prog->aux->attach_limit &&
> +	    tr->progs_cnt[kind] >= node->link->prog->aux->attach_limit)
> +		return -E2BIG;

No need. The check inside kfunc is enough.

>  	if (!hlist_unhashed(&node->tramp_hlist))
>  		/* prog already linked */
>  		return -EBUSY;
> diff --git a/security/bpf/hooks.c b/security/bpf/hooks.c
> index 40efde233f3a..d7c44c5c0e30 100644
> --- a/security/bpf/hooks.c
> +++ b/security/bpf/hooks.c
> @@ -30,6 +30,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) = {
> diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c
> index b59e3f121b8a..e0a05162accc 100644
> --- a/security/integrity/evm/evm_main.c
> +++ b/security/integrity/evm/evm_main.c
> @@ -1062,14 +1062,16 @@ static int evm_inode_copy_up_xattr(struct dentry *src, const char *name)
>   * evm_inode_init_security - initializes security.evm HMAC value
>   */
>  int evm_inode_init_security(struct inode *inode, struct inode *dir,
> -			    const struct qstr *qstr, struct xattr *xattrs,
> -			    int *xattr_count)
> +			    const struct qstr *qstr,
> +			    struct xattr_ctx *xattr_ctx)

the rest looks good.
Pls split the patch. Introduce xattr_ctx first across the LSMs.
Then another patch with a new kfunc.

pw-bot: cr

  parent reply	other threads:[~2026-06-22 23:57 UTC|newest]

Thread overview: 18+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-18 20:34 [PATCH bpf-next v3 0/2] bpf: add bpf_init_inode_xattr kfunc for atomic inode labeling David Windsor
2026-06-18 20:34 ` [PATCH bpf-next v3 1/2] " David Windsor
2026-06-18 21:22   ` bot+bpf-ci
2026-06-18 22:50     ` David Windsor
2026-06-19 10:25   ` Christian Brauner
2026-06-22 23:57   ` Alexei Starovoitov [this message]
2026-06-23  3:49     ` David Windsor
2026-06-23  3:59       ` Alexei Starovoitov
2026-06-23  4:01         ` David Windsor
2026-06-24  0:12   ` [PATCH " Paul Moore
2026-06-24 21:22     ` David Windsor
2026-06-25 14:23     ` Christian Brauner
2026-06-25 16:06       ` Paul Moore
2026-06-25 19:58       ` Alexei Starovoitov
2026-06-25 20:40         ` Paul Moore
2026-06-25 20:44           ` Alexei Starovoitov
2026-06-25 20:47             ` Paul Moore
2026-06-18 20:34 ` [PATCH bpf-next v3 2/2] selftests/bpf: add tests for bpf_init_inode_xattr kfunc David Windsor

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=DJFZGYZFMN73.1799LMXW49WZO@gmail.com \
    --to=alexei.starovoitov@gmail.com \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=brauner@kernel.org \
    --cc=casey@schaufler-ca.com \
    --cc=daniel@iogearbox.net \
    --cc=dmitry.kasatkin@gmail.com \
    --cc=dwindsor@gmail.com \
    --cc=eddyz87@gmail.com \
    --cc=emil@etsalapatis.com \
    --cc=eric.snowberg@oracle.com \
    --cc=jack@suse.cz \
    --cc=jmorris@namei.org \
    --cc=john.fastabend@gmail.com \
    --cc=jolsa@kernel.org \
    --cc=kpsingh@kernel.org \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-integrity@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=linux-security-module@vger.kernel.org \
    --cc=martin.lau@linux.dev \
    --cc=mattbobrowski@google.com \
    --cc=memxor@gmail.com \
    --cc=omosnace@redhat.com \
    --cc=paul@paul-moore.com \
    --cc=roberto.sassu@huawei.com \
    --cc=selinux@vger.kernel.org \
    --cc=serge@hallyn.com \
    --cc=shuah@kernel.org \
    --cc=song@kernel.org \
    --cc=stephen.smalley.work@gmail.com \
    --cc=viro@zeniv.linux.org.uk \
    --cc=yonghong.song@linux.dev \
    --cc=zohar@linux.ibm.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox