Add a regression test for bpf_d_path() to cover incorrect verifier
assumptions caused by an incorrect function prototype. The test
attaches to the fallocate hook, calls bpf_d_path() and verifies that
a simple prefix comparison on the returned pathname behaves correctly
after the fix in patch 1. It ensures the verifier does not assume
the buffer remains unwritten.

Co-developed-by: Zesen Liu <[email protected]>
Signed-off-by: Zesen Liu <[email protected]>
Co-developed-by: Peili Gao <[email protected]>
Signed-off-by: Peili Gao <[email protected]>
Co-developed-by: Haoran Ni <[email protected]>
Signed-off-by: Haoran Ni <[email protected]>
Signed-off-by: Shuran Liu <[email protected]>
---
 .../testing/selftests/bpf/prog_tests/d_path.c | 91 +++++++++++++++----
 .../testing/selftests/bpf/progs/test_d_path.c | 23 +++++
 2 files changed, 96 insertions(+), 18 deletions(-)

diff --git a/tools/testing/selftests/bpf/prog_tests/d_path.c 
b/tools/testing/selftests/bpf/prog_tests/d_path.c
index ccc768592e66..1a2a2f1abf03 100644
--- a/tools/testing/selftests/bpf/prog_tests/d_path.c
+++ b/tools/testing/selftests/bpf/prog_tests/d_path.c
@@ -38,6 +38,14 @@ static int set_pathname(int fd, pid_t pid)
        return readlink(buf, src.paths[src.cnt++], MAX_PATH_LEN);
 }
 
+static inline long syscall_close(int fd)
+{
+       return syscall(__NR_close_range,
+                       (unsigned int)fd,
+                       (unsigned int)fd,
+                       0u);
+}
+
 static int trigger_fstat_events(pid_t pid)
 {
        int sockfd = -1, procfd = -1, devfd = -1;
@@ -104,36 +112,47 @@ static int trigger_fstat_events(pid_t pid)
        /* sys_close no longer triggers filp_close, but we can
         * call sys_close_range instead which still does
         */
-#define close(fd) syscall(__NR_close_range, fd, fd, 0)
-
-       close(pipefd[0]);
-       close(pipefd[1]);
-       close(sockfd);
-       close(procfd);
-       close(devfd);
-       close(localfd);
-       close(indicatorfd);
-
-#undef close
+       syscall_close(pipefd[0]);
+       syscall_close(pipefd[1]);
+       syscall_close(sockfd);
+       syscall_close(procfd);
+       syscall_close(devfd);
+       syscall_close(localfd);
+       syscall_close(indicatorfd);
        return ret;
 }
 
+static void attach_and_load(struct test_d_path **skel)
+{
+       int err;
+
+       *skel = test_d_path__open_and_load();
+       if (CHECK(!*skel, "setup", "d_path skeleton failed\n"))
+               goto cleanup;
+
+       err = test_d_path__attach(*skel);
+       if (CHECK(err, "setup", "attach failed: %d\n", err))
+               goto cleanup;
+
+       (*skel)->bss->my_pid = getpid();
+       return;
+
+cleanup:
+       test_d_path__destroy(*skel);
+       *skel = NULL;
+}
+
 static void test_d_path_basic(void)
 {
        struct test_d_path__bss *bss;
        struct test_d_path *skel;
        int err;
 
-       skel = test_d_path__open_and_load();
-       if (CHECK(!skel, "setup", "d_path skeleton failed\n"))
-               goto cleanup;
-
-       err = test_d_path__attach(skel);
-       if (CHECK(err, "setup", "attach failed: %d\n", err))
+       attach_and_load(&skel);
+       if (!skel)
                goto cleanup;
 
        bss = skel->bss;
-       bss->my_pid = getpid();
 
        err = trigger_fstat_events(bss->my_pid);
        if (err < 0)
@@ -195,6 +214,39 @@ static void test_d_path_check_types(void)
        test_d_path_check_types__destroy(skel);
 }
 
+/* Check if the verifier correctly generates code for
+ * accessing the memory modified by d_path helper.
+ */
+static void test_d_path_mem_access(void)
+{
+       int localfd = -1;
+       char path_template[] = "/dev/shm/d_path_loadgen.XXXXXX";
+       struct test_d_path__bss *bss;
+       struct test_d_path *skel;
+
+       attach_and_load(&skel);
+       if (!skel)
+               goto cleanup;
+
+       bss = skel->bss;
+
+       localfd = mkstemp(path_template);
+       if (CHECK(localfd < 0, "trigger", "mkstemp failed\n"))
+               goto cleanup;
+
+       if (CHECK(fallocate(localfd, 0, 0, 1024) < 0, "trigger", "fallocate 
failed\n"))
+               goto cleanup;
+       remove(path_template);
+
+       if (CHECK(!bss->path_match_fallocate, "check",
+                 "failed to read fallocate path"))
+               goto cleanup;
+
+cleanup:
+       syscall_close(localfd);
+       test_d_path__destroy(skel);
+}
+
 void test_d_path(void)
 {
        if (test__start_subtest("basic"))
@@ -205,4 +257,7 @@ void test_d_path(void)
 
        if (test__start_subtest("check_alloc_mem"))
                test_d_path_check_types();
+
+       if (test__start_subtest("check_mem_access"))
+               test_d_path_mem_access();
 }
diff --git a/tools/testing/selftests/bpf/progs/test_d_path.c 
b/tools/testing/selftests/bpf/progs/test_d_path.c
index 84e1f883f97b..561b2f861808 100644
--- a/tools/testing/selftests/bpf/progs/test_d_path.c
+++ b/tools/testing/selftests/bpf/progs/test_d_path.c
@@ -17,6 +17,7 @@ int rets_close[MAX_FILES] = {};
 
 int called_stat = 0;
 int called_close = 0;
+int path_match_fallocate = 0;
 
 SEC("fentry/security_inode_getattr")
 int BPF_PROG(prog_stat, struct path *path, struct kstat *stat,
@@ -62,4 +63,26 @@ int BPF_PROG(prog_close, struct file *file, void *id)
        return 0;
 }
 
+SEC("fentry/vfs_fallocate")
+int BPF_PROG(prog_fallocate, struct file *file, int mode, loff_t offset, 
loff_t len)
+{
+       pid_t pid = bpf_get_current_pid_tgid() >> 32;
+       int ret = 0;
+       char path_fallocate[MAX_PATH_LEN] = {};
+
+       if (pid != my_pid)
+               return 0;
+
+       ret = bpf_d_path(&file->f_path,
+                        path_fallocate, MAX_PATH_LEN);
+       if (ret < 0)
+               return 0;
+
+       if (!path_fallocate[0])
+               return 0;
+
+       path_match_fallocate = 1;
+       return 0;
+}
+
 char _license[] SEC("license") = "GPL";
-- 
2.52.0


Reply via email to