Adapt the existing test_d_path test suite to cover the operability of the newly added trusted d_path() based BPF kfunc bpf_path_d_path(). Signed-off-by: Matt Bobrowski <mattbobrowski@xxxxxxxxxx> --- .../testing/selftests/bpf/prog_tests/d_path.c | 106 ++++++++++++++++-- .../selftests/bpf/progs/d_path_common.h | 34 ++++++ .../bpf/progs/d_path_kfunc_failure.c | 66 +++++++++++ .../bpf/progs/d_path_kfunc_success.c | 25 +++++ .../testing/selftests/bpf/progs/test_d_path.c | 20 +--- .../bpf/progs/test_d_path_check_rdonly_mem.c | 6 +- .../bpf/progs/test_d_path_check_types.c | 6 +- 7 files changed, 222 insertions(+), 41 deletions(-) create mode 100644 tools/testing/selftests/bpf/progs/d_path_common.h create mode 100644 tools/testing/selftests/bpf/progs/d_path_kfunc_failure.c create mode 100644 tools/testing/selftests/bpf/progs/d_path_kfunc_success.c diff --git a/tools/testing/selftests/bpf/prog_tests/d_path.c b/tools/testing/selftests/bpf/prog_tests/d_path.c index d77ae1b1e6ba..893324d4d59f 100644 --- a/tools/testing/selftests/bpf/prog_tests/d_path.c +++ b/tools/testing/selftests/bpf/prog_tests/d_path.c @@ -11,6 +11,8 @@ #include "test_d_path.skel.h" #include "test_d_path_check_rdonly_mem.skel.h" #include "test_d_path_check_types.skel.h" +#include "d_path_kfunc_failure.skel.h" +#include "d_path_kfunc_success.skel.h" /* sys_close_range is not around for long time, so let's * make sure we can call it on systems with older glibc @@ -44,7 +46,7 @@ static int set_pathname(int fd, pid_t pid) return readlink(buf, src.want[src.cnt++].path, MAX_PATH_LEN); } -static int trigger_fstat_events(pid_t pid) +static int trigger_fstat_events(pid_t pid, bool want_error) { int sockfd = -1, procfd = -1, devfd = -1, mntnsfd = -1; int localfd = -1, indicatorfd = -1; @@ -85,25 +87,25 @@ static int trigger_fstat_events(pid_t pid) * safely resolve paths that are comprised of dentries that make use of * dynamic names. We expect to return -EOPNOTSUPP for such paths. */ - src.want[src.cnt].err = true; + src.want[src.cnt].err = want_error; src.want[src.cnt].err_code = -EOPNOTSUPP; ret = set_pathname(pipefd[0], pid); if (CHECK(ret < 0, "trigger", "set_pathname failed for pipe[0]\n")) goto out_close; - src.want[src.cnt].err = true; + src.want[src.cnt].err = want_error; src.want[src.cnt].err_code = -EOPNOTSUPP; ret = set_pathname(pipefd[1], pid); if (CHECK(ret < 0, "trigger", "set_pathname failed for pipe[1]\n")) goto out_close; - src.want[src.cnt].err = true; + src.want[src.cnt].err = want_error; src.want[src.cnt].err_code = -EOPNOTSUPP; ret = set_pathname(sockfd, pid); if (CHECK(ret < 0, "trigger", "set_pathname failed for socket\n")) goto out_close; - src.want[src.cnt].err = true; + src.want[src.cnt].err = want_error; src.want[src.cnt].err_code = -EOPNOTSUPP; ret = set_pathname(mntnsfd, pid); if (CHECK(ret < 0, "trigger", "set_pathname failed for mntnsfd\n")) @@ -151,12 +153,19 @@ static int trigger_fstat_events(pid_t pid) return ret; } -static void test_d_path_basic(void) +static void test_bpf_d_path_basic(void) { struct test_d_path__bss *bss; struct test_d_path *skel; int err; + /* + * Carrying global state across test function invocations is super + * gross, but it was late and I was tired and I just wanted to get the + * darn test working. Zero'ing this out was a simple no brainer. + */ + memset(&src, 0, sizeof(src)); + skel = test_d_path__open_and_load(); if (CHECK(!skel, "setup", "d_path skeleton failed\n")) goto cleanup; @@ -168,7 +177,7 @@ static void test_d_path_basic(void) bss = skel->bss; bss->my_pid = getpid(); - err = trigger_fstat_events(bss->my_pid); + err = trigger_fstat_events(bss->my_pid, /*want_error=*/true); if (err < 0) goto cleanup; @@ -225,7 +234,7 @@ static void test_d_path_basic(void) test_d_path__destroy(skel); } -static void test_d_path_check_rdonly_mem(void) +static void test_bpf_d_path_check_rdonly_mem(void) { struct test_d_path_check_rdonly_mem *skel; @@ -235,7 +244,7 @@ static void test_d_path_check_rdonly_mem(void) test_d_path_check_rdonly_mem__destroy(skel); } -static void test_d_path_check_types(void) +static void test_bpf_d_path_check_types(void) { struct test_d_path_check_types *skel; @@ -245,14 +254,87 @@ static void test_d_path_check_types(void) test_d_path_check_types__destroy(skel); } +static struct bpf_path_d_path_t { + const char *prog_name; +} success_test_cases[] = { + { + .prog_name = "path_d_path_from_path_argument", + }, +}; + +static void test_bpf_path_d_path(struct bpf_path_d_path_t *t) +{ + int i, ret; + struct bpf_link *link; + struct bpf_program *prog; + struct d_path_kfunc_success__bss *bss; + struct d_path_kfunc_success *skel; + + /* + * Carrying global state across function invocations is super gross, but + * it was late and I was tired and I just wanted to get the darn test + * working. Zero'ing this out was a simple no brainer. + */ + memset(&src, 0, sizeof(src)); + + skel = d_path_kfunc_success__open(); + if (!ASSERT_OK_PTR(skel, "d_path_kfunc_success__open")) + return; + + bss = skel->bss; + bss->my_pid = getpid(); + + ret = d_path_kfunc_success__load(skel); + if (CHECK(ret, "setup", "d_path_kfunc_success__load\n")) + goto cleanup; + + link = NULL; + prog = bpf_object__find_program_by_name(skel->obj, t->prog_name); + if (!ASSERT_OK_PTR(prog, "bpf_object__find_program_by_name")) + goto cleanup; + + link = bpf_program__attach(prog); + if (!ASSERT_OK_PTR(link, "bpf_program__attach")) + goto cleanup; + + ret = trigger_fstat_events(bss->my_pid, /*want_error=*/false); + if (ret < 0) + goto cleanup; + + for (i = 0; i < MAX_FILES; i++) { + struct want want = src.want[i]; + CHECK(strncmp(want.path, bss->paths_stat[i], MAX_PATH_LEN), + "check", "failed to get stat path[%d]: %s vs %s\n", i, + want.path, bss->paths_stat[i]); + CHECK(bss->rets_stat[i] != strlen(bss->paths_stat[i]) + 1, + "check", + "failed to match stat return [%d]: %d vs %zd [%s]\n", + i, bss->rets_stat[i], strlen(bss->paths_stat[i]) + 1, + bss->paths_stat[i]); + } +cleanup: + bpf_link__destroy(link); + d_path_kfunc_success__destroy(skel); +} + void test_d_path(void) { + int i = 0; + if (test__start_subtest("basic")) - test_d_path_basic(); + test_bpf_d_path_basic(); if (test__start_subtest("check_rdonly_mem")) - test_d_path_check_rdonly_mem(); + test_bpf_d_path_check_rdonly_mem(); if (test__start_subtest("check_alloc_mem")) - test_d_path_check_types(); + test_bpf_d_path_check_types(); + + for (; i < ARRAY_SIZE(success_test_cases); i++) { + if (!test__start_subtest(success_test_cases[i].prog_name)) + continue; + test_bpf_path_d_path(&success_test_cases[i]); + } + + RUN_TESTS(d_path_kfunc_failure); } diff --git a/tools/testing/selftests/bpf/progs/d_path_common.h b/tools/testing/selftests/bpf/progs/d_path_common.h new file mode 100644 index 000000000000..42d0a28a94ea --- /dev/null +++ b/tools/testing/selftests/bpf/progs/d_path_common.h @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2024 Google LLC. */ + +#ifndef _D_PATH_COMMON_H +#define _D_PATH_COMMON_H + +#include <vmlinux.h> +#include <bpf/bpf_helpers.h> +#include <bpf/bpf_tracing.h> + +#include "bpf_misc.h" + +#define MAX_PATH_LEN 128 +#define MAX_FILES 8 + +int bpf_path_d_path(struct path *path, char *buf, int buflen) __ksym; + +pid_t my_pid = 0; + +__u32 cnt_stat = 0; +__u32 cnt_close = 0; + +char paths_stat[MAX_FILES][MAX_PATH_LEN] = {}; +char paths_close[MAX_FILES][MAX_PATH_LEN] = {}; + +int rets_stat[MAX_FILES] = {}; +int rets_close[MAX_FILES] = {}; + +int called_stat = 0; +int called_close = 0; + +char _license[] SEC("license") = "GPL"; + +#endif /* _D_PATH_COMMON_H */ diff --git a/tools/testing/selftests/bpf/progs/d_path_kfunc_failure.c b/tools/testing/selftests/bpf/progs/d_path_kfunc_failure.c new file mode 100644 index 000000000000..9da5f0d395c9 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/d_path_kfunc_failure.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2024 Google LLC. */ + +#include "d_path_common.h" + +char buf[MAX_PATH_LEN] = {}; + +SEC("lsm.s/file_open") +__failure __msg("Possibly NULL pointer passed to trusted arg0") +int BPF_PROG(path_d_path_kfunc_null) +{ + /* Can't pass NULL value to bpf_path_d_path() kfunc. */ + bpf_path_d_path(NULL, buf, sizeof(buf)); + return 0; +} + +SEC("fentry/vfs_open") +__failure __msg("calling kernel function bpf_path_d_path is not allowed") +int BPF_PROG(path_d_path_kfunc_non_lsm, struct path *path, struct file *f) +{ + /* Calling bpf_path_d_path() kfunc from a non-sleepable and non-LSM + * based program isn't permitted. + */ + bpf_path_d_path(path, buf, sizeof(buf)); + return 0; +} + +SEC("lsm.s/task_alloc") +__failure __msg("R1 must be referenced or trusted") +int BPF_PROG(path_d_path_kfunc_untrusted_from_argument, struct task_struct *task) +{ + struct path *root; + + /* Walking a trusted argument yields an untrusted pointer. */ + root = &task->fs->root; + bpf_path_d_path(root, buf, sizeof(buf)); + return 0; +} + +SEC("lsm.s/file_open") +__failure __msg("R1 must be referenced or trusted") +int BPF_PROG(path_d_path_kfunc_untrusted_from_current) +{ + struct path *pwd; + struct task_struct *current; + + current = bpf_get_current_task_btf(); + /* Walking a trusted pointer returned from bpf_get_current_task_btf() + * yields and untrusted pointer. */ + pwd = ¤t->fs->pwd; + bpf_path_d_path(pwd, buf, sizeof(buf)); + return 0; +} + +SEC("lsm.s/file_open") +__failure __msg("R1 must have zero offset when passed to release func or trusted arg to kfunc") +int BPF_PROG(path_d_path_kfunc_trusted_variable_offset, struct file *file) +{ + /* Passing variable offsets from a trusted aren't supported just yet, + * despite being perfectly OK i.e. file->f_path. Once the BPF verifier + * has been updated to handle this case, this test can be removed. For + * now, ensure we reject the BPF program upon load if this is attempted. + */ + bpf_path_d_path(&file->f_path, buf, sizeof(buf)); + return 0; +} diff --git a/tools/testing/selftests/bpf/progs/d_path_kfunc_success.c b/tools/testing/selftests/bpf/progs/d_path_kfunc_success.c new file mode 100644 index 000000000000..72d1a64618d1 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/d_path_kfunc_success.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2024 Google LLC. */ + +#include "d_path_common.h" + +SEC("lsm.s/inode_getattr") +int BPF_PROG(path_d_path_from_path_argument, struct path *path) +{ + u32 cnt = cnt_stat; + int ret; + pid_t pid; + + pid = bpf_get_current_pid_tgid() >> 32; + if (pid != my_pid) + return 0; + + if (cnt >= MAX_FILES) + return 0; + + ret = bpf_path_d_path(path, paths_stat[cnt], MAX_PATH_LEN); + rets_stat[cnt] = ret; + cnt_stat++; + + return 0; +} diff --git a/tools/testing/selftests/bpf/progs/test_d_path.c b/tools/testing/selftests/bpf/progs/test_d_path.c index fc2754f166ec..5bdfa4abb5f6 100644 --- a/tools/testing/selftests/bpf/progs/test_d_path.c +++ b/tools/testing/selftests/bpf/progs/test_d_path.c @@ -1,22 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 -#include "vmlinux.h" -#include <bpf/bpf_helpers.h> -#include <bpf/bpf_tracing.h> - -#define MAX_PATH_LEN 128 -#define MAX_FILES 8 - -pid_t my_pid = 0; -__u32 cnt_stat = 0; -__u32 cnt_close = 0; -char paths_stat[MAX_FILES][MAX_PATH_LEN] = {}; -char paths_close[MAX_FILES][MAX_PATH_LEN] = {}; -int rets_stat[MAX_FILES] = {}; -int rets_close[MAX_FILES] = {}; - -int called_stat = 0; -int called_close = 0; +#include "d_path_common.h" SEC("fentry/security_inode_getattr") int BPF_PROG(prog_stat, struct path *path, struct kstat *stat, @@ -61,5 +45,3 @@ int BPF_PROG(prog_close, struct file *file, void *id) cnt_close++; return 0; } - -char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/test_d_path_check_rdonly_mem.c b/tools/testing/selftests/bpf/progs/test_d_path_check_rdonly_mem.c index 27c27cff6a3a..76654dbf637e 100644 --- a/tools/testing/selftests/bpf/progs/test_d_path_check_rdonly_mem.c +++ b/tools/testing/selftests/bpf/progs/test_d_path_check_rdonly_mem.c @@ -1,9 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2022 Google */ -#include "vmlinux.h" -#include <bpf/bpf_helpers.h> -#include <bpf/bpf_tracing.h> +#include "d_path_common.h" extern const int bpf_prog_active __ksym; @@ -24,5 +22,3 @@ int BPF_PROG(d_path_check_rdonly_mem, struct path *path, struct kstat *stat, } return 0; } - -char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/progs/test_d_path_check_types.c b/tools/testing/selftests/bpf/progs/test_d_path_check_types.c index 7e02b7361307..c722754aedb0 100644 --- a/tools/testing/selftests/bpf/progs/test_d_path_check_types.c +++ b/tools/testing/selftests/bpf/progs/test_d_path_check_types.c @@ -1,8 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 -#include "vmlinux.h" -#include <bpf/bpf_helpers.h> -#include <bpf/bpf_tracing.h> +#include "d_path_common.h" extern const int bpf_prog_active __ksym; @@ -28,5 +26,3 @@ int BPF_PROG(d_path_check_rdonly_mem, struct path *path, struct kstat *stat, } return 0; } - -char _license[] SEC("license") = "GPL"; -- 2.44.0.rc0.258.g7320e95886-goog /M