mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-09-18 22:09:30 +02:00
Pull vfs bpf access updates from Christian Brauner: "This adds a bpf_sock_read_xattr() kfunc so a BPF LSM program can read a user.* extended attribute from a socket's sockfs inode locklessly. userspace already uses user.* xattrs on sockets to implement socket rate limiting and to tag sockets for other purposes such as a varlink registry. There has been no efficient way for a BPF program to read those labels back. With this a listening socket marked from userspace with fsetxattr() can be read back during bind or connect and acted upon on the connecting socket. That lets userspace mark sockets and later rediscover them or implement policy on them" * tag 'vfs-7.3-rc1.kfunc' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs: selftests/bpf: Add test for bpf_sock_read_xattr() kfunc fs: Add bpf_sock_read_xattr() kfunc to read socket xattrs
504 lines
15 KiB
C
504 lines
15 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/* Copyright (c) 2024 Google LLC. */
|
|
|
|
#include <linux/binfmt_misc.h>
|
|
#include <linux/bpf.h>
|
|
#include <linux/bpf_lsm.h>
|
|
#include <linux/btf.h>
|
|
#include <linux/btf_ids.h>
|
|
#include <linux/dcache.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/fsnotify.h>
|
|
#include <linux/file.h>
|
|
#include <linux/kernfs.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/net.h>
|
|
#include <linux/xattr.h>
|
|
|
|
__bpf_kfunc_start_defs();
|
|
|
|
/**
|
|
* bpf_get_task_exe_file - get a reference on the exe_file struct file member of
|
|
* the mm_struct that is nested within the supplied
|
|
* task_struct
|
|
* @task: task_struct of which the nested mm_struct exe_file member to get a
|
|
* reference on
|
|
*
|
|
* Get a reference on the exe_file struct file member field of the mm_struct
|
|
* nested within the supplied *task*. The referenced file pointer acquired by
|
|
* this BPF kfunc must be released using bpf_put_file(). Failing to call
|
|
* bpf_put_file() on the returned referenced struct file pointer that has been
|
|
* acquired by this BPF kfunc will result in the BPF program being rejected by
|
|
* the BPF verifier.
|
|
*
|
|
* This BPF kfunc may only be called from BPF LSM programs.
|
|
*
|
|
* Internally, this BPF kfunc leans on get_task_exe_file(), such that calling
|
|
* bpf_get_task_exe_file() would be analogous to calling get_task_exe_file()
|
|
* directly in kernel context.
|
|
*
|
|
* Return: A referenced struct file pointer to the exe_file member of the
|
|
* mm_struct that is nested within the supplied *task*. On error, NULL is
|
|
* returned.
|
|
*/
|
|
__bpf_kfunc struct file *bpf_get_task_exe_file(struct task_struct *task)
|
|
{
|
|
return get_task_exe_file(task);
|
|
}
|
|
|
|
/**
|
|
* bpf_put_file - put a reference on the supplied file
|
|
* @file: file to put a reference on
|
|
*
|
|
* Put a reference on the supplied *file*. Only referenced file pointers may be
|
|
* passed to this BPF kfunc. Attempting to pass an unreferenced file pointer, or
|
|
* any other arbitrary pointer for that matter, will result in the BPF program
|
|
* being rejected by the BPF verifier.
|
|
*
|
|
* This BPF kfunc may only be called from BPF LSM programs.
|
|
*/
|
|
__bpf_kfunc void bpf_put_file(struct file *file)
|
|
{
|
|
fput(file);
|
|
}
|
|
|
|
/**
|
|
* bpf_path_d_path - resolve the pathname for the supplied path
|
|
* @path: path to resolve the pathname for
|
|
* @buf: buffer to return the resolved pathname in
|
|
* @buf__sz: length of the supplied buffer
|
|
*
|
|
* Resolve the pathname for the supplied *path* and store it in *buf*. This BPF
|
|
* kfunc is the safer variant of the legacy bpf_d_path() helper and should be
|
|
* used in place of bpf_d_path() whenever possible.
|
|
*
|
|
* This BPF kfunc may only be called from BPF LSM programs.
|
|
*
|
|
* Return: A positive integer corresponding to the length of the resolved
|
|
* pathname in *buf*, including the NUL termination character. On error, a
|
|
* negative integer is returned.
|
|
*/
|
|
__bpf_kfunc int bpf_path_d_path(const struct path *path, char *buf, size_t buf__sz)
|
|
{
|
|
int len;
|
|
char *ret;
|
|
|
|
if (!buf__sz)
|
|
return -EINVAL;
|
|
|
|
ret = d_path(path, buf, buf__sz);
|
|
if (IS_ERR(ret))
|
|
return PTR_ERR(ret);
|
|
|
|
len = buf + buf__sz - ret;
|
|
memmove(buf, ret, len);
|
|
return len;
|
|
}
|
|
|
|
static bool match_security_bpf_prefix(const char *name__str)
|
|
{
|
|
return !strncmp(name__str, XATTR_NAME_BPF_LSM, XATTR_NAME_BPF_LSM_LEN);
|
|
}
|
|
|
|
static int bpf_xattr_read_permission(const char *name, struct inode *inode)
|
|
{
|
|
if (!inode)
|
|
return -EINVAL;
|
|
|
|
/* Allow reading xattr with user. and security.bpf. prefix */
|
|
if (strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN) &&
|
|
!match_security_bpf_prefix(name))
|
|
return -EPERM;
|
|
|
|
return inode_permission(&nop_mnt_idmap, inode, MAY_READ);
|
|
}
|
|
|
|
/**
|
|
* bpf_get_dentry_xattr - get xattr of a dentry
|
|
* @dentry: dentry to get xattr from
|
|
* @name__str: name of the xattr
|
|
* @value_p: output buffer of the xattr value
|
|
*
|
|
* Get xattr *name__str* of *dentry* and store the output in *value_ptr*.
|
|
*
|
|
* For security reasons, only *name__str* with prefixes "user." or
|
|
* "security.bpf." are allowed.
|
|
*
|
|
* Return: length of the xattr value on success, a negative value on error.
|
|
*/
|
|
__bpf_kfunc int bpf_get_dentry_xattr(struct dentry *dentry, const char *name__str,
|
|
struct bpf_dynptr *value_p)
|
|
{
|
|
struct bpf_dynptr_kern *value_ptr = (struct bpf_dynptr_kern *)value_p;
|
|
struct inode *inode = d_inode(dentry);
|
|
u32 value_len;
|
|
void *value;
|
|
int ret;
|
|
|
|
value_len = __bpf_dynptr_size(value_ptr);
|
|
value = __bpf_dynptr_data_rw(value_ptr, value_len);
|
|
if (!value)
|
|
return -EINVAL;
|
|
|
|
ret = bpf_xattr_read_permission(name__str, inode);
|
|
if (ret)
|
|
return ret;
|
|
return __vfs_getxattr(dentry, inode, name__str, value, value_len);
|
|
}
|
|
|
|
/**
|
|
* bpf_get_file_xattr - get xattr of a file
|
|
* @file: file to get xattr from
|
|
* @name__str: name of the xattr
|
|
* @value_p: output buffer of the xattr value
|
|
*
|
|
* Get xattr *name__str* of *file* and store the output in *value_ptr*.
|
|
*
|
|
* For security reasons, only *name__str* with prefixes "user." or
|
|
* "security.bpf." are allowed.
|
|
*
|
|
* Return: length of the xattr value on success, a negative value on error.
|
|
*/
|
|
__bpf_kfunc int bpf_get_file_xattr(struct file *file, const char *name__str,
|
|
struct bpf_dynptr *value_p)
|
|
{
|
|
struct dentry *dentry;
|
|
|
|
dentry = file_dentry(file);
|
|
return bpf_get_dentry_xattr(dentry, name__str, value_p);
|
|
}
|
|
|
|
__bpf_kfunc_end_defs();
|
|
|
|
static int bpf_xattr_write_permission(const char *name, struct inode *inode)
|
|
{
|
|
if (!inode)
|
|
return -EINVAL;
|
|
|
|
/* Only allow setting and removing security.bpf. xattrs */
|
|
if (!match_security_bpf_prefix(name))
|
|
return -EPERM;
|
|
|
|
return inode_permission(&nop_mnt_idmap, inode, MAY_WRITE);
|
|
}
|
|
|
|
/**
|
|
* bpf_set_dentry_xattr_locked - set a xattr of a dentry
|
|
* @dentry: dentry to get xattr from
|
|
* @name__str: name of the xattr
|
|
* @value_p: xattr value
|
|
* @flags: flags to pass into filesystem operations
|
|
*
|
|
* Set xattr *name__str* of *dentry* to the value in *value_ptr*.
|
|
*
|
|
* For security reasons, only *name__str* with prefix "security.bpf."
|
|
* is allowed.
|
|
*
|
|
* The caller already locked dentry->d_inode.
|
|
*
|
|
* Return: 0 on success, a negative value on error.
|
|
*/
|
|
int bpf_set_dentry_xattr_locked(struct dentry *dentry, const char *name__str,
|
|
const struct bpf_dynptr *value_p, int flags)
|
|
{
|
|
|
|
const struct bpf_dynptr_kern *value_ptr = (struct bpf_dynptr_kern *)value_p;
|
|
struct inode *inode = d_inode(dentry);
|
|
const void *value;
|
|
u32 value_len;
|
|
int ret;
|
|
|
|
value_len = __bpf_dynptr_size(value_ptr);
|
|
value = __bpf_dynptr_data(value_ptr, value_len);
|
|
if (!value)
|
|
return -EINVAL;
|
|
|
|
ret = bpf_xattr_write_permission(name__str, inode);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = __vfs_setxattr(&nop_mnt_idmap, dentry, inode, name__str,
|
|
value, value_len, flags);
|
|
if (!ret) {
|
|
fsnotify_xattr(dentry);
|
|
|
|
/* This xattr is set by BPF LSM, so we do not call
|
|
* security_inode_post_setxattr. Otherwise, we would
|
|
* risk deadlocks by calling back to the same kfunc.
|
|
*
|
|
* This is the same as security_inode_setsecurity().
|
|
*/
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* bpf_remove_dentry_xattr_locked - remove a xattr of a dentry
|
|
* @dentry: dentry to get xattr from
|
|
* @name__str: name of the xattr
|
|
*
|
|
* Rmove xattr *name__str* of *dentry*.
|
|
*
|
|
* For security reasons, only *name__str* with prefix "security.bpf."
|
|
* is allowed.
|
|
*
|
|
* The caller already locked dentry->d_inode.
|
|
*
|
|
* Return: 0 on success, a negative value on error.
|
|
*/
|
|
int bpf_remove_dentry_xattr_locked(struct dentry *dentry, const char *name__str)
|
|
{
|
|
struct inode *inode = d_inode(dentry);
|
|
int ret;
|
|
|
|
ret = bpf_xattr_write_permission(name__str, inode);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = __vfs_removexattr(&nop_mnt_idmap, dentry, name__str);
|
|
if (!ret) {
|
|
fsnotify_xattr(dentry);
|
|
|
|
/* This xattr is removed by BPF LSM, so we do not call
|
|
* security_inode_post_removexattr. Otherwise, we would
|
|
* risk deadlocks by calling back to the same kfunc.
|
|
*/
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
__bpf_kfunc_start_defs();
|
|
|
|
/**
|
|
* bpf_set_dentry_xattr - set a xattr of a dentry
|
|
* @dentry: dentry to get xattr from
|
|
* @name__str: name of the xattr
|
|
* @value_p: xattr value
|
|
* @flags: flags to pass into filesystem operations
|
|
*
|
|
* Set xattr *name__str* of *dentry* to the value in *value_ptr*.
|
|
*
|
|
* For security reasons, only *name__str* with prefix "security.bpf."
|
|
* is allowed.
|
|
*
|
|
* The caller has not locked dentry->d_inode.
|
|
*
|
|
* Return: 0 on success, a negative value on error.
|
|
*/
|
|
__bpf_kfunc int bpf_set_dentry_xattr(struct dentry *dentry, const char *name__str,
|
|
const struct bpf_dynptr *value_p, int flags)
|
|
{
|
|
struct inode *inode = d_inode(dentry);
|
|
int ret;
|
|
|
|
if (!inode)
|
|
return -EINVAL;
|
|
|
|
inode_lock(inode);
|
|
ret = bpf_set_dentry_xattr_locked(dentry, name__str, value_p, flags);
|
|
inode_unlock(inode);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* bpf_remove_dentry_xattr - remove a xattr of a dentry
|
|
* @dentry: dentry to get xattr from
|
|
* @name__str: name of the xattr
|
|
*
|
|
* Rmove xattr *name__str* of *dentry*.
|
|
*
|
|
* For security reasons, only *name__str* with prefix "security.bpf."
|
|
* is allowed.
|
|
*
|
|
* The caller has not locked dentry->d_inode.
|
|
*
|
|
* Return: 0 on success, a negative value on error.
|
|
*/
|
|
__bpf_kfunc int bpf_remove_dentry_xattr(struct dentry *dentry, const char *name__str)
|
|
{
|
|
struct inode *inode = d_inode(dentry);
|
|
int ret;
|
|
|
|
if (!inode)
|
|
return -EINVAL;
|
|
|
|
inode_lock(inode);
|
|
ret = bpf_remove_dentry_xattr_locked(dentry, name__str);
|
|
inode_unlock(inode);
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_CGROUPS
|
|
/**
|
|
* bpf_cgroup_read_xattr - read xattr of a cgroup's node in cgroupfs
|
|
* @cgroup: cgroup to get xattr from
|
|
* @name__str: name of the xattr
|
|
* @value_p: output buffer of the xattr value
|
|
*
|
|
* Get xattr *name__str* of *cgroup* and store the output in *value_ptr*.
|
|
*
|
|
* For security reasons, only *name__str* with prefix "user." is allowed.
|
|
*
|
|
* Return: length of the xattr value on success, a negative value on error.
|
|
*/
|
|
__bpf_kfunc int bpf_cgroup_read_xattr(struct cgroup *cgroup, const char *name__str,
|
|
struct bpf_dynptr *value_p)
|
|
{
|
|
struct bpf_dynptr_kern *value_ptr = (struct bpf_dynptr_kern *)value_p;
|
|
u32 value_len;
|
|
void *value;
|
|
|
|
/* Only allow reading "user.*" xattrs */
|
|
if (strncmp(name__str, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN))
|
|
return -EPERM;
|
|
|
|
value_len = __bpf_dynptr_size(value_ptr);
|
|
value = __bpf_dynptr_data_rw(value_ptr, value_len);
|
|
if (!value)
|
|
return -EINVAL;
|
|
|
|
return kernfs_xattr_get(cgroup->kn, name__str, value, value_len);
|
|
}
|
|
#endif /* CONFIG_CGROUPS */
|
|
|
|
#ifdef CONFIG_NET
|
|
/**
|
|
* bpf_sock_read_xattr - read xattr of a socket's inode in sockfs
|
|
* @sock: socket to get xattr from
|
|
* @name__str: name of the xattr
|
|
* @value_p: output buffer of the xattr value
|
|
*
|
|
* Get xattr *name__str* of *sock* and store the output in *value_p*.
|
|
*
|
|
* For security reasons, only *name__str* with prefix "user." is allowed.
|
|
*
|
|
* Return: length of the xattr value on success, a negative value on error.
|
|
*/
|
|
__bpf_kfunc int bpf_sock_read_xattr(struct socket *sock, const char *name__str,
|
|
struct bpf_dynptr *value_p)
|
|
{
|
|
struct bpf_dynptr_kern *value_ptr = (struct bpf_dynptr_kern *)value_p;
|
|
u32 value_len;
|
|
void *value;
|
|
|
|
/* Only allow reading "user.*" xattrs */
|
|
if (strncmp(name__str, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN))
|
|
return -EPERM;
|
|
|
|
value_len = __bpf_dynptr_size(value_ptr);
|
|
value = __bpf_dynptr_data_rw(value_ptr, value_len);
|
|
if (!value)
|
|
return -EINVAL;
|
|
|
|
return sock_read_xattr(sock, name__str, value, value_len);
|
|
}
|
|
#endif /* CONFIG_NET */
|
|
|
|
/**
|
|
* bpf_real_data_inode - get the real inode hosting a file's data
|
|
* @file: file to resolve
|
|
*
|
|
* Resolve @file to the inode that hosts its data. For a regular file on a
|
|
* union/overlay filesystem this is the underlying (upper or lower) inode that
|
|
* stores the data, not the overlay inode.
|
|
*
|
|
* Data resolution only applies to regular files. For a non-regular file (e.g.
|
|
* a device node, fifo or socket) on a union/overlay filesystem the overlay
|
|
* inode itself is returned; for any file on a non-union filesystem the inode
|
|
* attached to @file is returned.
|
|
*
|
|
* Return: The inode hosting @file's data, or NULL.
|
|
*/
|
|
__bpf_kfunc struct inode *bpf_real_data_inode(struct file *file)
|
|
{
|
|
return d_real_inode(file_dentry(file));
|
|
}
|
|
|
|
__bpf_kfunc_end_defs();
|
|
|
|
BTF_KFUNCS_START(bpf_fs_kfunc_set_ids)
|
|
BTF_ID_FLAGS(func, bpf_get_task_exe_file, KF_ACQUIRE | KF_RET_NULL)
|
|
BTF_ID_FLAGS(func, bpf_put_file, KF_RELEASE)
|
|
BTF_ID_FLAGS(func, bpf_path_d_path)
|
|
BTF_ID_FLAGS(func, bpf_get_dentry_xattr, KF_SLEEPABLE)
|
|
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)
|
|
#ifdef CONFIG_NET
|
|
BTF_ID_FLAGS(func, bpf_sock_read_xattr, KF_RCU)
|
|
#endif
|
|
BTF_KFUNCS_END(bpf_fs_kfunc_set_ids)
|
|
|
|
/* Side-effecting kfuncs that stay exclusive to LSM programs. */
|
|
BTF_SET_START(bpf_fs_kfunc_lsm_only_ids)
|
|
BTF_ID(func, bpf_set_dentry_xattr)
|
|
BTF_ID(func, bpf_remove_dentry_xattr)
|
|
BTF_SET_END(bpf_fs_kfunc_lsm_only_ids)
|
|
|
|
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 (prog->type == BPF_PROG_TYPE_LSM)
|
|
return 0;
|
|
if (prog->type != BPF_PROG_TYPE_STRUCT_OPS)
|
|
return -EACCES;
|
|
/* ->st_ops is unset during the cfg pass; enforced once it is set. */
|
|
if (!prog->aux->st_ops)
|
|
return 0;
|
|
if (bpf_prog_is_binfmt_misc_ops(prog) &&
|
|
!btf_id_set_contains(&bpf_fs_kfunc_lsm_only_ids, kfunc_id))
|
|
return 0;
|
|
return -EACCES;
|
|
}
|
|
|
|
/* bpf_[set|remove]_dentry_xattr.* hooks have KF_SLEEPABLE, so they are only
|
|
* available to sleepable hooks with dentry arguments.
|
|
*
|
|
* Setting and removing xattr requires exclusive lock on dentry->d_inode.
|
|
* Some hooks already locked d_inode, while some hooks have not locked
|
|
* d_inode. Therefore, we need different kfuncs for different hooks.
|
|
* Specifically, hooks in the following list (d_inode_locked_hooks)
|
|
* should call bpf_[set|remove]_dentry_xattr_locked; while other hooks
|
|
* should call bpf_[set|remove]_dentry_xattr.
|
|
*/
|
|
BTF_SET_START(d_inode_locked_hooks)
|
|
BTF_ID(func, bpf_lsm_inode_post_removexattr)
|
|
BTF_ID(func, bpf_lsm_inode_post_setattr)
|
|
BTF_ID(func, bpf_lsm_inode_post_setxattr)
|
|
BTF_ID(func, bpf_lsm_inode_removexattr)
|
|
BTF_ID(func, bpf_lsm_inode_rmdir)
|
|
BTF_ID(func, bpf_lsm_inode_setattr)
|
|
BTF_ID(func, bpf_lsm_inode_setxattr)
|
|
BTF_ID(func, bpf_lsm_inode_unlink)
|
|
#ifdef CONFIG_SECURITY_PATH
|
|
BTF_ID(func, bpf_lsm_path_unlink)
|
|
BTF_ID(func, bpf_lsm_path_rmdir)
|
|
#endif /* CONFIG_SECURITY_PATH */
|
|
BTF_SET_END(d_inode_locked_hooks)
|
|
|
|
bool bpf_lsm_has_d_inode_locked(const struct bpf_prog *prog)
|
|
{
|
|
return btf_id_set_contains(&d_inode_locked_hooks, prog->aux->attach_btf_id);
|
|
}
|
|
|
|
static const struct btf_kfunc_id_set bpf_fs_kfunc_set = {
|
|
.owner = THIS_MODULE,
|
|
.set = &bpf_fs_kfunc_set_ids,
|
|
.filter = bpf_fs_kfuncs_filter,
|
|
};
|
|
|
|
static int __init bpf_fs_kfuncs_init(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_LSM, &bpf_fs_kfunc_set);
|
|
if (ret || !IS_ENABLED(CONFIG_BINFMT_MISC_BPF))
|
|
return ret;
|
|
return register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS,
|
|
&bpf_fs_kfunc_set);
|
|
}
|
|
|
|
late_initcall(bpf_fs_kfuncs_init);
|