mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
bpf, lsm: Let BPF LSM provide xattrs at inode creation
Many in-kernel LSMs (SELinux, Smack, IMA) store security labels in extended attributes. For these LSMs, atomic labeling during inode creation is critical: if the inode becomes accessible before its xattr is set, it is briefly unlabeled, which can disrupt LSMs making policy decisions based on file labels. Existing LSMs solve this by setting xattrs in the inode_init_security hook, which runs before the inode becomes accessible. BPF LSM programs currently lack this capability because the hook uses an output parameter (xattr_count) that BPF programs cannot write to, and existing kfuncs like bpf_set_dentry_xattr() require a dentry that isn't available until after the inode is accessible. Add a bpf_inode_init_xattr() kfunc that takes the hook's own xattrs and xattr_count arguments, passed through from the program's context, and claims a slot via lsm_get_xattr_slot() on the program's behalf. The xattr_count output argument is exposed to inode_init_security programs as trusted read-only memory, so programs can pass it to the kfunc but cannot modify the count themselves. Reserve BPF_LSM_INODE_INIT_XATTRS slots in bpf_lsm_blob_sizes the way every other xattr-providing LSM does, for the life of the kernel. The framework keys the collection off the reserved slot count, so a kernel built with CONFIG_BPF_LSM=y now allocates the xattr array on every inode creation, whether or not a program sits on the hook. Give the hook a strong prototype in bpf_lsm_proto.c so that its qstr and xattrs arguments are marked __nullable. Both can be NULL for some callers. Without the annotation the verifier otherwise hands the program a trusted non-NULL pointer which it dereferences. Also, keep the hook out of the sleepable set. inode_init_security runs inside the transaction creating the inode, with a journal handle held on ext4 and btrfs and the parent's i_rwsem down, which is why everything on the path allocates GFP_NOFS. Co-developed-by: Daniel Borkmann <daniel@iogearbox.net> Signed-off-by: David Windsor <dwindsor@gmail.com> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net> Signed-off-by: Alexei Starovoitov <ast@kernel.org> Link: https://patch.msgid.link/20260915150739.284189-5-daniel@iogearbox.net
This commit is contained in:
parent
806fc431e0
commit
ff0d1c0915
|
|
@ -11,6 +11,7 @@
|
|||
#include <linux/fsnotify.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/kernfs.h>
|
||||
#include <linux/lsm_hooks.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/net.h>
|
||||
#include <linux/xattr.h>
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,13 @@ extern bool bpf_lsm_initialized __ro_after_init;
|
|||
#include <linux/lsm_hook_defs.h>
|
||||
#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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
*/
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include <linux/xattr.h>
|
||||
#include <linux/bpf_lsm.h>
|
||||
|
||||
/*
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) = {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user