1 // SPDX-License-Identifier: GPL-2.0 2 #define _GNU_SOURCE 3 #include <test_progs.h> 4 #include <sys/stat.h> 5 #include <linux/sched.h> 6 #include <sys/syscall.h> 7 8 #define MAX_PATH_LEN 128 9 #define MAX_FILES 7 10 11 #include "test_d_path.skel.h" 12 #include "test_d_path_check_rdonly_mem.skel.h" 13 #include "test_d_path_check_types.skel.h" 14 15 /* sys_close_range is not around for long time, so let's 16 * make sure we can call it on systems with older glibc 17 */ 18 #ifndef __NR_close_range 19 #ifdef __alpha__ 20 #define __NR_close_range 546 21 #else 22 #define __NR_close_range 436 23 #endif 24 #endif 25 26 static int duration; 27 28 static struct { 29 __u32 cnt; 30 char paths[MAX_FILES][MAX_PATH_LEN]; 31 } src; 32 33 static int set_pathname(int fd, pid_t pid) 34 { 35 char buf[MAX_PATH_LEN]; 36 37 snprintf(buf, MAX_PATH_LEN, "/proc/%d/fd/%d", pid, fd); 38 return readlink(buf, src.paths[src.cnt++], MAX_PATH_LEN); 39 } 40 41 static inline long syscall_close(int fd) 42 { 43 return syscall(__NR_close_range, 44 (unsigned int)fd, 45 (unsigned int)fd, 46 0u); 47 } 48 49 static int trigger_fstat_events(pid_t pid) 50 { 51 int sockfd = -1, procfd = -1, devfd = -1; 52 int localfd = -1, indicatorfd = -1; 53 int pipefd[2] = { -1, -1 }; 54 struct stat fileStat; 55 int ret = -1; 56 57 /* unmountable pseudo-filesystems */ 58 if (CHECK(pipe(pipefd) < 0, "trigger", "pipe failed\n")) 59 return ret; 60 /* unmountable pseudo-filesystems */ 61 sockfd = socket(AF_INET, SOCK_STREAM, 0); 62 if (CHECK(sockfd < 0, "trigger", "socket failed\n")) 63 goto out_close; 64 /* mountable pseudo-filesystems */ 65 procfd = open("/proc/self/comm", O_RDONLY); 66 if (CHECK(procfd < 0, "trigger", "open /proc/self/comm failed\n")) 67 goto out_close; 68 devfd = open("/dev/urandom", O_RDONLY); 69 if (CHECK(devfd < 0, "trigger", "open /dev/urandom failed\n")) 70 goto out_close; 71 localfd = open("/tmp/d_path_loadgen.txt", O_CREAT | O_RDONLY, 0644); 72 if (CHECK(localfd < 0, "trigger", "open /tmp/d_path_loadgen.txt failed\n")) 73 goto out_close; 74 /* bpf_d_path will return path with (deleted) */ 75 remove("/tmp/d_path_loadgen.txt"); 76 indicatorfd = open("/tmp/", O_PATH); 77 if (CHECK(indicatorfd < 0, "trigger", "open /tmp/ failed\n")) 78 goto out_close; 79 80 ret = set_pathname(pipefd[0], pid); 81 if (CHECK(ret < 0, "trigger", "set_pathname failed for pipe[0]\n")) 82 goto out_close; 83 ret = set_pathname(pipefd[1], pid); 84 if (CHECK(ret < 0, "trigger", "set_pathname failed for pipe[1]\n")) 85 goto out_close; 86 ret = set_pathname(sockfd, pid); 87 if (CHECK(ret < 0, "trigger", "set_pathname failed for socket\n")) 88 goto out_close; 89 ret = set_pathname(procfd, pid); 90 if (CHECK(ret < 0, "trigger", "set_pathname failed for proc\n")) 91 goto out_close; 92 ret = set_pathname(devfd, pid); 93 if (CHECK(ret < 0, "trigger", "set_pathname failed for dev\n")) 94 goto out_close; 95 ret = set_pathname(localfd, pid); 96 if (CHECK(ret < 0, "trigger", "set_pathname failed for file\n")) 97 goto out_close; 98 ret = set_pathname(indicatorfd, pid); 99 if (CHECK(ret < 0, "trigger", "set_pathname failed for dir\n")) 100 goto out_close; 101 102 /* triggers vfs_getattr */ 103 fstat(pipefd[0], &fileStat); 104 fstat(pipefd[1], &fileStat); 105 fstat(sockfd, &fileStat); 106 fstat(procfd, &fileStat); 107 fstat(devfd, &fileStat); 108 fstat(localfd, &fileStat); 109 fstat(indicatorfd, &fileStat); 110 111 out_close: 112 /* sys_close no longer triggers filp_close, but we can 113 * call sys_close_range instead which still does 114 */ 115 syscall_close(pipefd[0]); 116 syscall_close(pipefd[1]); 117 syscall_close(sockfd); 118 syscall_close(procfd); 119 syscall_close(devfd); 120 syscall_close(localfd); 121 syscall_close(indicatorfd); 122 return ret; 123 } 124 125 static void attach_and_load(struct test_d_path **skel) 126 { 127 int err; 128 129 *skel = test_d_path__open_and_load(); 130 if (CHECK(!*skel, "setup", "d_path skeleton failed\n")) 131 goto cleanup; 132 133 err = test_d_path__attach(*skel); 134 if (CHECK(err, "setup", "attach failed: %d\n", err)) 135 goto cleanup; 136 137 (*skel)->bss->my_pid = getpid(); 138 return; 139 140 cleanup: 141 test_d_path__destroy(*skel); 142 *skel = NULL; 143 } 144 145 static void test_d_path_basic(void) 146 { 147 struct test_d_path__bss *bss; 148 struct test_d_path *skel; 149 int err; 150 151 attach_and_load(&skel); 152 if (!skel) 153 goto cleanup; 154 155 bss = skel->bss; 156 157 err = trigger_fstat_events(bss->my_pid); 158 if (err < 0) 159 goto cleanup; 160 161 if (CHECK(!bss->called_stat, 162 "stat", 163 "trampoline for security_inode_getattr was not called\n")) 164 goto cleanup; 165 166 if (CHECK(!bss->called_close, 167 "close", 168 "trampoline for filp_close was not called\n")) 169 goto cleanup; 170 171 for (int i = 0; i < MAX_FILES; i++) { 172 CHECK(strncmp(src.paths[i], bss->paths_stat[i], MAX_PATH_LEN), 173 "check", 174 "failed to get stat path[%d]: %s vs %s\n", 175 i, src.paths[i], bss->paths_stat[i]); 176 CHECK(strncmp(src.paths[i], bss->paths_close[i], MAX_PATH_LEN), 177 "check", 178 "failed to get close path[%d]: %s vs %s\n", 179 i, src.paths[i], bss->paths_close[i]); 180 /* The d_path helper returns size plus NUL char, hence + 1 */ 181 CHECK(bss->rets_stat[i] != strlen(bss->paths_stat[i]) + 1, 182 "check", 183 "failed to match stat return [%d]: %d vs %zd [%s]\n", 184 i, bss->rets_stat[i], strlen(bss->paths_stat[i]) + 1, 185 bss->paths_stat[i]); 186 CHECK(bss->rets_close[i] != strlen(bss->paths_stat[i]) + 1, 187 "check", 188 "failed to match stat return [%d]: %d vs %zd [%s]\n", 189 i, bss->rets_close[i], strlen(bss->paths_close[i]) + 1, 190 bss->paths_stat[i]); 191 } 192 193 cleanup: 194 test_d_path__destroy(skel); 195 } 196 197 static void test_d_path_check_rdonly_mem(void) 198 { 199 struct test_d_path_check_rdonly_mem *skel; 200 201 skel = test_d_path_check_rdonly_mem__open_and_load(); 202 ASSERT_ERR_PTR(skel, "unexpected_load_overwriting_rdonly_mem"); 203 204 test_d_path_check_rdonly_mem__destroy(skel); 205 } 206 207 static void test_d_path_check_types(void) 208 { 209 struct test_d_path_check_types *skel; 210 211 skel = test_d_path_check_types__open_and_load(); 212 ASSERT_ERR_PTR(skel, "unexpected_load_passing_wrong_type"); 213 214 test_d_path_check_types__destroy(skel); 215 } 216 217 /* Check if the verifier correctly generates code for 218 * accessing the memory modified by d_path helper. 219 */ 220 static void test_d_path_mem_access(void) 221 { 222 int localfd = -1; 223 char path_template[] = "/dev/shm/d_path_loadgen.XXXXXX"; 224 struct test_d_path__bss *bss; 225 struct test_d_path *skel; 226 227 attach_and_load(&skel); 228 if (!skel) 229 goto cleanup; 230 231 bss = skel->bss; 232 233 localfd = mkstemp(path_template); 234 if (CHECK(localfd < 0, "trigger", "mkstemp failed\n")) 235 goto cleanup; 236 237 if (CHECK(fallocate(localfd, 0, 0, 1024) < 0, "trigger", "fallocate failed\n")) 238 goto cleanup; 239 remove(path_template); 240 241 if (CHECK(!bss->path_match_fallocate, "check", 242 "failed to read fallocate path")) 243 goto cleanup; 244 245 cleanup: 246 syscall_close(localfd); 247 test_d_path__destroy(skel); 248 } 249 250 void test_d_path(void) 251 { 252 if (test__start_subtest("basic")) 253 test_d_path_basic(); 254 255 if (test__start_subtest("check_rdonly_mem")) 256 test_d_path_check_rdonly_mem(); 257 258 if (test__start_subtest("check_alloc_mem")) 259 test_d_path_check_types(); 260 261 if (test__start_subtest("check_mem_access")) 262 test_d_path_mem_access(); 263 } 264