public inbox for selinux@vger.kernel.org
 help / color / mirror / Atom feed
From: Eric Suen <ericsu@linux.microsoft.com>
To: selinux@vger.kernel.org
Cc: paul@paul-moore.com, stephen.smalley.work@gmail.com,
	omosnace@redhat.com, danieldurning.work@gmail.com
Subject: Re: [PATCH v7] SELinux: Add support for BPF token access control
Date: Thu, 4 Dec 2025 19:12:40 -0800	[thread overview]
Message-ID: <caeaa280-ffa3-415f-bf39-340f66ee45fa@linux.microsoft.com> (raw)
In-Reply-To: <20251205024259.704-1-ericsu@linux.microsoft.com>

On 12/4/2025 6:42 PM, Eric Suen wrote:

I need to mention that this patch is based on v6.17. Ran into few 
Hyper-V VM/testing issues with the latest kernel (v6.18) that I have not 
yet been able to resolve. I believe the issues are related to HYPERV 
configs, but troubleshooting will take some time. In the meantime, I 
would like to get this patch reviewed early.
> BPF token support was introduced to allow a privileged process to delegate
> limited BPF functionality—such as map creation and program loading—to
> an unprivileged process:
>    https://lore.kernel.org/linux-security-module/20231130185229.2688956-1-andrii@kernel.org/
>
> This patch adds SELinux support for controlling BPF token access. With
> this change, SELinux policies can now enforce constraints on BPF token
> usage based on both the delegating (privileged) process and the recipient
> (unprivileged) process.
>
> Supported operations currently include:
>    - map_create
>    - prog_load
>
> High-level workflow:
>    1. An unprivileged process creates a VFS context via `fsopen()` and
>       obtains a file descriptor.
>    2. This descriptor is passed to a privileged process, which configures
>       BPF token delegation options and mounts a BPF filesystem.
>    3. SELinux records the `creator_sid` of the privileged process during
>       mount setup.
>    4. The unprivileged process then uses this BPF fs mount to create a
>       token and attach it to subsequent BPF syscalls.
>    5. During verification of `map_create` and `prog_load`, SELinux uses
>       `creator_sid` and the current SID to check policy permissions via:
>         avc_has_perm(creator_sid, current_sid, SECCLASS_BPF,
>                      BPF__MAP_CREATE, NULL);
>
> The implementation introduces two new permissions:
>    - map_create_as
>    - prog_load_as
>
> At token creation time, SELinux verifies that the current process has the
> appropriate `*_as` permission (depending on the `allowed_cmds` value in
> the bpf_token) to act on behalf of the `creator_sid`.
>
> Example SELinux policy:
>    allow test_bpf_t self:bpf {
>        map_create map_read map_write prog_load prog_run
>        map_create_as prog_load_as
>    };
>
> Additionally, a new policy capability bpf_token_perms is added to ensure
> backward compatibility. If disabled, previous behavior ((checks based on
> current process SID)) is preserved.
>
> Signed-off-by: Eric Suen <ericsu@linux.microsoft.com>
> ---
> Changes in v2:
> - Fixed bug in selinux_bpffs_creator_sid(u32 fd) where it retrieved
>    creator_sid from wrong file descriptor
> - Removed unnecessary checks for null, per review comments from
>    the first patch
>
> Changes in v3:
> - Removed check for 'sid == SECSID_NULL' in selinux_bpf_token_create and
>    allow it to fall through to the permission checks which will fail as
>    access denied to unlabeled_t
>
> Changes in v4:
> - Added initialization of creator_sid in selinux_sb_alloc_security
> - Enabled handling of creator_sid in selinux_cmp_sb_context and
>    selinux_sb_clone_mnt_opts
> - Minor updates based on review comments
>
> Changes in v5:
> - Moved to dev-staging branch instead of main
> - Added implementation of selinux_bpf_token_capable which is originally
>    from Daniel's patch
>    https://lore.kernel.org/selinux/20250801154637.143931-1-danieldurning.work@gmail.com/
>
> Changes in v6:
> - Updated bpf_token_capable to use grantor_sid as object in permission
>    check
>
> Changes in v7:
> - Bug fix in selinux_bpf_token_capable where incorrect source sid
>    was used in avc_has_perm
>
>   security/selinux/hooks.c                   | 117 ++++++++++++++++++++-
>   security/selinux/include/classmap.h        |   2 +-
>   security/selinux/include/objsec.h          |   3 +
>   security/selinux/include/policycap.h       |   1 +
>   security/selinux/include/policycap_names.h |   1 +
>   security/selinux/include/security.h        |   6 ++
>   6 files changed, 127 insertions(+), 3 deletions(-)
>
> diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
> index c95a5874bf7d..ffae25a18bd9 100644
> --- a/security/selinux/hooks.c
> +++ b/security/selinux/hooks.c
> @@ -733,6 +733,8 @@ static int selinux_set_mnt_opts(struct super_block *sb,
>   		goto out;
>   	}
>   
> +	sbsec->creator_sid = current_sid();
> +
>   	if (strcmp(sb->s_type->name, "proc") == 0)
>   		sbsec->flags |= SE_SBPROC | SE_SBGENFS;
>   
> @@ -902,6 +904,8 @@ static int selinux_cmp_sb_context(const struct super_block *oldsb,
>   		if (oldroot->sid != newroot->sid)
>   			goto mismatch;
>   	}
> +	if (old->creator_sid != new->creator_sid)
> +		goto mismatch;
>   	return 0;
>   mismatch:
>   	pr_warn("SELinux: mount invalid.  Same superblock, "
> @@ -961,6 +965,7 @@ static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb,
>   	newsbsec->sid = oldsbsec->sid;
>   	newsbsec->def_sid = oldsbsec->def_sid;
>   	newsbsec->behavior = oldsbsec->behavior;
> +	newsbsec->creator_sid = oldsbsec->creator_sid;
>   
>   	if (newsbsec->behavior == SECURITY_FS_USE_NATIVE &&
>   		!(kern_flags & SECURITY_LSM_NATIVE_LABELS) && !set_context) {
> @@ -2576,6 +2581,7 @@ static int selinux_sb_alloc_security(struct super_block *sb)
>   	sbsec->sid = SECINITSID_UNLABELED;
>   	sbsec->def_sid = SECINITSID_FILE;
>   	sbsec->mntpoint_sid = SECINITSID_UNLABELED;
> +	sbsec->creator_sid = SECINITSID_UNLABELED;
>   
>   	return 0;
>   }
> @@ -7017,6 +7023,9 @@ static int selinux_bpf(int cmd, union bpf_attr *attr,
>   	u32 sid = current_sid();
>   	int ret;
>   
> +	if (selinux_policycap_bpf_token_perms())
> +		return 0;
> +
>   	switch (cmd) {
>   	case BPF_MAP_CREATE:
>   		ret = avc_has_perm(sid, sid, SECCLASS_BPF, BPF__MAP_CREATE,
> @@ -7098,10 +7107,29 @@ static int selinux_bpf_prog(struct bpf_prog *prog)
>   			    BPF__PROG_RUN, NULL);
>   }
>   
> +static u32 selinux_bpffs_creator_sid(u32 fd)
> +{
> +	struct path path;
> +	struct super_block *sb;
> +	struct superblock_security_struct *sbsec;
> +
> +	CLASS(fd, f)(fd);
> +
> +	if (fd_empty(f))
> +		return SECSID_NULL;
> +
> +	path = fd_file(f)->f_path;
> +	sb = path.dentry->d_sb;
> +	sbsec = selinux_superblock(sb);
> +
> +	return sbsec->creator_sid;
> +}
> +
>   static int selinux_bpf_map_create(struct bpf_map *map, union bpf_attr *attr,
>   				  struct bpf_token *token, bool kernel)
>   {
>   	struct bpf_security_struct *bpfsec;
> +	u32 ssid;
>   
>   	bpfsec = kzalloc(sizeof(*bpfsec), GFP_KERNEL);
>   	if (!bpfsec)
> @@ -7110,7 +7138,12 @@ static int selinux_bpf_map_create(struct bpf_map *map, union bpf_attr *attr,
>   	bpfsec->sid = current_sid();
>   	map->security = bpfsec;
>   
> -	return 0;
> +	if (!token)
> +		ssid = bpfsec->sid;
> +	else
> +		ssid = selinux_bpffs_creator_sid(attr->map_token_fd);
> +
> +	return avc_has_perm(ssid, bpfsec->sid, SECCLASS_BPF, BPF__MAP_CREATE, NULL);
>   }
>   
>   static void selinux_bpf_map_free(struct bpf_map *map)
> @@ -7125,6 +7158,7 @@ static int selinux_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr,
>   				 struct bpf_token *token, bool kernel)
>   {
>   	struct bpf_security_struct *bpfsec;
> +	u32 ssid;
>   
>   	bpfsec = kzalloc(sizeof(*bpfsec), GFP_KERNEL);
>   	if (!bpfsec)
> @@ -7133,7 +7167,12 @@ static int selinux_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr,
>   	bpfsec->sid = current_sid();
>   	prog->aux->security = bpfsec;
>   
> -	return 0;
> +	if (!token)
> +		ssid = bpfsec->sid;
> +	else
> +		ssid = selinux_bpffs_creator_sid(attr->prog_token_fd);
> +
> +	return avc_has_perm(ssid, bpfsec->sid, SECCLASS_BPF, BPF__PROG_LOAD, NULL);
>   }
>   
>   static void selinux_bpf_prog_free(struct bpf_prog *prog)
> @@ -7144,18 +7183,47 @@ static void selinux_bpf_prog_free(struct bpf_prog *prog)
>   	kfree(bpfsec);
>   }
>   
> +#define bpf_token_cmd(T, C) \
> +	((T)->allowed_cmds & (1ULL << (C)))
> +
>   static int selinux_bpf_token_create(struct bpf_token *token, union bpf_attr *attr,
>   				    const struct path *path)
>   {
>   	struct bpf_security_struct *bpfsec;
> +	u32 sid = selinux_bpffs_creator_sid(attr->token_create.bpffs_fd);
> +	int err;
>   
>   	bpfsec = kzalloc(sizeof(*bpfsec), GFP_KERNEL);
>   	if (!bpfsec)
>   		return -ENOMEM;
>   
>   	bpfsec->sid = current_sid();
> +	bpfsec->grantor_sid = sid;
>   	token->security = bpfsec;
>   
> +	bpfsec->perms = 0;
> +
> +	/**
> +	 * 'token->allowed_cmds' is a bit mask of allowed commands
> +	 * Convert the BPF command enum to a bitmask representing its position in the
> +	 * allowed_cmds bitmap.
> +	 */
> +	if (bpf_token_cmd(token, BPF_MAP_CREATE)) {
> +		err = avc_has_perm(bpfsec->sid, sid, SECCLASS_BPF, BPF__MAP_CREATE_AS, NULL);
> +		if (err)
> +			return err;
> +
> +		bpfsec->perms |= BPF__MAP_CREATE;
> +	}
> +
> +	if (bpf_token_cmd(token, BPF_PROG_LOAD)) {
> +		err = avc_has_perm(bpfsec->sid, sid, SECCLASS_BPF, BPF__PROG_LOAD_AS, NULL);
> +		if (err)
> +			return err;
> +
> +		bpfsec->perms |= BPF__PROG_LOAD;
> +	}
> +
>   	return 0;
>   }
>   
> @@ -7166,6 +7234,49 @@ static void selinux_bpf_token_free(struct bpf_token *token)
>   	token->security = NULL;
>   	kfree(bpfsec);
>   }
> +
> +static int selinux_bpf_token_cmd(const struct bpf_token *token, enum bpf_cmd cmd)
> +{
> +	struct bpf_security_struct *bpfsec;
> +
> +	bpfsec = token->security;
> +	switch (cmd) {
> +	case BPF_MAP_CREATE:
> +		if (!(bpfsec->perms & BPF__MAP_CREATE))
> +			return -EACCES;
> +		break;
> +	case BPF_PROG_LOAD:
> +		if (!(bpfsec->perms & BPF__PROG_LOAD))
> +			return -EACCES;
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	return 0;
> +}
> +
> +static int selinux_bpf_token_capable(const struct bpf_token *token, int cap)
> +{
> +	u16 sclass;
> +	struct bpf_security_struct *bpfsec = token->security;
> +	bool initns = (token->userns == &init_user_ns);
> +	u32 av = CAP_TO_MASK(cap);
> +
> +	switch (CAP_TO_INDEX(cap)) {
> +	case 0:
> +		sclass = initns ? SECCLASS_CAPABILITY : SECCLASS_CAP_USERNS;
> +		break;
> +	case 1:
> +		sclass = initns ? SECCLASS_CAPABILITY : SECCLASS_CAP2_USERNS;
> +		break;
> +	default:
> +		pr_err("SELinux:  out of range capability %d\n", cap);
> +		return -EINVAL;
> +	}
> +
> +	return avc_has_perm(current_sid(), bpfsec->grantor_sid, sclass, av, NULL);
> +}
>   #endif
>   
>   struct lsm_blob_sizes selinux_blob_sizes __ro_after_init = {
> @@ -7599,6 +7710,8 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = {
>   	LSM_HOOK_INIT(bpf_map_create, selinux_bpf_map_create),
>   	LSM_HOOK_INIT(bpf_prog_load, selinux_bpf_prog_load),
>   	LSM_HOOK_INIT(bpf_token_create, selinux_bpf_token_create),
> +	LSM_HOOK_INIT(bpf_token_cmd, selinux_bpf_token_cmd),
> +	LSM_HOOK_INIT(bpf_token_capable, selinux_bpf_token_capable),
>   #endif
>   #ifdef CONFIG_PERF_EVENTS
>   	LSM_HOOK_INIT(perf_event_alloc, selinux_perf_event_alloc),
> diff --git a/security/selinux/include/classmap.h b/security/selinux/include/classmap.h
> index 5665aa5e7853..a6ed864af64c 100644
> --- a/security/selinux/include/classmap.h
> +++ b/security/selinux/include/classmap.h
> @@ -171,7 +171,7 @@ const struct security_class_mapping secclass_map[] = {
>   	{ "infiniband_endport", { "manage_subnet", NULL } },
>   	{ "bpf",
>   	  { "map_create", "map_read", "map_write", "prog_load", "prog_run",
> -	    NULL } },
> +	    "map_create_as", "prog_load_as", NULL } },
>   	{ "xdp_socket", { COMMON_SOCK_PERMS, NULL } },
>   	{ "mctp_socket", { COMMON_SOCK_PERMS, NULL } },
>   	{ "perf_event",
> diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h
> index 1d7ac59015a1..c1b223e012b4 100644
> --- a/security/selinux/include/objsec.h
> +++ b/security/selinux/include/objsec.h
> @@ -87,6 +87,7 @@ struct superblock_security_struct {
>   	u32 sid; /* SID of file system superblock */
>   	u32 def_sid; /* default SID for labeling */
>   	u32 mntpoint_sid; /* SECURITY_FS_USE_MNTPOINT context for files */
> +	u32 creator_sid; /* SID of privileged process */
>   	unsigned short behavior; /* labeling behavior */
>   	unsigned short flags; /* which mount options were specified */
>   	struct mutex lock;
> @@ -164,6 +165,8 @@ struct pkey_security_struct {
>   
>   struct bpf_security_struct {
>   	u32 sid; /* SID of bpf obj creator */
> +	u32 perms; /* allowed AV permissions, e.g. BPF__MAP_CREATE, for requested bpf commands */
> +	u32 grantor_sid; /* SID of privileged process used for permission check */
>   };
>   
>   struct perf_event_security_struct {
> diff --git a/security/selinux/include/policycap.h b/security/selinux/include/policycap.h
> index 7405154e6c42..cde6aaf442cd 100644
> --- a/security/selinux/include/policycap.h
> +++ b/security/selinux/include/policycap.h
> @@ -17,6 +17,7 @@ enum {
>   	POLICYDB_CAP_NETLINK_XPERM,
>   	POLICYDB_CAP_NETIF_WILDCARD,
>   	POLICYDB_CAP_GENFS_SECLABEL_WILDCARD,
> +	POLICYDB_CAP_BPF_TOKEN_PERMS,
>   	__POLICYDB_CAP_MAX
>   };
>   #define POLICYDB_CAP_MAX (__POLICYDB_CAP_MAX - 1)
> diff --git a/security/selinux/include/policycap_names.h b/security/selinux/include/policycap_names.h
> index d8962fcf2ff9..cd5e73f992ea 100644
> --- a/security/selinux/include/policycap_names.h
> +++ b/security/selinux/include/policycap_names.h
> @@ -20,6 +20,7 @@ const char *const selinux_policycap_names[__POLICYDB_CAP_MAX] = {
>   	"netlink_xperm",
>   	"netif_wildcard",
>   	"genfs_seclabel_wildcard",
> +	"bpf_token_perms",
>   };
>   /* clang-format on */
>   
> diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h
> index 8201e6a3ac0f..d3832e4ad4fb 100644
> --- a/security/selinux/include/security.h
> +++ b/security/selinux/include/security.h
> @@ -209,6 +209,12 @@ static inline bool selinux_policycap_netif_wildcard(void)
>   		selinux_state.policycap[POLICYDB_CAP_NETIF_WILDCARD]);
>   }
>   
> +static inline bool selinux_policycap_bpf_token_perms(void)
> +{
> +	return READ_ONCE(
> +		selinux_state.policycap[POLICYDB_CAP_BPF_TOKEN_PERMS]);
> +}
> +
>   struct selinux_policy_convert_data;
>   
>   struct selinux_load_state {



  reply	other threads:[~2025-12-05  3:12 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-12-05  2:42 [PATCH v7] SELinux: Add support for BPF token access control Eric Suen
2025-12-05  3:12 ` Eric Suen [this message]
2025-12-10 17:36   ` Daniel Durning
2026-01-10  3:34     ` Paul Moore
2026-01-12 14:13       ` Stephen Smalley
2026-01-13 16:51         ` Paul Moore
2026-01-13 20:42 ` Paul Moore
2026-01-14 17:55   ` Christian Göttsche
2026-01-14 20:59     ` Paul Moore
2026-01-21 20:40       ` Eric Suen

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=caeaa280-ffa3-415f-bf39-340f66ee45fa@linux.microsoft.com \
    --to=ericsu@linux.microsoft.com \
    --cc=danieldurning.work@gmail.com \
    --cc=omosnace@redhat.com \
    --cc=paul@paul-moore.com \
    --cc=selinux@vger.kernel.org \
    --cc=stephen.smalley.work@gmail.com \
    /path/to/YOUR_REPLY

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

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