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