Add six new BPF kfuncs that enable BPF LSM programs to safely interact with dentry objects: - bpf_dget(): Acquire reference on dentry - bpf_dput(): Release reference on dentry - bpf_dget_parent(): Get referenced parent dentry - bpf_d_find_alias(): Find referenced alias dentry for inode - bpf_file_dentry(): Get dentry from file - bpf_file_vfsmount(): Get vfsmount from file All kfuncs are currently restricted to BPF_PROG_TYPE_LSM programs. Signed-off-by: David Windsor --- fs/bpf_fs_kfuncs.c | 104 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c index 1e36a12b88f7..988e408fe7b3 100644 --- a/fs/bpf_fs_kfuncs.c +++ b/fs/bpf_fs_kfuncs.c @@ -169,6 +169,104 @@ __bpf_kfunc int bpf_get_file_xattr(struct file *file, const char *name__str, return bpf_get_dentry_xattr(dentry, name__str, value_p); } +/** + * bpf_dget - get a reference on a dentry + * @dentry: dentry to get a reference on + * + * Get a reference on the supplied *dentry*. The referenced dentry pointer + * acquired by this BPF kfunc must be released using bpf_dput(). + * + * This BPF kfunc may only be called from BPF LSM programs. + * + * Return: A referenced dentry pointer. On error, NULL is returned. + */ +__bpf_kfunc struct dentry *bpf_dget(struct dentry *dentry) +{ + return dget(dentry); +} + +/** + * bpf_dput - put a reference on a dentry + * @dentry: dentry to put a reference on + * + * Put a reference on the supplied *dentry*. + * + * This BPF kfunc may only be called from BPF LSM programs. + */ +__bpf_kfunc void bpf_dput(struct dentry *dentry) +{ + dput(dentry); +} + +/** + * bpf_dget_parent - get a reference on the parent dentry + * @dentry: dentry to get the parent of + * + * Get a reference on the parent of the supplied *dentry*. The referenced + * dentry pointer acquired by this BPF kfunc must be released using bpf_dput(). + * + * This BPF kfunc may only be called from BPF LSM programs. + * + * Return: A referenced parent dentry pointer. On error, NULL is returned. + */ +__bpf_kfunc struct dentry *bpf_dget_parent(struct dentry *dentry) +{ + return dget_parent(dentry); +} + +/** + * bpf_d_find_alias - find an alias dentry for an inode + * @inode: inode to find an alias for + * + * Find an alias dentry for the supplied *inode*. The referenced dentry pointer + * acquired by this BPF kfunc must be released using bpf_dput(). + * + * This BPF kfunc may only be called from BPF LSM programs. + * + * Return: A referenced alias dentry pointer. On error, NULL is returned. + */ +__bpf_kfunc struct dentry *bpf_d_find_alias(struct inode *inode) +{ + return d_find_alias(inode); +} + +/** + * bpf_file_dentry - get the dentry associated with a file + * @file: file to get the dentry from + * + * Get the dentry associated with the supplied *file*. This is a trusted + * accessor that allows BPF programs to safely obtain a dentry pointer + * from a file structure. The returned pointer is borrowed and does not + * require bpf_dput(). + * + * This BPF kfunc may only be called from BPF LSM programs. + * + * Return: A dentry pointer. On error, NULL is returned. + */ +__bpf_kfunc struct dentry *bpf_file_dentry(struct file *file) +{ + return file_dentry(file); +} + +/** + * bpf_file_vfsmount - get the vfsmount associated with a file + * @file: file to get the vfsmount from + * + * Get the vfsmount associated with the supplied *file*. This is a trusted + * accessor that allows BPF programs to safely obtain a vfsmount pointer + * from a file structure. The returned pointer is borrowed and does not + * require any release function. + * + * This BPF kfunc may only be called from BPF LSM programs. + * + * Return: A vfsmount pointer. On error, NULL is returned. + */ +__bpf_kfunc struct vfsmount *bpf_file_vfsmount(struct file *file) +{ + return file->f_path.mnt; +} + + __bpf_kfunc_end_defs(); static int bpf_xattr_write_permission(const char *name, struct inode *inode) @@ -367,6 +465,12 @@ BTF_ID_FLAGS(func, bpf_get_dentry_xattr, KF_SLEEPABLE | KF_TRUSTED_ARGS) BTF_ID_FLAGS(func, bpf_get_file_xattr, KF_SLEEPABLE | KF_TRUSTED_ARGS) BTF_ID_FLAGS(func, bpf_set_dentry_xattr, KF_SLEEPABLE | KF_TRUSTED_ARGS) BTF_ID_FLAGS(func, bpf_remove_dentry_xattr, KF_SLEEPABLE | KF_TRUSTED_ARGS) +BTF_ID_FLAGS(func, bpf_dget, KF_ACQUIRE | KF_RET_NULL) +BTF_ID_FLAGS(func, bpf_dput, KF_RELEASE) +BTF_ID_FLAGS(func, bpf_dget_parent, KF_ACQUIRE | KF_RET_NULL) +BTF_ID_FLAGS(func, bpf_d_find_alias, KF_ACQUIRE | KF_RET_NULL) +BTF_ID_FLAGS(func, bpf_file_dentry, KF_TRUSTED_ARGS | KF_RET_NULL) +BTF_ID_FLAGS(func, bpf_file_vfsmount, KF_TRUSTED_ARGS | KF_RET_NULL) BTF_KFUNCS_END(bpf_fs_kfunc_set_ids) static int bpf_fs_kfuncs_filter(const struct bpf_prog *prog, u32 kfunc_id) -- 2.43.0 Add BPF selftests that exercise the new dentry kfuncs via an LSM program attached to the file_open hook. Signed-off-by: David Windsor --- .../selftests/bpf/prog_tests/dentry_lsm.c | 48 +++++++++++++++++ .../testing/selftests/bpf/progs/dentry_lsm.c | 51 +++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/dentry_lsm.c create mode 100644 tools/testing/selftests/bpf/progs/dentry_lsm.c diff --git a/tools/testing/selftests/bpf/prog_tests/dentry_lsm.c b/tools/testing/selftests/bpf/prog_tests/dentry_lsm.c new file mode 100644 index 000000000000..3e8c68017954 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/dentry_lsm.c @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2025 David Windsor */ + +#include +#include +#include +#include +#include +#include +#include +#include "dentry_lsm.skel.h" + +void test_dentry_lsm(void) +{ + struct dentry_lsm *skel; + char test_file[PATH_MAX]; + int fd, ret; + + skel = dentry_lsm__open_and_load(); + if (!ASSERT_OK_PTR(skel, "dentry_lsm__open_and_load")) + return; + + ret = dentry_lsm__attach(skel); + if (!ASSERT_OK(ret, "dentry_lsm__attach")) + goto cleanup; + + /* Create a temporary file to trigger file_open LSM hook */ + ret = snprintf(test_file, sizeof(test_file), "/tmp/bpf_test_file_%d", getpid()); + if (!ASSERT_GT(ret, 0, "snprintf")) + goto cleanup_link; + if (!ASSERT_LT(ret, sizeof(test_file), "snprintf")) + goto cleanup_link; + + fd = open(test_file, O_CREAT | O_RDWR, 0644); + if (!ASSERT_GE(fd, 0, "open")) + goto cleanup_link; + close(fd); + + /* Test passes if BPF program loaded and ran without error */ + + /* Clean up test file */ + unlink(test_file); + +cleanup_link: + unlink(test_file); +cleanup: + dentry_lsm__destroy(skel); +} diff --git a/tools/testing/selftests/bpf/progs/dentry_lsm.c b/tools/testing/selftests/bpf/progs/dentry_lsm.c new file mode 100644 index 000000000000..fa6d65d2c50f --- /dev/null +++ b/tools/testing/selftests/bpf/progs/dentry_lsm.c @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2025 David Windsor */ + +#include "vmlinux.h" +#include +#include +#include + +extern struct dentry *bpf_dget(struct dentry *dentry) __ksym; +extern void bpf_dput(struct dentry *dentry) __ksym; +extern struct dentry *bpf_dget_parent(struct dentry *dentry) __ksym; +extern struct dentry *bpf_d_find_alias(struct inode *inode) __ksym; +extern struct dentry *bpf_file_dentry(struct file *file) __ksym; +extern struct vfsmount *bpf_file_vfsmount(struct file *file) __ksym; + +SEC("lsm.s/file_open") +int BPF_PROG(file_open, struct file *file) +{ + struct dentry *dentry, *parent, *alias, *dentry_ref; + struct vfsmount *vfs_mnt; + + if (!file) + return 0; + + dentry = bpf_file_dentry(file); + if (dentry) { + dentry_ref = bpf_dget(dentry); + if (dentry_ref) + bpf_dput(dentry_ref); + + parent = bpf_dget_parent(dentry); + if (parent) + bpf_dput(parent); + } + + if (file->f_inode) { + alias = bpf_d_find_alias(file->f_inode); + if (alias) + bpf_dput(alias); + } + + vfs_mnt = bpf_file_vfsmount(file); + if (vfs_mnt) { + /* Test that we can access vfsmount */ + (void)vfs_mnt; + } + + return 0; +} + +char _license[] SEC("license") = "GPL"; -- 2.43.0