Skip to content

Commit 5473a72

Browse files
Eric Suenpcmoore
authored andcommitted
selinux: add support for BPF token access control
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>
1 parent 27a7cef commit 5473a72

File tree

6 files changed

+131
-4
lines changed

6 files changed

+131
-4
lines changed

security/selinux/hooks.c

Lines changed: 119 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,8 @@ static int selinux_set_mnt_opts(struct super_block *sb,
737737
goto out;
738738
}
739739

740+
sbsec->creator_sid = current_sid();
741+
740742
if (strcmp(sb->s_type->name, "proc") == 0)
741743
sbsec->flags |= SE_SBPROC | SE_SBGENFS;
742744

@@ -908,6 +910,8 @@ static int selinux_cmp_sb_context(const struct super_block *oldsb,
908910
if (oldroot->sid != newroot->sid)
909911
goto mismatch;
910912
}
913+
if (old->creator_sid != new->creator_sid)
914+
goto mismatch;
911915
return 0;
912916
mismatch:
913917
pr_warn("SELinux: mount invalid. Same superblock, "
@@ -967,6 +971,7 @@ static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb,
967971
newsbsec->sid = oldsbsec->sid;
968972
newsbsec->def_sid = oldsbsec->def_sid;
969973
newsbsec->behavior = oldsbsec->behavior;
974+
newsbsec->creator_sid = oldsbsec->creator_sid;
970975

971976
if (newsbsec->behavior == SECURITY_FS_USE_NATIVE &&
972977
!(kern_flags & SECURITY_LSM_NATIVE_LABELS) && !set_context) {
@@ -2586,6 +2591,7 @@ static int selinux_sb_alloc_security(struct super_block *sb)
25862591
sbsec->sid = SECINITSID_UNLABELED;
25872592
sbsec->def_sid = SECINITSID_FILE;
25882593
sbsec->mntpoint_sid = SECINITSID_UNLABELED;
2594+
sbsec->creator_sid = SECINITSID_UNLABELED;
25892595

25902596
return 0;
25912597
}
@@ -7043,6 +7049,9 @@ static int selinux_bpf(int cmd, union bpf_attr *attr,
70437049
u32 sid = current_sid();
70447050
int ret;
70457051

7052+
if (selinux_policycap_bpf_token_perms())
7053+
return 0;
7054+
70467055
switch (cmd) {
70477056
case BPF_MAP_CREATE:
70487057
ret = avc_has_perm(sid, sid, SECCLASS_BPF, BPF__MAP_CREATE,
@@ -7124,38 +7133,143 @@ static int selinux_bpf_prog(struct bpf_prog *prog)
71247133
BPF__PROG_RUN, NULL);
71257134
}
71267135

7136+
static u32 selinux_bpffs_creator_sid(u32 fd)
7137+
{
7138+
struct path path;
7139+
struct super_block *sb;
7140+
struct superblock_security_struct *sbsec;
7141+
7142+
CLASS(fd, f)(fd);
7143+
7144+
if (fd_empty(f))
7145+
return SECSID_NULL;
7146+
7147+
path = fd_file(f)->f_path;
7148+
sb = path.dentry->d_sb;
7149+
sbsec = selinux_superblock(sb);
7150+
7151+
return sbsec->creator_sid;
7152+
}
7153+
71277154
static int selinux_bpf_map_create(struct bpf_map *map, union bpf_attr *attr,
71287155
struct bpf_token *token, bool kernel)
71297156
{
71307157
struct bpf_security_struct *bpfsec;
7158+
u32 ssid;
71317159

71327160
bpfsec = selinux_bpf_map_security(map);
71337161
bpfsec->sid = current_sid();
71347162

7135-
return 0;
7163+
if (!token)
7164+
ssid = bpfsec->sid;
7165+
else
7166+
ssid = selinux_bpffs_creator_sid(attr->map_token_fd);
7167+
7168+
return avc_has_perm(ssid, bpfsec->sid, SECCLASS_BPF, BPF__MAP_CREATE,
7169+
NULL);
71367170
}
71377171

71387172
static int selinux_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr,
71397173
struct bpf_token *token, bool kernel)
71407174
{
71417175
struct bpf_security_struct *bpfsec;
7176+
u32 ssid;
71427177

71437178
bpfsec = selinux_bpf_prog_security(prog);
71447179
bpfsec->sid = current_sid();
71457180

7146-
return 0;
7181+
if (!token)
7182+
ssid = bpfsec->sid;
7183+
else
7184+
ssid = selinux_bpffs_creator_sid(attr->prog_token_fd);
7185+
7186+
return avc_has_perm(ssid, bpfsec->sid, SECCLASS_BPF, BPF__PROG_LOAD,
7187+
NULL);
71477188
}
71487189

7149-
static int selinux_bpf_token_create(struct bpf_token *token, union bpf_attr *attr,
7190+
#define bpf_token_cmd(T, C) \
7191+
((T)->allowed_cmds & (1ULL << (C)))
7192+
7193+
static int selinux_bpf_token_create(struct bpf_token *token,
7194+
union bpf_attr *attr,
71507195
const struct path *path)
71517196
{
71527197
struct bpf_security_struct *bpfsec;
7198+
u32 sid = selinux_bpffs_creator_sid(attr->token_create.bpffs_fd);
7199+
int err;
71537200

71547201
bpfsec = selinux_bpf_token_security(token);
71557202
bpfsec->sid = current_sid();
7203+
bpfsec->grantor_sid = sid;
7204+
7205+
bpfsec->perms = 0;
7206+
/**
7207+
* 'token->allowed_cmds' is a bit mask of allowed commands
7208+
* Convert the BPF command enum to a bitmask representing its position
7209+
* in the allowed_cmds bitmap.
7210+
*/
7211+
if (bpf_token_cmd(token, BPF_MAP_CREATE)) {
7212+
err = avc_has_perm(bpfsec->sid, sid, SECCLASS_BPF,
7213+
BPF__MAP_CREATE_AS, NULL);
7214+
if (err)
7215+
return err;
7216+
bpfsec->perms |= BPF__MAP_CREATE;
7217+
}
7218+
if (bpf_token_cmd(token, BPF_PROG_LOAD)) {
7219+
err = avc_has_perm(bpfsec->sid, sid, SECCLASS_BPF,
7220+
BPF__PROG_LOAD_AS, NULL);
7221+
if (err)
7222+
return err;
7223+
bpfsec->perms |= BPF__PROG_LOAD;
7224+
}
7225+
7226+
return 0;
7227+
}
7228+
7229+
static int selinux_bpf_token_cmd(const struct bpf_token *token,
7230+
enum bpf_cmd cmd)
7231+
{
7232+
struct bpf_security_struct *bpfsec;
7233+
7234+
bpfsec = token->security;
7235+
switch (cmd) {
7236+
case BPF_MAP_CREATE:
7237+
if (!(bpfsec->perms & BPF__MAP_CREATE))
7238+
return -EACCES;
7239+
break;
7240+
case BPF_PROG_LOAD:
7241+
if (!(bpfsec->perms & BPF__PROG_LOAD))
7242+
return -EACCES;
7243+
break;
7244+
default:
7245+
break;
7246+
}
71567247

71577248
return 0;
71587249
}
7250+
7251+
static int selinux_bpf_token_capable(const struct bpf_token *token, int cap)
7252+
{
7253+
u16 sclass;
7254+
struct bpf_security_struct *bpfsec = token->security;
7255+
bool initns = (token->userns == &init_user_ns);
7256+
u32 av = CAP_TO_MASK(cap);
7257+
7258+
switch (CAP_TO_INDEX(cap)) {
7259+
case 0:
7260+
sclass = initns ? SECCLASS_CAPABILITY : SECCLASS_CAP_USERNS;
7261+
break;
7262+
case 1:
7263+
sclass = initns ? SECCLASS_CAPABILITY : SECCLASS_CAP2_USERNS;
7264+
break;
7265+
default:
7266+
pr_err("SELinux: out of range capability %d\n", cap);
7267+
return -EINVAL;
7268+
}
7269+
7270+
return avc_has_perm(current_sid(), bpfsec->grantor_sid, sclass, av,
7271+
NULL);
7272+
}
71597273
#endif
71607274

71617275
#ifdef CONFIG_PERF_EVENTS
@@ -7590,6 +7704,8 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = {
75907704
LSM_HOOK_INIT(bpf_map_create, selinux_bpf_map_create),
75917705
LSM_HOOK_INIT(bpf_prog_load, selinux_bpf_prog_load),
75927706
LSM_HOOK_INIT(bpf_token_create, selinux_bpf_token_create),
7707+
LSM_HOOK_INIT(bpf_token_cmd, selinux_bpf_token_cmd),
7708+
LSM_HOOK_INIT(bpf_token_capable, selinux_bpf_token_capable),
75937709
#endif
75947710
#ifdef CONFIG_PERF_EVENTS
75957711
LSM_HOOK_INIT(perf_event_alloc, selinux_perf_event_alloc),

security/selinux/include/classmap.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ const struct security_class_mapping secclass_map[] = {
171171
{ "infiniband_endport", { "manage_subnet", NULL } },
172172
{ "bpf",
173173
{ "map_create", "map_read", "map_write", "prog_load", "prog_run",
174-
NULL } },
174+
"map_create_as", "prog_load_as", NULL } },
175175
{ "xdp_socket", { COMMON_SOCK_PERMS, NULL } },
176176
{ "mctp_socket", { COMMON_SOCK_PERMS, NULL } },
177177
{ "perf_event",

security/selinux/include/objsec.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ struct superblock_security_struct {
9292
u32 sid; /* SID of file system superblock */
9393
u32 def_sid; /* default SID for labeling */
9494
u32 mntpoint_sid; /* SECURITY_FS_USE_MNTPOINT context for files */
95+
u32 creator_sid; /* SID of privileged process */
9596
unsigned short behavior; /* labeling behavior */
9697
unsigned short flags; /* which mount options were specified */
9798
struct mutex lock;
@@ -169,6 +170,8 @@ struct pkey_security_struct {
169170

170171
struct bpf_security_struct {
171172
u32 sid; /* SID of bpf obj creator */
173+
u32 perms; /* permissions for allowed bpf token commands */
174+
u32 grantor_sid; /* SID of token grantor */
172175
};
173176

174177
struct perf_event_security_struct {

security/selinux/include/policycap.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ enum {
1919
POLICYDB_CAP_GENFS_SECLABEL_WILDCARD,
2020
POLICYDB_CAP_FUNCTIONFS_SECLABEL,
2121
POLICYDB_CAP_MEMFD_CLASS,
22+
POLICYDB_CAP_BPF_TOKEN_PERMS,
2223
__POLICYDB_CAP_MAX
2324
};
2425
#define POLICYDB_CAP_MAX (__POLICYDB_CAP_MAX - 1)

security/selinux/include/policycap_names.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const char *const selinux_policycap_names[__POLICYDB_CAP_MAX] = {
2222
"genfs_seclabel_wildcard",
2323
"functionfs_seclabel",
2424
"memfd_class",
25+
"bpf_token_perms",
2526
};
2627
/* clang-format on */
2728

security/selinux/include/security.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,12 @@ static inline bool selinux_policycap_memfd_class(void)
214214
return READ_ONCE(selinux_state.policycap[POLICYDB_CAP_MEMFD_CLASS]);
215215
}
216216

217+
static inline bool selinux_policycap_bpf_token_perms(void)
218+
{
219+
return READ_ONCE(
220+
selinux_state.policycap[POLICYDB_CAP_BPF_TOKEN_PERMS]);
221+
}
222+
217223
struct selinux_policy_convert_data;
218224

219225
struct selinux_load_state {

0 commit comments

Comments
 (0)