Let the BPF verifier to recognize const char * arguments from LSM hooks (and other BPF program types) as valid const string pointers that can be passed to kfuncs expecting KF_ARG_PTR_TO_CONST_STR. Previously, kfuncs with KF_ARG_PTR_TO_CONST_STR only accepted PTR_TO_MAP_VALUE from readonly maps. This was limiting for LSM programs that receive const char * arguments from hooks like sb_mount's dev_name. Signed-off-by: Song Liu --- include/linux/btf.h | 1 + kernel/bpf/btf.c | 33 ++++++++++++++++++++++++++++ kernel/bpf/verifier.c | 51 +++++++++++++++++++++++++++++++++---------- 3 files changed, 73 insertions(+), 12 deletions(-) diff --git a/include/linux/btf.h b/include/linux/btf.h index f06976ffb63f..bd5a32d33254 100644 --- a/include/linux/btf.h +++ b/include/linux/btf.h @@ -224,6 +224,7 @@ struct btf *btf_base_btf(const struct btf *btf); bool btf_type_is_i32(const struct btf_type *t); bool btf_type_is_i64(const struct btf_type *t); bool btf_type_is_primitive(const struct btf_type *t); +bool btf_type_is_const_char_ptr(const struct btf *btf, const struct btf_type *t); bool btf_member_is_reg_int(const struct btf *btf, const struct btf_type *s, const struct btf_member *m, u32 expected_offset, u32 expected_size); diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index 0de8fc8a0e0b..94a272585b97 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -897,6 +897,25 @@ bool btf_type_is_primitive(const struct btf_type *t) btf_is_any_enum(t); } +bool btf_type_is_const_char_ptr(const struct btf *btf, const struct btf_type *t) +{ + const char *tname; + + /* The type chain has to be PTR->CONST->CHAR */ + if (BTF_INFO_KIND(t->info) != BTF_KIND_PTR) + return false; + + t = btf_type_by_id(btf, t->type); + if (BTF_INFO_KIND(t->info) != BTF_KIND_CONST) + return false; + + t = btf_type_by_id(btf, t->type); + tname = btf_name_by_offset(btf, t->name_off); + if (tname && strcmp(tname, "char") == 0) + return true; + return false; +} + /* * Check that given struct member is a regular int with expected * offset and size. @@ -6746,6 +6765,20 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type, /* Default prog with MAX_BPF_FUNC_REG_ARGS args */ return true; t = btf_type_by_id(btf, args[arg].type); + + /* + * For const string, we need to match "const char *" + * exactly. Therefore, do the check before the skipping + * modifiers. + */ + if (btf_type_is_const_char_ptr(btf, t)) { + info->reg_type = PTR_TO_BTF_ID; + if (prog_args_trusted(prog)) + info->reg_type |= PTR_TRUSTED; + info->btf = btf; + info->btf_id = args[arg].type; + return true; + } } /* skip modifiers */ diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 766695491bc5..a9757c056d4b 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -9598,8 +9598,12 @@ static enum bpf_dynptr_type dynptr_get_type(struct bpf_verifier_env *env, return state->stack[spi].spilled_ptr.dynptr.type; } -static int check_reg_const_str(struct bpf_verifier_env *env, - struct bpf_reg_state *reg, u32 regno) +/* + * Check for const string saved in a bpf map. The caller is responsible + * to check reg->type == PTR_TO_MAP_VALUE. + */ +static int check_reg_const_str_in_map(struct bpf_verifier_env *env, + struct bpf_reg_state *reg, u32 regno) { struct bpf_map *map = reg->map_ptr; int err; @@ -9607,9 +9611,6 @@ static int check_reg_const_str(struct bpf_verifier_env *env, u64 map_addr; char *str_ptr; - if (reg->type != PTR_TO_MAP_VALUE) - return -EINVAL; - if (!bpf_map_is_rdonly(map)) { verbose(env, "R%d does not point to a readonly map'\n", regno); return -EACCES; @@ -9646,6 +9647,26 @@ static int check_reg_const_str(struct bpf_verifier_env *env, return 0; } +/* Check for const string passed in as input to the bpf program. */ +static int check_reg_const_str_arg(struct bpf_reg_state *reg) +{ + const struct btf *btf; + const struct btf_type *t; + const char *tname; + + if (base_type(reg->type) != PTR_TO_BTF_ID) + return -EINVAL; + + btf = reg->btf; + t = btf_type_by_id(btf, reg->btf_id); + if (!t) + return -EINVAL; + + if (btf_type_is_const_char_ptr(btf, t)) + return 0; + return -EINVAL; +} + /* Returns constant key value in `value` if possible, else negative error */ static int get_constant_map_key(struct bpf_verifier_env *env, struct bpf_reg_state *key, @@ -9964,7 +9985,9 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg, break; case ARG_PTR_TO_CONST_STR: { - err = check_reg_const_str(env, reg, regno); + if (reg->type != PTR_TO_MAP_VALUE) + return -EINVAL; + err = check_reg_const_str_in_map(env, reg, regno); if (err) return err; break; @@ -13626,13 +13649,17 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_ meta->arg_btf_id = reg->btf_id; break; case KF_ARG_PTR_TO_CONST_STR: - if (reg->type != PTR_TO_MAP_VALUE) { - verbose(env, "arg#%d doesn't point to a const string\n", i); - return -EINVAL; + if (reg->type == PTR_TO_MAP_VALUE) { + ret = check_reg_const_str_in_map(env, reg, regno); + if (ret) + return ret; + } else { + ret = check_reg_const_str_arg(reg); + if (ret) { + verbose(env, "arg#%d doesn't point to a const string\n", i); + return ret; + } } - ret = check_reg_const_str(env, reg, regno); - if (ret) - return ret; break; case KF_ARG_PTR_TO_WORKQUEUE: if (reg->type != PTR_TO_MAP_VALUE) { -- 2.47.3 Add two new kfuncs to fs/bpf_fs_kfuncs.c that wrap kern_path() for use by BPF LSM programs: bpf_kern_path(): - Resolves a pathname string to a struct path - Allocates memory for the path structure - Returns NULL on error or if the path doesn't exist - Marked with KF_ACQUIRE | KF_SLEEPABLE | KF_RET_NULL bpf_path_put(): - Releases the path reference and frees the allocated memory - Marked with KF_RELEASE to enforce acquire/release semantics These kfuncs enable BPF LSM programs to resolve pathnames provided by hook arguments (e.g., dev_name from sb_mount) and validate or inspect the resolved paths. The verifier enforces proper resource management through acquire/release tracking. Example usage: struct path *p = bpf_kern_path("/etc/passwd", LOOKUP_FOLLOW); if (p) { // Use the path... bpf_path_put(p); // Must release } Signed-off-by: Song Liu --- fs/bpf_fs_kfuncs.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c index 5ace2511fec5..977f8dcbc208 100644 --- a/fs/bpf_fs_kfuncs.c +++ b/fs/bpf_fs_kfuncs.c @@ -11,6 +11,7 @@ #include #include #include +#include #include __bpf_kfunc_start_defs(); @@ -96,6 +97,61 @@ __bpf_kfunc int bpf_path_d_path(const struct path *path, char *buf, size_t buf__ return len; } +/** + * bpf_kern_path - resolve a pathname to a struct path + * @pathname__str: pathname to resolve + * @flags: lookup flags (e.g., LOOKUP_FOLLOW) + * + * Resolve the pathname for the supplied *pathname__str* and return a pointer + * to a struct path. This is a wrapper around kern_path() that allocates and + * returns a struct path pointer on success. + * + * The returned struct path pointer must be released using bpf_path_put(). + * Failing to call bpf_path_put() on the returned struct path pointer will + * result in the BPF program being rejected by the BPF verifier. + * + * This BPF kfunc may only be called from BPF LSM programs. + * + * Return: A pointer to an allocated struct path on success, NULL on error. + */ +__bpf_kfunc struct path *bpf_kern_path(const char *pathname__str, unsigned int flags) +{ + struct path *path; + int ret; + + path = kmalloc(sizeof(*path), GFP_KERNEL); + if (!path) + return NULL; + + ret = kern_path(pathname__str, flags, path); + if (ret) { + kfree(path); + return NULL; + } + + return path; +} + +/** + * bpf_path_put - release a struct path reference + * @path: struct path pointer to release + * + * Release the struct path pointer that was acquired by bpf_kern_path(). + * This BPF kfunc calls path_put() on the supplied *path* and then frees + * the allocated memory. + * + * Only struct path pointers acquired by bpf_kern_path() may be passed to + * this BPF kfunc. Attempting to pass any other pointer 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_path_put(struct path *path) +{ + path_put(path); + kfree(path); +} + static bool match_security_bpf_prefix(const char *name__str) { return !strncmp(name__str, XATTR_NAME_BPF_LSM, XATTR_NAME_BPF_LSM_LEN); @@ -363,6 +419,8 @@ BTF_ID_FLAGS(func, bpf_get_task_exe_file, KF_ACQUIRE | KF_TRUSTED_ARGS | KF_RET_NULL) BTF_ID_FLAGS(func, bpf_put_file, KF_RELEASE) BTF_ID_FLAGS(func, bpf_path_d_path, KF_TRUSTED_ARGS) +BTF_ID_FLAGS(func, bpf_kern_path, KF_TRUSTED_ARGS | KF_ACQUIRE | KF_SLEEPABLE | KF_RET_NULL) +BTF_ID_FLAGS(func, bpf_path_put, KF_RELEASE) 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) -- 2.47.3 Add comprehensive selftests for the new bpf_kern_path and bpf_path_put kfuncs: 1. Functional tests (prog_tests/kern_path.c, progs/test_kern_path.c): - test_kern_path_basic: Tests successful path resolution using /proc/self/exe and validates the resolved path with bpf_path_d_path - test_kern_path_sb_mount: Tests bpf_kern_path with dynamic input from LSM hook parameter (dev_name from sb_mount), demonstrating real-world usage where BPF programs resolve paths from hook args 2. Verifier success tests (progs/verifier_kern_path.c): - kern_path_success: Proper acquire -> use -> release pattern - kern_path_multiple_paths: Multiple concurrent path acquisitions 3. Verifier failure tests (progs/verifier_kern_path_fail.c): - kern_path_unreleased: Resource leak detection - path_put_unacquired: Releasing unacquired path - path_use_after_put: Use-after-free detection - double_path_put: Double-free detection - kern_path_non_lsm: Program type restrictions (LSM only) - kern_path_non_const_str: reject none const string These tests verify both the functionality of the kfuncs and that the verifier properly enforces acquire/release semantics to prevent resource leaks. Signed-off-by: Song Liu --- .../testing/selftests/bpf/bpf_experimental.h | 4 + .../selftests/bpf/prog_tests/kern_path.c | 82 ++++++++++++++++ .../selftests/bpf/progs/test_kern_path.c | 56 +++++++++++ .../selftests/bpf/progs/verifier_kern_path.c | 52 ++++++++++ .../bpf/progs/verifier_kern_path_fail.c | 97 +++++++++++++++++++ 5 files changed, 291 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/kern_path.c create mode 100644 tools/testing/selftests/bpf/progs/test_kern_path.c create mode 100644 tools/testing/selftests/bpf/progs/verifier_kern_path.c create mode 100644 tools/testing/selftests/bpf/progs/verifier_kern_path_fail.c diff --git a/tools/testing/selftests/bpf/bpf_experimental.h b/tools/testing/selftests/bpf/bpf_experimental.h index 2cd9165c7348..c512c9a14752 100644 --- a/tools/testing/selftests/bpf/bpf_experimental.h +++ b/tools/testing/selftests/bpf/bpf_experimental.h @@ -221,6 +221,10 @@ extern void bpf_put_file(struct file *file) __ksym; */ extern int bpf_path_d_path(const struct path *path, char *buf, size_t buf__sz) __ksym; +extern struct path *bpf_kern_path(const char *pathname, unsigned int flags) __ksym; +extern void bpf_path_put(struct path *path) __ksym; +extern int bpf_path_d_path(const struct path *path, char *buf, size_t buf__sz) __ksym; + /* This macro must be used to mark the exception callback corresponding to the * main program. For example: * diff --git a/tools/testing/selftests/bpf/prog_tests/kern_path.c b/tools/testing/selftests/bpf/prog_tests/kern_path.c new file mode 100644 index 000000000000..f4cdfe202a26 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/kern_path.c @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2025 Meta Platforms, Inc. */ + +#include +#include +#include +#include +#include + +#include "test_kern_path.skel.h" +#include "verifier_kern_path.skel.h" +#include "verifier_kern_path_fail.skel.h" + +static void __test_kern_path(void (*trigger)(void)) +{ + struct test_kern_path *skel; + int err; + + skel = test_kern_path__open_and_load(); + if (!ASSERT_OK_PTR(skel, "test_kern_path__open_and_load")) + return; + + skel->bss->monitored_pid = getpid(); + + err = test_kern_path__attach(skel); + if (!ASSERT_OK(err, "test_kern_path__attach")) + goto cleanup; + + trigger(); + + /* Verify the bpf_path_d_path worked */ + ASSERT_GT(skel->bss->path_len, 0, "path_len > 0"); + +cleanup: + test_kern_path__destroy(skel); +} + +static void trigger_file_open(void) +{ + int fd; + + fd = open("/dev/null", O_RDONLY); + if (!ASSERT_OK_FD(fd, "open /dev/null")) + return; + close(fd); +} + +static void trigger_sb_mount(void) +{ + char tmpdir[] = "/tmp/bpf_kern_path_test_XXXXXX"; + int err; + + if (!ASSERT_OK_PTR(mkdtemp(tmpdir), "mkdtemp")) + return; + + err = mount("/tmp", tmpdir, NULL, MS_BIND, NULL); + if (!ASSERT_OK(err, "bind mount")) + goto rmdir; + + umount(tmpdir); +rmdir: + rmdir(tmpdir); +} + +void test_kern_path(void) +{ + if (test__start_subtest("file_open")) + __test_kern_path(trigger_file_open); + + if (test__start_subtest("sb_mount")) + __test_kern_path(trigger_sb_mount); +} + +void test_verifier_kern_path(void) +{ + RUN_TESTS(verifier_kern_path); +} + +void test_verifier_kern_path_fail(void) +{ + RUN_TESTS(verifier_kern_path_fail); +} diff --git a/tools/testing/selftests/bpf/progs/test_kern_path.c b/tools/testing/selftests/bpf/progs/test_kern_path.c new file mode 100644 index 000000000000..e9186a1aa990 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/test_kern_path.c @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2025 Meta Platforms, Inc. */ + +#include "vmlinux.h" +#include +#include "bpf_misc.h" +#include "bpf_experimental.h" + +#define MAX_PATH_LEN 256 + +char buf[MAX_PATH_LEN]; +int path_len = 0; +u32 monitored_pid = 0; + +SEC("lsm.s/file_open") +int BPF_PROG(test_kern_path_basic, struct file *file) +{ + struct path *p; + int ret; + + if (bpf_get_current_pid_tgid() >> 32 != monitored_pid) + return 0; + + p = bpf_kern_path("/proc/self/exe", 0); + if (p) { + ret = bpf_path_d_path(p, buf, MAX_PATH_LEN); + if (ret > 0) + path_len = ret; + bpf_path_put(p); + } + + return 0; +} + +SEC("lsm.s/sb_mount") +int BPF_PROG(test_kern_path_from_sb_mount, const char *dev_name, const struct path *path, + const char *type, unsigned long flags, void *data) +{ + struct path *p; + int ret; + + if (bpf_get_current_pid_tgid() >> 32 != monitored_pid) + return 0; + + p = bpf_kern_path(dev_name, 0); + if (p) { + ret = bpf_path_d_path(p, buf, MAX_PATH_LEN); + if (ret > 0) + path_len = ret; + bpf_path_put(p); + } + + return 0; +} + +char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/verifier_kern_path.c b/tools/testing/selftests/bpf/progs/verifier_kern_path.c new file mode 100644 index 000000000000..0e6ccf640b64 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/verifier_kern_path.c @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2025 Meta Platforms, Inc. */ + +#include +#include +#include +#include "bpf_misc.h" +#include "bpf_experimental.h" + +static char buf[PATH_MAX]; + +SEC("lsm.s/file_open") +__success +int BPF_PROG(kern_path_success) +{ + struct path *p; + + p = bpf_kern_path("/proc/self/exe", 0); + if (!p) + return 0; + + bpf_path_d_path(p, buf, sizeof(buf)); + + bpf_path_put(p); + return 0; +} + +SEC("lsm.s/file_open") +__success +int BPF_PROG(kern_path_multiple_paths) +{ + struct path *p1, *p2; + + p1 = bpf_kern_path("/proc/self/exe", 0); + if (!p1) + return 0; + + p2 = bpf_kern_path("/proc/self/cwd", 0); + if (!p2) { + bpf_path_put(p1); + return 0; + } + + bpf_path_d_path(p1, buf, sizeof(buf)); + bpf_path_d_path(p2, buf, sizeof(buf)); + + bpf_path_put(p2); + bpf_path_put(p1); + return 0; +} + +char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/verifier_kern_path_fail.c b/tools/testing/selftests/bpf/progs/verifier_kern_path_fail.c new file mode 100644 index 000000000000..520c227af5ca --- /dev/null +++ b/tools/testing/selftests/bpf/progs/verifier_kern_path_fail.c @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2025 Meta Platforms, Inc. */ + +#include +#include +#include +#include "bpf_misc.h" +#include "bpf_experimental.h" + +static char buf[PATH_MAX]; + +SEC("lsm.s/file_open") +__failure __msg("Unreleased reference") +int BPF_PROG(kern_path_unreleased) +{ + struct path *p; + + p = bpf_kern_path("/proc/self/exe", 0); + if (!p) + return 0; + + /* Acquired but never released - should fail verification */ + return 0; +} + +SEC("lsm.s/file_open") +__failure __msg("pointer type STRUCT path must point to scalar, or struct with scalar") +int BPF_PROG(path_put_unacquired) +{ + struct path p = {}; + + /* Can't release an unacquired path - should fail verification */ + bpf_path_put(&p); + return 0; +} + +SEC("lsm.s/file_open") +__failure __msg("pointer type STRUCT path must point to scalar, or struct with scalar") +int BPF_PROG(path_use_after_put, struct file *file) +{ + struct path *p; + + p = bpf_kern_path("/proc/self/exe", 0); + if (!p) + return 0; + + bpf_path_put(p); + + /* Using path after put - should fail verification */ + bpf_path_d_path(p, buf, sizeof(buf)); + return 0; +} + +SEC("lsm.s/file_open") +__failure __msg("pointer type STRUCT path must point to scalar, or struct with scalar") +int BPF_PROG(double_path_put) +{ + struct path *p; + + p = bpf_kern_path("/proc/self/exe", 0); + if (!p) + return 0; + + bpf_path_put(p); + /* Double put - should fail verification */ + bpf_path_put(p); + return 0; +} + +SEC("fentry/vfs_open") +__failure __msg("calling kernel function bpf_kern_path is not allowed") +int BPF_PROG(kern_path_non_lsm) +{ + struct path *p; + + /* Calling bpf_kern_path() from a non-LSM BPF program isn't permitted */ + p = bpf_kern_path("/proc/self/exe", 0); + if (p) + bpf_path_put(p); + return 0; +} + +SEC("lsm.s/sb_eat_lsm_opts") +__failure __msg("arg#0 doesn't point to a const string") +int BPF_PROG(kern_path_non_const_str, char *options, void **mnt_opts) +{ + struct path *p; + + /* Calling bpf_kern_path() from a with non-const string isn't permitted */ + p = bpf_kern_path(options, 0); + if (p) + bpf_path_put(p); + return 0; +} + + +char _license[] SEC("license") = "GPL"; -- 2.47.3