1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Landlock tests - Common user space base |
4 | * |
5 | * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net> |
6 | * Copyright © 2019-2020 ANSSI |
7 | */ |
8 | |
9 | #define _GNU_SOURCE |
10 | #include <errno.h> |
11 | #include <fcntl.h> |
12 | #include <linux/landlock.h> |
13 | #include <string.h> |
14 | #include <sys/prctl.h> |
15 | #include <sys/socket.h> |
16 | #include <sys/types.h> |
17 | |
18 | #include "common.h" |
19 | |
20 | #ifndef O_PATH |
21 | #define O_PATH 010000000 |
22 | #endif |
23 | |
24 | TEST(inconsistent_attr) |
25 | { |
26 | const long page_size = sysconf(_SC_PAGESIZE); |
27 | char *const buf = malloc(page_size + 1); |
28 | struct landlock_ruleset_attr *const ruleset_attr = (void *)buf; |
29 | |
30 | ASSERT_NE(NULL, buf); |
31 | |
32 | /* Checks copy_from_user(). */ |
33 | ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, 0, 0)); |
34 | /* The size if less than sizeof(struct landlock_attr_enforce). */ |
35 | ASSERT_EQ(EINVAL, errno); |
36 | ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, 1, 0)); |
37 | ASSERT_EQ(EINVAL, errno); |
38 | ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, 7, 0)); |
39 | ASSERT_EQ(EINVAL, errno); |
40 | |
41 | ASSERT_EQ(-1, landlock_create_ruleset(NULL, 1, 0)); |
42 | /* The size if less than sizeof(struct landlock_attr_enforce). */ |
43 | ASSERT_EQ(EFAULT, errno); |
44 | |
45 | ASSERT_EQ(-1, landlock_create_ruleset( |
46 | NULL, sizeof(struct landlock_ruleset_attr), 0)); |
47 | ASSERT_EQ(EFAULT, errno); |
48 | |
49 | ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, page_size + 1, 0)); |
50 | ASSERT_EQ(E2BIG, errno); |
51 | |
52 | /* Checks minimal valid attribute size. */ |
53 | ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, 8, 0)); |
54 | ASSERT_EQ(ENOMSG, errno); |
55 | ASSERT_EQ(-1, landlock_create_ruleset( |
56 | ruleset_attr, |
57 | sizeof(struct landlock_ruleset_attr), 0)); |
58 | ASSERT_EQ(ENOMSG, errno); |
59 | ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, page_size, 0)); |
60 | ASSERT_EQ(ENOMSG, errno); |
61 | |
62 | /* Checks non-zero value. */ |
63 | buf[page_size - 2] = '.'; |
64 | ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, page_size, 0)); |
65 | ASSERT_EQ(E2BIG, errno); |
66 | |
67 | ASSERT_EQ(-1, landlock_create_ruleset(ruleset_attr, page_size + 1, 0)); |
68 | ASSERT_EQ(E2BIG, errno); |
69 | |
70 | free(buf); |
71 | } |
72 | |
73 | TEST(abi_version) |
74 | { |
75 | const struct landlock_ruleset_attr ruleset_attr = { |
76 | .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, |
77 | }; |
78 | ASSERT_EQ(4, landlock_create_ruleset(NULL, 0, |
79 | LANDLOCK_CREATE_RULESET_VERSION)); |
80 | |
81 | ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0, |
82 | LANDLOCK_CREATE_RULESET_VERSION)); |
83 | ASSERT_EQ(EINVAL, errno); |
84 | |
85 | ASSERT_EQ(-1, landlock_create_ruleset(NULL, sizeof(ruleset_attr), |
86 | LANDLOCK_CREATE_RULESET_VERSION)); |
87 | ASSERT_EQ(EINVAL, errno); |
88 | |
89 | ASSERT_EQ(-1, |
90 | landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), |
91 | LANDLOCK_CREATE_RULESET_VERSION)); |
92 | ASSERT_EQ(EINVAL, errno); |
93 | |
94 | ASSERT_EQ(-1, landlock_create_ruleset(NULL, 0, |
95 | LANDLOCK_CREATE_RULESET_VERSION | |
96 | 1 << 31)); |
97 | ASSERT_EQ(EINVAL, errno); |
98 | } |
99 | |
100 | /* Tests ordering of syscall argument checks. */ |
101 | TEST(create_ruleset_checks_ordering) |
102 | { |
103 | const int last_flag = LANDLOCK_CREATE_RULESET_VERSION; |
104 | const int invalid_flag = last_flag << 1; |
105 | int ruleset_fd; |
106 | const struct landlock_ruleset_attr ruleset_attr = { |
107 | .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, |
108 | }; |
109 | |
110 | /* Checks priority for invalid flags. */ |
111 | ASSERT_EQ(-1, landlock_create_ruleset(NULL, 0, invalid_flag)); |
112 | ASSERT_EQ(EINVAL, errno); |
113 | |
114 | ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0, invalid_flag)); |
115 | ASSERT_EQ(EINVAL, errno); |
116 | |
117 | ASSERT_EQ(-1, landlock_create_ruleset(NULL, sizeof(ruleset_attr), |
118 | invalid_flag)); |
119 | ASSERT_EQ(EINVAL, errno); |
120 | |
121 | ASSERT_EQ(-1, |
122 | landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), |
123 | invalid_flag)); |
124 | ASSERT_EQ(EINVAL, errno); |
125 | |
126 | /* Checks too big ruleset_attr size. */ |
127 | ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, -1, 0)); |
128 | ASSERT_EQ(E2BIG, errno); |
129 | |
130 | /* Checks too small ruleset_attr size. */ |
131 | ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0, 0)); |
132 | ASSERT_EQ(EINVAL, errno); |
133 | ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 1, 0)); |
134 | ASSERT_EQ(EINVAL, errno); |
135 | |
136 | /* Checks valid call. */ |
137 | ruleset_fd = |
138 | landlock_create_ruleset(attr: &ruleset_attr, size: sizeof(ruleset_attr), flags: 0); |
139 | ASSERT_LE(0, ruleset_fd); |
140 | ASSERT_EQ(0, close(ruleset_fd)); |
141 | } |
142 | |
143 | /* Tests ordering of syscall argument checks. */ |
144 | TEST(add_rule_checks_ordering) |
145 | { |
146 | const struct landlock_ruleset_attr ruleset_attr = { |
147 | .handled_access_fs = LANDLOCK_ACCESS_FS_EXECUTE, |
148 | }; |
149 | struct landlock_path_beneath_attr path_beneath_attr = { |
150 | .allowed_access = LANDLOCK_ACCESS_FS_EXECUTE, |
151 | .parent_fd = -1, |
152 | }; |
153 | const int ruleset_fd = |
154 | landlock_create_ruleset(attr: &ruleset_attr, size: sizeof(ruleset_attr), flags: 0); |
155 | |
156 | ASSERT_LE(0, ruleset_fd); |
157 | |
158 | /* Checks invalid flags. */ |
159 | ASSERT_EQ(-1, landlock_add_rule(-1, 0, NULL, 1)); |
160 | ASSERT_EQ(EINVAL, errno); |
161 | |
162 | /* Checks invalid ruleset FD. */ |
163 | ASSERT_EQ(-1, landlock_add_rule(-1, 0, NULL, 0)); |
164 | ASSERT_EQ(EBADF, errno); |
165 | |
166 | /* Checks invalid rule type. */ |
167 | ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, 0, NULL, 0)); |
168 | ASSERT_EQ(EINVAL, errno); |
169 | |
170 | /* Checks invalid rule attr. */ |
171 | ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, |
172 | NULL, 0)); |
173 | ASSERT_EQ(EFAULT, errno); |
174 | |
175 | /* Checks invalid path_beneath.parent_fd. */ |
176 | ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, |
177 | &path_beneath_attr, 0)); |
178 | ASSERT_EQ(EBADF, errno); |
179 | |
180 | /* Checks valid call. */ |
181 | path_beneath_attr.parent_fd = |
182 | open("/tmp" , O_PATH | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC); |
183 | ASSERT_LE(0, path_beneath_attr.parent_fd); |
184 | ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, |
185 | &path_beneath_attr, 0)); |
186 | ASSERT_EQ(0, close(path_beneath_attr.parent_fd)); |
187 | ASSERT_EQ(0, close(ruleset_fd)); |
188 | } |
189 | |
190 | /* Tests ordering of syscall argument and permission checks. */ |
191 | TEST(restrict_self_checks_ordering) |
192 | { |
193 | const struct landlock_ruleset_attr ruleset_attr = { |
194 | .handled_access_fs = LANDLOCK_ACCESS_FS_EXECUTE, |
195 | }; |
196 | struct landlock_path_beneath_attr path_beneath_attr = { |
197 | .allowed_access = LANDLOCK_ACCESS_FS_EXECUTE, |
198 | .parent_fd = -1, |
199 | }; |
200 | const int ruleset_fd = |
201 | landlock_create_ruleset(attr: &ruleset_attr, size: sizeof(ruleset_attr), flags: 0); |
202 | |
203 | ASSERT_LE(0, ruleset_fd); |
204 | path_beneath_attr.parent_fd = |
205 | open("/tmp" , O_PATH | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC); |
206 | ASSERT_LE(0, path_beneath_attr.parent_fd); |
207 | ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, |
208 | &path_beneath_attr, 0)); |
209 | ASSERT_EQ(0, close(path_beneath_attr.parent_fd)); |
210 | |
211 | /* Checks unprivileged enforcement without no_new_privs. */ |
212 | drop_caps(_metadata); |
213 | ASSERT_EQ(-1, landlock_restrict_self(-1, -1)); |
214 | ASSERT_EQ(EPERM, errno); |
215 | ASSERT_EQ(-1, landlock_restrict_self(-1, 0)); |
216 | ASSERT_EQ(EPERM, errno); |
217 | ASSERT_EQ(-1, landlock_restrict_self(ruleset_fd, 0)); |
218 | ASSERT_EQ(EPERM, errno); |
219 | |
220 | ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); |
221 | |
222 | /* Checks invalid flags. */ |
223 | ASSERT_EQ(-1, landlock_restrict_self(-1, -1)); |
224 | ASSERT_EQ(EINVAL, errno); |
225 | |
226 | /* Checks invalid ruleset FD. */ |
227 | ASSERT_EQ(-1, landlock_restrict_self(-1, 0)); |
228 | ASSERT_EQ(EBADF, errno); |
229 | |
230 | /* Checks valid call. */ |
231 | ASSERT_EQ(0, landlock_restrict_self(ruleset_fd, 0)); |
232 | ASSERT_EQ(0, close(ruleset_fd)); |
233 | } |
234 | |
235 | TEST(ruleset_fd_io) |
236 | { |
237 | struct landlock_ruleset_attr ruleset_attr = { |
238 | .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, |
239 | }; |
240 | int ruleset_fd; |
241 | char buf; |
242 | |
243 | drop_caps(_metadata); |
244 | ruleset_fd = |
245 | landlock_create_ruleset(attr: &ruleset_attr, size: sizeof(ruleset_attr), flags: 0); |
246 | ASSERT_LE(0, ruleset_fd); |
247 | |
248 | ASSERT_EQ(-1, write(ruleset_fd, "." , 1)); |
249 | ASSERT_EQ(EINVAL, errno); |
250 | ASSERT_EQ(-1, read(ruleset_fd, &buf, 1)); |
251 | ASSERT_EQ(EINVAL, errno); |
252 | |
253 | ASSERT_EQ(0, close(ruleset_fd)); |
254 | } |
255 | |
256 | /* Tests enforcement of a ruleset FD transferred through a UNIX socket. */ |
257 | TEST(ruleset_fd_transfer) |
258 | { |
259 | struct landlock_ruleset_attr ruleset_attr = { |
260 | .handled_access_fs = LANDLOCK_ACCESS_FS_READ_DIR, |
261 | }; |
262 | struct landlock_path_beneath_attr path_beneath_attr = { |
263 | .allowed_access = LANDLOCK_ACCESS_FS_READ_DIR, |
264 | }; |
265 | int ruleset_fd_tx, dir_fd; |
266 | int socket_fds[2]; |
267 | pid_t child; |
268 | int status; |
269 | |
270 | drop_caps(_metadata); |
271 | |
272 | /* Creates a test ruleset with a simple rule. */ |
273 | ruleset_fd_tx = |
274 | landlock_create_ruleset(attr: &ruleset_attr, size: sizeof(ruleset_attr), flags: 0); |
275 | ASSERT_LE(0, ruleset_fd_tx); |
276 | path_beneath_attr.parent_fd = |
277 | open("/tmp" , O_PATH | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC); |
278 | ASSERT_LE(0, path_beneath_attr.parent_fd); |
279 | ASSERT_EQ(0, |
280 | landlock_add_rule(ruleset_fd_tx, LANDLOCK_RULE_PATH_BENEATH, |
281 | &path_beneath_attr, 0)); |
282 | ASSERT_EQ(0, close(path_beneath_attr.parent_fd)); |
283 | |
284 | /* Sends the ruleset FD over a socketpair and then close it. */ |
285 | ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, |
286 | socket_fds)); |
287 | ASSERT_EQ(0, send_fd(socket_fds[0], ruleset_fd_tx)); |
288 | ASSERT_EQ(0, close(socket_fds[0])); |
289 | ASSERT_EQ(0, close(ruleset_fd_tx)); |
290 | |
291 | child = fork(); |
292 | ASSERT_LE(0, child); |
293 | if (child == 0) { |
294 | const int ruleset_fd_rx = recv_fd(usock: socket_fds[1]); |
295 | |
296 | ASSERT_LE(0, ruleset_fd_rx); |
297 | ASSERT_EQ(0, close(socket_fds[1])); |
298 | |
299 | /* Enforces the received ruleset on the child. */ |
300 | ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); |
301 | ASSERT_EQ(0, landlock_restrict_self(ruleset_fd_rx, 0)); |
302 | ASSERT_EQ(0, close(ruleset_fd_rx)); |
303 | |
304 | /* Checks that the ruleset enforcement. */ |
305 | ASSERT_EQ(-1, open("/" , O_RDONLY | O_DIRECTORY | O_CLOEXEC)); |
306 | ASSERT_EQ(EACCES, errno); |
307 | dir_fd = open("/tmp" , O_RDONLY | O_DIRECTORY | O_CLOEXEC); |
308 | ASSERT_LE(0, dir_fd); |
309 | ASSERT_EQ(0, close(dir_fd)); |
310 | _exit(_metadata->exit_code); |
311 | return; |
312 | } |
313 | |
314 | ASSERT_EQ(0, close(socket_fds[1])); |
315 | |
316 | /* Checks that the parent is unrestricted. */ |
317 | dir_fd = open("/" , O_RDONLY | O_DIRECTORY | O_CLOEXEC); |
318 | ASSERT_LE(0, dir_fd); |
319 | ASSERT_EQ(0, close(dir_fd)); |
320 | dir_fd = open("/tmp" , O_RDONLY | O_DIRECTORY | O_CLOEXEC); |
321 | ASSERT_LE(0, dir_fd); |
322 | ASSERT_EQ(0, close(dir_fd)); |
323 | |
324 | ASSERT_EQ(child, waitpid(child, &status, 0)); |
325 | ASSERT_EQ(1, WIFEXITED(status)); |
326 | ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); |
327 | } |
328 | |
329 | TEST_HARNESS_MAIN |
330 | |