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, fmt: "/proc/%d/fd/%d" , pid, fd); |
38 | return readlink(buf, src.paths[src.cnt++], MAX_PATH_LEN); |
39 | } |
40 | |
41 | static int trigger_fstat_events(pid_t pid) |
42 | { |
43 | int sockfd = -1, procfd = -1, devfd = -1; |
44 | int localfd = -1, indicatorfd = -1; |
45 | int pipefd[2] = { -1, -1 }; |
46 | struct stat fileStat; |
47 | int ret = -1; |
48 | |
49 | /* unmountable pseudo-filesystems */ |
50 | if (CHECK(pipe(pipefd) < 0, "trigger" , "pipe failed\n" )) |
51 | return ret; |
52 | /* unmountable pseudo-filesystems */ |
53 | sockfd = socket(AF_INET, SOCK_STREAM, 0); |
54 | if (CHECK(sockfd < 0, "trigger" , "socket failed\n" )) |
55 | goto out_close; |
56 | /* mountable pseudo-filesystems */ |
57 | procfd = open("/proc/self/comm" , O_RDONLY); |
58 | if (CHECK(procfd < 0, "trigger" , "open /proc/self/comm failed\n" )) |
59 | goto out_close; |
60 | devfd = open("/dev/urandom" , O_RDONLY); |
61 | if (CHECK(devfd < 0, "trigger" , "open /dev/urandom failed\n" )) |
62 | goto out_close; |
63 | localfd = open("/tmp/d_path_loadgen.txt" , O_CREAT | O_RDONLY, 0644); |
64 | if (CHECK(localfd < 0, "trigger" , "open /tmp/d_path_loadgen.txt failed\n" )) |
65 | goto out_close; |
66 | /* bpf_d_path will return path with (deleted) */ |
67 | remove("/tmp/d_path_loadgen.txt" ); |
68 | indicatorfd = open("/tmp/" , O_PATH); |
69 | if (CHECK(indicatorfd < 0, "trigger" , "open /tmp/ failed\n" )) |
70 | goto out_close; |
71 | |
72 | ret = set_pathname(fd: pipefd[0], pid); |
73 | if (CHECK(ret < 0, "trigger" , "set_pathname failed for pipe[0]\n" )) |
74 | goto out_close; |
75 | ret = set_pathname(fd: pipefd[1], pid); |
76 | if (CHECK(ret < 0, "trigger" , "set_pathname failed for pipe[1]\n" )) |
77 | goto out_close; |
78 | ret = set_pathname(fd: sockfd, pid); |
79 | if (CHECK(ret < 0, "trigger" , "set_pathname failed for socket\n" )) |
80 | goto out_close; |
81 | ret = set_pathname(fd: procfd, pid); |
82 | if (CHECK(ret < 0, "trigger" , "set_pathname failed for proc\n" )) |
83 | goto out_close; |
84 | ret = set_pathname(fd: devfd, pid); |
85 | if (CHECK(ret < 0, "trigger" , "set_pathname failed for dev\n" )) |
86 | goto out_close; |
87 | ret = set_pathname(fd: localfd, pid); |
88 | if (CHECK(ret < 0, "trigger" , "set_pathname failed for file\n" )) |
89 | goto out_close; |
90 | ret = set_pathname(fd: indicatorfd, pid); |
91 | if (CHECK(ret < 0, "trigger" , "set_pathname failed for dir\n" )) |
92 | goto out_close; |
93 | |
94 | /* triggers vfs_getattr */ |
95 | fstat(pipefd[0], &fileStat); |
96 | fstat(pipefd[1], &fileStat); |
97 | fstat(sockfd, &fileStat); |
98 | fstat(procfd, &fileStat); |
99 | fstat(devfd, &fileStat); |
100 | fstat(localfd, &fileStat); |
101 | fstat(indicatorfd, &fileStat); |
102 | |
103 | out_close: |
104 | /* sys_close no longer triggers filp_close, but we can |
105 | * call sys_close_range instead which still does |
106 | */ |
107 | #define close(fd) syscall(__NR_close_range, fd, fd, 0) |
108 | |
109 | close(pipefd[0]); |
110 | close(pipefd[1]); |
111 | close(sockfd); |
112 | close(procfd); |
113 | close(devfd); |
114 | close(localfd); |
115 | close(indicatorfd); |
116 | |
117 | #undef close |
118 | return ret; |
119 | } |
120 | |
121 | static void test_d_path_basic(void) |
122 | { |
123 | struct test_d_path__bss *bss; |
124 | struct test_d_path *skel; |
125 | int err; |
126 | |
127 | skel = test_d_path__open_and_load(); |
128 | if (CHECK(!skel, "setup" , "d_path skeleton failed\n" )) |
129 | goto cleanup; |
130 | |
131 | err = test_d_path__attach(skel); |
132 | if (CHECK(err, "setup" , "attach failed: %d\n" , err)) |
133 | goto cleanup; |
134 | |
135 | bss = skel->bss; |
136 | bss->my_pid = getpid(); |
137 | |
138 | err = trigger_fstat_events(pid: bss->my_pid); |
139 | if (err < 0) |
140 | goto cleanup; |
141 | |
142 | if (CHECK(!bss->called_stat, |
143 | "stat" , |
144 | "trampoline for security_inode_getattr was not called\n" )) |
145 | goto cleanup; |
146 | |
147 | if (CHECK(!bss->called_close, |
148 | "close" , |
149 | "trampoline for filp_close was not called\n" )) |
150 | goto cleanup; |
151 | |
152 | for (int i = 0; i < MAX_FILES; i++) { |
153 | CHECK(strncmp(src.paths[i], bss->paths_stat[i], MAX_PATH_LEN), |
154 | "check" , |
155 | "failed to get stat path[%d]: %s vs %s\n" , |
156 | i, src.paths[i], bss->paths_stat[i]); |
157 | CHECK(strncmp(src.paths[i], bss->paths_close[i], MAX_PATH_LEN), |
158 | "check" , |
159 | "failed to get close path[%d]: %s vs %s\n" , |
160 | i, src.paths[i], bss->paths_close[i]); |
161 | /* The d_path helper returns size plus NUL char, hence + 1 */ |
162 | CHECK(bss->rets_stat[i] != strlen(bss->paths_stat[i]) + 1, |
163 | "check" , |
164 | "failed to match stat return [%d]: %d vs %zd [%s]\n" , |
165 | i, bss->rets_stat[i], strlen(bss->paths_stat[i]) + 1, |
166 | bss->paths_stat[i]); |
167 | CHECK(bss->rets_close[i] != strlen(bss->paths_stat[i]) + 1, |
168 | "check" , |
169 | "failed to match stat return [%d]: %d vs %zd [%s]\n" , |
170 | i, bss->rets_close[i], strlen(bss->paths_close[i]) + 1, |
171 | bss->paths_stat[i]); |
172 | } |
173 | |
174 | cleanup: |
175 | test_d_path__destroy(skel); |
176 | } |
177 | |
178 | static void test_d_path_check_rdonly_mem(void) |
179 | { |
180 | struct test_d_path_check_rdonly_mem *skel; |
181 | |
182 | skel = test_d_path_check_rdonly_mem__open_and_load(); |
183 | ASSERT_ERR_PTR(skel, "unexpected_load_overwriting_rdonly_mem" ); |
184 | |
185 | test_d_path_check_rdonly_mem__destroy(skel); |
186 | } |
187 | |
188 | static void test_d_path_check_types(void) |
189 | { |
190 | struct test_d_path_check_types *skel; |
191 | |
192 | skel = test_d_path_check_types__open_and_load(); |
193 | ASSERT_ERR_PTR(skel, "unexpected_load_passing_wrong_type" ); |
194 | |
195 | test_d_path_check_types__destroy(skel); |
196 | } |
197 | |
198 | void test_d_path(void) |
199 | { |
200 | if (test__start_subtest("basic" )) |
201 | test_d_path_basic(); |
202 | |
203 | if (test__start_subtest("check_rdonly_mem" )) |
204 | test_d_path_check_rdonly_mem(); |
205 | |
206 | if (test__start_subtest("check_alloc_mem" )) |
207 | test_d_path_check_types(); |
208 | } |
209 | |