diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index d053ce562370..feda34b18d83 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -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) { @@ -1654,7 +1659,6 @@ static int cred_has_capability(const struct cred *cred, break; default: pr_err("SELinux: out of range capability %d\n", cap); - BUG(); return -EINVAL; } @@ -2586,6 +2590,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 +7048,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,60 +7132,144 @@ 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; } -#endif -struct lsm_blob_sizes selinux_blob_sizes __ro_after_init = { - .lbs_cred = sizeof(struct cred_security_struct), - .lbs_task = sizeof(struct task_security_struct), - .lbs_file = sizeof(struct file_security_struct), - .lbs_inode = sizeof(struct inode_security_struct), - .lbs_ipc = sizeof(struct ipc_security_struct), - .lbs_key = sizeof(struct key_security_struct), - .lbs_msg_msg = sizeof(struct msg_security_struct), -#ifdef CONFIG_PERF_EVENTS - .lbs_perf_event = sizeof(struct perf_event_security_struct), +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_CAPABILITY2 : 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 - .lbs_sock = sizeof(struct sk_security_struct), - .lbs_superblock = sizeof(struct superblock_security_struct), - .lbs_xattr_count = SELINUX_INODE_INIT_XATTRS, - .lbs_tun_dev = sizeof(struct tun_security_struct), - .lbs_ib = sizeof(struct ib_security_struct), - .lbs_bpf_map = sizeof(struct bpf_security_struct), - .lbs_bpf_prog = sizeof(struct bpf_security_struct), - .lbs_bpf_token = sizeof(struct bpf_security_struct), -}; #ifdef CONFIG_PERF_EVENTS static int selinux_perf_event_open(int type) @@ -7297,6 +7389,27 @@ static const struct lsm_id selinux_lsmid = { .id = LSM_ID_SELINUX, }; +struct lsm_blob_sizes selinux_blob_sizes __ro_after_init = { + .lbs_cred = sizeof(struct cred_security_struct), + .lbs_task = sizeof(struct task_security_struct), + .lbs_file = sizeof(struct file_security_struct), + .lbs_inode = sizeof(struct inode_security_struct), + .lbs_ipc = sizeof(struct ipc_security_struct), + .lbs_key = sizeof(struct key_security_struct), + .lbs_msg_msg = sizeof(struct msg_security_struct), +#ifdef CONFIG_PERF_EVENTS + .lbs_perf_event = sizeof(struct perf_event_security_struct), +#endif + .lbs_sock = sizeof(struct sk_security_struct), + .lbs_superblock = sizeof(struct superblock_security_struct), + .lbs_xattr_count = SELINUX_INODE_INIT_XATTRS, + .lbs_tun_dev = sizeof(struct tun_security_struct), + .lbs_ib = sizeof(struct ib_security_struct), + .lbs_bpf_map = sizeof(struct bpf_security_struct), + .lbs_bpf_prog = sizeof(struct bpf_security_struct), + .lbs_bpf_token = sizeof(struct bpf_security_struct), +}; + /* * IMPORTANT NOTE: When adding new hooks, please be careful to keep this order: * 1. any hooks that don't belong to (2.) or (3.) below, @@ -7590,6 +7703,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 3ec85142771f..90cb61b16425 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 8fc3de5234ac..5bddd28ea5cb 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h @@ -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 { diff --git a/security/selinux/include/policycap.h b/security/selinux/include/policycap.h index 231d02227e59..dbf39358ae6a 100644 --- a/security/selinux/include/policycap.h +++ b/security/selinux/include/policycap.h @@ -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) diff --git a/security/selinux/include/policycap_names.h b/security/selinux/include/policycap_names.h index 454dab37bda3..6e2b808e12e8 100644 --- a/security/selinux/include/policycap_names.h +++ b/security/selinux/include/policycap_names.h @@ -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 */ diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index 5d1dad8058b1..d1f16d7f684d 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -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 {