@@ -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);
}
new file mode 100644
@@ -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 */
new file mode 100644
@@ -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;
+}
new file mode 100644
@@ -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;
+}
@@ -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";
@@ -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";
@@ -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";
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@google.com> --- .../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