diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c index 6cb877267978..c51c3ae8063b 100644 --- a/fs/bpf_fs_kfuncs.c +++ b/fs/bpf_fs_kfuncs.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -328,6 +329,89 @@ __bpf_kfunc int bpf_remove_dentry_xattr(struct dentry *dentry, const char *name_ return ret; } +static int bpf_inode_init_xattrs_claimed(const struct xattr *xattrs, int xattr_count) +{ + const size_t suffix_len = sizeof(XATTR_BPF_LSM_SUFFIX) - 1; + int i, claimed = 0; + + for (i = 0; i < xattr_count; i++) { + const char *name = xattrs[i].name; + + if (name && !strncmp(name, XATTR_BPF_LSM_SUFFIX, suffix_len)) + claimed++; + } + return claimed; +} + +/** + * bpf_inode_init_xattr - attach a xattr to an inode that is being created + * @xattrs: xattr array the inode_init_security hook was handed + * @xattr_count__ctx_out: xattr count the inode_init_security hook was handed + * @name__str: name of the xattr + * @value_p: xattr value + * + * Claim one of the slots the BPF LSM reserved in the xattr array, so that + * the xattr is written out as part of the transaction creating the inode. + * + * For security reasons, only *name__str* with prefix "security.bpf." is + * allowed. The slot stores the name without that "security." prefix, which + * the filesystem's initxattrs() callback puts back. + * + * At most BPF_LSM_INODE_INIT_XATTRS xattrs can be attached to one inode, + * matching the number of slots the BPF LSM reserves. + * + * Return: 0 on success, a negative value on error. + */ +__bpf_kfunc int bpf_inode_init_xattr(struct xattr *xattrs, + int *xattr_count__ctx_out, + const char *name__str, + const struct bpf_dynptr *value_p) +{ + const struct bpf_dynptr_kern *value_ptr = (struct bpf_dynptr_kern *)value_p; + int *xattr_count = xattr_count__ctx_out; + const char *suffix; + struct xattr *slot; + const void *value; + size_t name_len; + u32 value_len; + char *buf; + + if (!match_security_bpf_prefix(name__str)) + return -EPERM; + if (bpf_inode_init_xattrs_claimed(xattrs, *xattr_count) >= + BPF_LSM_INODE_INIT_XATTRS) + return -ENOSPC; + + suffix = name__str + XATTR_SECURITY_PREFIX_LEN; + name_len = strlen(suffix); + if (name_len <= sizeof(XATTR_BPF_LSM_SUFFIX) - 1 || + name_len > XATTR_NAME_MAX - XATTR_SECURITY_PREFIX_LEN) + return -EINVAL; + + value_len = __bpf_dynptr_size(value_ptr); + value = __bpf_dynptr_data(value_ptr, value_len); + if (!value) + return -EINVAL; + if (value_len > XATTR_SIZE_MAX) + return -E2BIG; + + buf = kmalloc(value_len + name_len + 1, GFP_NOWAIT | __GFP_NOWARN); + if (!buf) + return -ENOMEM; + memcpy(buf, value, value_len); + memcpy(buf + value_len, suffix, name_len + 1); + + slot = lsm_get_xattr_slot(xattrs, xattr_count); + if (!slot) { + kfree(buf); + return -ENOSPC; + } + slot->value = buf; + slot->value_len = value_len; + slot->name = buf + value_len; + return 0; +} + #ifdef CONFIG_CGROUPS /** * bpf_cgroup_read_xattr - read xattr of a cgroup's node in cgroupfs @@ -425,6 +509,7 @@ 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_data_inode, KF_SLEEPABLE | KF_RET_NULL) +BTF_ID_FLAGS(func, bpf_inode_init_xattr) #ifdef CONFIG_NET BTF_ID_FLAGS(func, bpf_sock_read_xattr, KF_RCU) #endif @@ -436,10 +521,24 @@ BTF_ID(func, bpf_set_dentry_xattr) BTF_ID(func, bpf_remove_dentry_xattr) BTF_SET_END(bpf_fs_kfunc_lsm_only_ids) +BTF_ID_LIST_SINGLE(bpf_inode_init_xattr_ids, func, bpf_inode_init_xattr) + +BTF_SET_START(bpf_inode_init_xattr_hooks) +BTF_ID(func, bpf_lsm_inode_init_security) +BTF_SET_END(bpf_inode_init_xattr_hooks) + 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)) return 0; + if (kfunc_id == bpf_inode_init_xattr_ids[0]) { + if (prog->type != BPF_PROG_TYPE_LSM || + prog->expected_attach_type != BPF_LSM_MAC || + !btf_id_set_contains(&bpf_inode_init_xattr_hooks, + prog->aux->attach_btf_id)) + return -EACCES; + return 0; + } if (prog->type == BPF_PROG_TYPE_LSM) return 0; if (prog->type != BPF_PROG_TYPE_STRUCT_OPS) diff --git a/include/linux/bpf_lsm.h b/include/linux/bpf_lsm.h index dda272d78f01..eb4a38eef87e 100644 --- a/include/linux/bpf_lsm.h +++ b/include/linux/bpf_lsm.h @@ -21,6 +21,13 @@ extern bool bpf_lsm_initialized __ro_after_init; #include #undef LSM_HOOK +/* + * Number of xattr slots the BPF LSM reserves in the array handed to + * security_inode_init_security(), i.e. the maximum number of labels + * a policy may attach to an inode while it is being created. + */ +#define BPF_LSM_INODE_INIT_XATTRS 2 + struct bpf_storage_blob { struct bpf_local_storage __rcu *storage; }; @@ -28,7 +35,7 @@ struct bpf_storage_blob { extern struct lsm_blob_sizes bpf_lsm_blob_sizes; int bpf_lsm_verify_prog(struct bpf_verifier_log *vlog, - const struct bpf_prog *prog); + struct bpf_prog *prog); bool bpf_lsm_is_sleepable_hook(u32 btf_id); bool bpf_lsm_is_trusted(const struct bpf_prog *prog); @@ -71,7 +78,7 @@ static inline bool bpf_lsm_is_trusted(const struct bpf_prog *prog) } static inline int bpf_lsm_verify_prog(struct bpf_verifier_log *vlog, - const struct bpf_prog *prog) + struct bpf_prog *prog) { return -EOPNOTSUPP; } diff --git a/kernel/bpf/bpf_lsm.c b/kernel/bpf/bpf_lsm.c index 2660a89fc8d7..f58dce888ff4 100644 --- a/kernel/bpf/bpf_lsm.c +++ b/kernel/bpf/bpf_lsm.c @@ -116,8 +116,11 @@ void bpf_lsm_find_cgroup_shim(const struct bpf_prog *prog, } #endif +BTF_ID_LIST_SINGLE(bpf_lsm_inode_init_security_btf_id, func, + bpf_lsm_inode_init_security) + int bpf_lsm_verify_prog(struct bpf_verifier_log *vlog, - const struct bpf_prog *prog) + struct bpf_prog *prog) { u32 btf_id = prog->aux->attach_btf_id; const char *func_name = prog->aux->attach_func_name; @@ -140,6 +143,23 @@ int bpf_lsm_verify_prog(struct bpf_verifier_log *vlog, return -EINVAL; } + if (btf_id == bpf_lsm_inode_init_security_btf_id[0]) { + /* + * inode, dir, qstr, xattrs, xattr_count + * + * The trusted pointer this hands the program is only as + * trustworthy as the context it is loaded from, which + * lsm_verifier_ops keeps read-only. + */ + static const struct bpf_ctx_arg_aux xattr_count_arg_info = { + .offset = 4 * sizeof(u64), + .reg_type = PTR_TO_MEM | MEM_RDONLY | PTR_TRUSTED, + .mem_size = sizeof(int), + }; + + return bpf_prog_ctx_arg_info_init(prog, &xattr_count_arg_info, 1); + } + return 0; } diff --git a/kernel/bpf/bpf_lsm_proto.c b/kernel/bpf/bpf_lsm_proto.c index 44a54fd8045e..4a776df76cbb 100644 --- a/kernel/bpf/bpf_lsm_proto.c +++ b/kernel/bpf/bpf_lsm_proto.c @@ -4,6 +4,7 @@ */ #include +#include #include /* @@ -17,3 +18,17 @@ int bpf_lsm_mmap_file(struct file *file__nullable, unsigned long reqprot, { return 0; } + +/* + * Strong definition of the inode_init_security() BPF LSM hook. Both the + * qstr and the xattr array are NULL for some callers, so the __nullable + * suffix marks it as PTR_MAYBE_NULL. BPF LSM programs have to check before + * dereferencing them or handing them to bpf_inode_init_xattr(). + */ +int bpf_lsm_inode_init_security(struct inode *inode, struct inode *dir, + const struct qstr *qstr__nullable, + struct xattr *xattrs__nullable, + int *xattr_count) +{ + return -EOPNOTSUPP; +} diff --git a/security/bpf/hooks.c b/security/bpf/hooks.c index 7b98f5d1e2be..8f8c3de3035f 100644 --- a/security/bpf/hooks.c +++ b/security/bpf/hooks.c @@ -33,6 +33,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) = {