]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
selinux: add support for BPF token access control
authorEric Suen <ericsu@linux.microsoft.com>
Fri, 5 Dec 2025 02:42:59 +0000 (18:42 -0800)
committerPaul Moore <paul@paul-moore.com>
Tue, 13 Jan 2026 20:42:37 +0000 (15:42 -0500)
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>
Tested-by: Daniel Durning <danieldurning.work@gmail.com>
Reviewed-by: Daniel Durning <danieldurning.work@gmail.com>
[PM: merge fuzz, subject tweaks, whitespace tweaks, line length tweaks]
Signed-off-by: Paul Moore <paul@paul-moore.com>
security/selinux/hooks.c
security/selinux/include/classmap.h
security/selinux/include/objsec.h
security/selinux/include/policycap.h
security/selinux/include/policycap_names.h
security/selinux/include/security.h

index bb9bf848df4ee3193bbe0a61705ef1bec53180e5..c7c19ceeeb2f165f355365e935dd84841dff0f84 100644 (file)
@@ -737,6 +737,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;
 
@@ -908,6 +910,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, "
@@ -967,6 +971,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) {
@@ -2586,6 +2591,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;
 }
@@ -7043,6 +7049,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,
@@ -7124,38 +7133,143 @@ 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 = selinux_bpf_map_security(map);
        bpfsec->sid = current_sid();
 
-       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 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 = selinux_bpf_prog_security(prog);
        bpfsec->sid = current_sid();
 
-       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 int selinux_bpf_token_create(struct bpf_token *token, union bpf_attr *attr,
+#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 = selinux_bpf_token_security(token);
        bpfsec->sid = current_sid();
+       bpfsec->grantor_sid = sid;
+
+       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;
+}
+
+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
 
 #ifdef CONFIG_PERF_EVENTS
@@ -7590,6 +7704,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),
index 3ec85142771fcc1da02cff7d655572fa8400780a..90cb61b16425636e247938941537352fa810f65e 100644 (file)
@@ -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",
index 8fc3de5234acd8b338c0b1bdb9ead1def1088a21..5bddd28ea5cb89401deeb867687b1d4925aef2b7 100644 (file)
@@ -92,6 +92,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;
@@ -169,6 +170,8 @@ struct pkey_security_struct {
 
 struct bpf_security_struct {
        u32 sid; /* SID of bpf obj creator */
+       u32 perms; /* permissions for allowed bpf token commands */
+       u32 grantor_sid; /* SID of token grantor */
 };
 
 struct perf_event_security_struct {
index 231d02227e593a6d891cdc2fd5ced5b9a4b03506..dbf39358ae6af11cfb45fbb07069b3586ea67f8f 100644 (file)
@@ -19,6 +19,7 @@ enum {
        POLICYDB_CAP_GENFS_SECLABEL_WILDCARD,
        POLICYDB_CAP_FUNCTIONFS_SECLABEL,
        POLICYDB_CAP_MEMFD_CLASS,
+       POLICYDB_CAP_BPF_TOKEN_PERMS,
        __POLICYDB_CAP_MAX
 };
 #define POLICYDB_CAP_MAX (__POLICYDB_CAP_MAX - 1)
index 454dab37bda3fcac3eaa34b288e4348686ba0334..6e2b808e12e89fd035681b7614680cc305565378 100644 (file)
@@ -22,6 +22,7 @@ const char *const selinux_policycap_names[__POLICYDB_CAP_MAX] = {
        "genfs_seclabel_wildcard",
        "functionfs_seclabel",
        "memfd_class",
+       "bpf_token_perms",
 };
 /* clang-format on */
 
index 5d1dad8058b1456a5abebb539dce3b9c8a4ccd11..d1f16d7f684de31a64ae339c3fea0ad53264b6e2 100644 (file)
@@ -214,6 +214,12 @@ static inline bool selinux_policycap_memfd_class(void)
        return READ_ONCE(selinux_state.policycap[POLICYDB_CAP_MEMFD_CLASS]);
 }
 
+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 {