]>
Commit | Line | Data |
---|---|---|
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ | |
2 | ||
3 | #include <fcntl.h> | |
4 | #include <sched.h> | |
5 | #include <stdlib.h> | |
6 | #include <sys/socket.h> | |
7 | #include <sys/stat.h> | |
8 | #include <sysexits.h> | |
9 | #include <unistd.h> | |
10 | ||
11 | #include "sd-id128.h" | |
12 | ||
13 | #include "alloc-util.h" | |
14 | #include "fd-util.h" | |
15 | #include "fileio.h" | |
16 | #include "namespace-util.h" | |
17 | #include "namespace.h" | |
18 | #include "pidref.h" | |
19 | #include "process-util.h" | |
20 | #include "string-util.h" | |
21 | #include "tests.h" | |
22 | #include "uid-range.h" | |
23 | #include "user-util.h" | |
24 | #include "virt.h" | |
25 | ||
26 | TEST(namespace_cleanup_tmpdir) { | |
27 | { | |
28 | _cleanup_(namespace_cleanup_tmpdirp) char *dir; | |
29 | assert_se(dir = strdup(RUN_SYSTEMD_EMPTY)); | |
30 | } | |
31 | ||
32 | { | |
33 | _cleanup_(namespace_cleanup_tmpdirp) char *dir; | |
34 | assert_se(dir = strdup("/tmp/systemd-test-namespace.XXXXXX")); | |
35 | assert_se(mkdtemp(dir)); | |
36 | } | |
37 | } | |
38 | ||
39 | static void test_tmpdir_one(const char *id, const char *A, const char *B) { | |
40 | _cleanup_free_ char *a, *b; | |
41 | struct stat x, y; | |
42 | char *c, *d; | |
43 | ||
44 | assert_se(setup_tmp_dirs(id, &a, &b) == 0); | |
45 | ||
46 | assert_se(stat(a, &x) >= 0); | |
47 | assert_se(stat(b, &y) >= 0); | |
48 | ||
49 | assert_se(S_ISDIR(x.st_mode)); | |
50 | assert_se(S_ISDIR(y.st_mode)); | |
51 | ||
52 | if (!streq(a, RUN_SYSTEMD_EMPTY)) { | |
53 | assert_se(startswith(a, A)); | |
54 | assert_se((x.st_mode & 01777) == 0700); | |
55 | c = strjoina(a, "/tmp"); | |
56 | assert_se(stat(c, &x) >= 0); | |
57 | assert_se(S_ISDIR(x.st_mode)); | |
58 | assert_se(FLAGS_SET(x.st_mode, 01777)); | |
59 | assert_se(rmdir(c) >= 0); | |
60 | assert_se(rmdir(a) >= 0); | |
61 | } | |
62 | ||
63 | if (!streq(b, RUN_SYSTEMD_EMPTY)) { | |
64 | assert_se(startswith(b, B)); | |
65 | assert_se((y.st_mode & 01777) == 0700); | |
66 | d = strjoina(b, "/tmp"); | |
67 | assert_se(stat(d, &y) >= 0); | |
68 | assert_se(S_ISDIR(y.st_mode)); | |
69 | assert_se(FLAGS_SET(y.st_mode, 01777)); | |
70 | assert_se(rmdir(d) >= 0); | |
71 | assert_se(rmdir(b) >= 0); | |
72 | } | |
73 | } | |
74 | ||
75 | TEST(tmpdir) { | |
76 | _cleanup_free_ char *x = NULL, *y = NULL, *z = NULL, *zz = NULL; | |
77 | sd_id128_t bid; | |
78 | ||
79 | assert_se(sd_id128_get_boot(&bid) >= 0); | |
80 | ||
81 | x = strjoin("/tmp/systemd-private-", SD_ID128_TO_STRING(bid), "-abcd.service-"); | |
82 | y = strjoin("/var/tmp/systemd-private-", SD_ID128_TO_STRING(bid), "-abcd.service-"); | |
83 | assert_se(x && y); | |
84 | ||
85 | test_tmpdir_one("abcd.service", x, y); | |
86 | ||
87 | z = strjoin("/tmp/systemd-private-", SD_ID128_TO_STRING(bid), "-sys-devices-pci0000:00-0000:00:1a.0-usb3-3\\x2d1-3\\x2d1:1.0-bluetooth-hci0.device-"); | |
88 | zz = strjoin("/var/tmp/systemd-private-", SD_ID128_TO_STRING(bid), "-sys-devices-pci0000:00-0000:00:1a.0-usb3-3\\x2d1-3\\x2d1:1.0-bluetooth-hci0.device-"); | |
89 | ||
90 | assert_se(z && zz); | |
91 | ||
92 | test_tmpdir_one("sys-devices-pci0000:00-0000:00:1a.0-usb3-3\\x2d1-3\\x2d1:1.0-bluetooth-hci0.device", z, zz); | |
93 | } | |
94 | ||
95 | static void test_shareable_ns(unsigned long nsflag) { | |
96 | _cleanup_close_pair_ int s[2] = EBADF_PAIR; | |
97 | bool permission_denied = false; | |
98 | pid_t pid1, pid2, pid3; | |
99 | int r, n = 0; | |
100 | siginfo_t si; | |
101 | ||
102 | if (geteuid() > 0) { | |
103 | (void) log_tests_skipped("not root"); | |
104 | return; | |
105 | } | |
106 | ||
107 | assert_se(socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, s) >= 0); | |
108 | ||
109 | pid1 = fork(); | |
110 | assert_se(pid1 >= 0); | |
111 | ||
112 | if (pid1 == 0) { | |
113 | r = setup_shareable_ns(s, nsflag); | |
114 | assert_se(r >= 0 || ERRNO_IS_NEG_PRIVILEGE(r)); | |
115 | _exit(r >= 0 ? r : EX_NOPERM); | |
116 | } | |
117 | ||
118 | pid2 = fork(); | |
119 | assert_se(pid2 >= 0); | |
120 | ||
121 | if (pid2 == 0) { | |
122 | r = setup_shareable_ns(s, nsflag); | |
123 | assert_se(r >= 0 || ERRNO_IS_NEG_PRIVILEGE(r)); | |
124 | _exit(r >= 0 ? r : EX_NOPERM); | |
125 | } | |
126 | ||
127 | pid3 = fork(); | |
128 | assert_se(pid3 >= 0); | |
129 | ||
130 | if (pid3 == 0) { | |
131 | r = setup_shareable_ns(s, nsflag); | |
132 | assert_se(r >= 0 || ERRNO_IS_NEG_PRIVILEGE(r)); | |
133 | _exit(r >= 0 ? r : EX_NOPERM); | |
134 | } | |
135 | ||
136 | r = wait_for_terminate(pid1, &si); | |
137 | assert_se(r >= 0); | |
138 | assert_se(si.si_code == CLD_EXITED); | |
139 | if (si.si_status == EX_NOPERM) | |
140 | permission_denied = true; | |
141 | else | |
142 | n += si.si_status; | |
143 | ||
144 | r = wait_for_terminate(pid2, &si); | |
145 | assert_se(r >= 0); | |
146 | assert_se(si.si_code == CLD_EXITED); | |
147 | if (si.si_status == EX_NOPERM) | |
148 | permission_denied = true; | |
149 | else | |
150 | n += si.si_status; | |
151 | ||
152 | r = wait_for_terminate(pid3, &si); | |
153 | assert_se(r >= 0); | |
154 | assert_se(si.si_code == CLD_EXITED); | |
155 | if (si.si_status == EX_NOPERM) | |
156 | permission_denied = true; | |
157 | else | |
158 | n += si.si_status; | |
159 | ||
160 | /* LSMs can cause setup_shareable_ns() to fail with permission denied, do not fail the test in that | |
161 | * case (e.g.: LXC with AppArmor on kernel < v6.2). */ | |
162 | if (permission_denied) | |
163 | return (void) log_tests_skipped("insufficient privileges"); | |
164 | ||
165 | assert_se(n == 1); | |
166 | } | |
167 | ||
168 | TEST(netns) { | |
169 | test_shareable_ns(CLONE_NEWNET); | |
170 | } | |
171 | ||
172 | TEST(ipcns) { | |
173 | test_shareable_ns(CLONE_NEWIPC); | |
174 | } | |
175 | ||
176 | TEST(fd_is_namespace) { | |
177 | _cleanup_close_ int fd = -EBADF; | |
178 | ||
179 | ASSERT_OK_ZERO(fd_is_namespace(STDIN_FILENO, NAMESPACE_NET)); | |
180 | ASSERT_OK_ZERO(fd_is_namespace(STDOUT_FILENO, NAMESPACE_NET)); | |
181 | ASSERT_OK_ZERO(fd_is_namespace(STDERR_FILENO, NAMESPACE_NET)); | |
182 | ||
183 | fd = namespace_open_by_type(NAMESPACE_MOUNT); | |
184 | if (IN_SET(fd, -ENOSYS, -ENOPKG)) { | |
185 | log_notice("Path %s not found, skipping test", "/proc/self/ns/mnt"); | |
186 | return; | |
187 | } | |
188 | ASSERT_OK(fd); | |
189 | ASSERT_OK_POSITIVE(fd_is_namespace(fd, NAMESPACE_MOUNT)); | |
190 | ASSERT_OK_ZERO(fd_is_namespace(fd, NAMESPACE_NET)); | |
191 | fd = safe_close(fd); | |
192 | ||
193 | ASSERT_OK(fd = namespace_open_by_type(NAMESPACE_IPC)); | |
194 | ASSERT_OK_POSITIVE(fd_is_namespace(fd, NAMESPACE_IPC)); | |
195 | fd = safe_close(fd); | |
196 | ||
197 | ASSERT_OK(fd = namespace_open_by_type(NAMESPACE_NET)); | |
198 | ASSERT_OK_POSITIVE(fd_is_namespace(fd, NAMESPACE_NET)); | |
199 | } | |
200 | ||
201 | TEST(protect_kernel_logs) { | |
202 | static const NamespaceParameters p = { | |
203 | .runtime_scope = RUNTIME_SCOPE_SYSTEM, | |
204 | .protect_kernel_logs = true, | |
205 | }; | |
206 | pid_t pid; | |
207 | int r; | |
208 | ||
209 | if (geteuid() > 0) { | |
210 | (void) log_tests_skipped("not root"); | |
211 | return; | |
212 | } | |
213 | ||
214 | /* In a container we likely don't have access to /dev/kmsg */ | |
215 | if (detect_container() > 0) { | |
216 | (void) log_tests_skipped("in container"); | |
217 | return; | |
218 | } | |
219 | ||
220 | pid = fork(); | |
221 | assert_se(pid >= 0); | |
222 | ||
223 | if (pid == 0) { | |
224 | _cleanup_close_ int fd = -EBADF; | |
225 | ||
226 | fd = open("/dev/kmsg", O_RDONLY | O_CLOEXEC); | |
227 | assert_se(fd > 0); | |
228 | ||
229 | r = setup_namespace(&p, NULL); | |
230 | assert_se(r == 0); | |
231 | ||
232 | assert_se(setresuid(UID_NOBODY, UID_NOBODY, UID_NOBODY) >= 0); | |
233 | assert_se(open("/dev/kmsg", O_RDONLY | O_CLOEXEC) < 0); | |
234 | assert_se(errno == EACCES); | |
235 | ||
236 | _exit(EXIT_SUCCESS); | |
237 | } | |
238 | ||
239 | assert_se(wait_for_terminate_and_check("ns-kernellogs", pid, WAIT_LOG) == EXIT_SUCCESS); | |
240 | } | |
241 | ||
242 | TEST(idmapping_supported) { | |
243 | assert_se(is_idmapping_supported("/run") >= 0); | |
244 | assert_se(is_idmapping_supported("/var/lib") >= 0); | |
245 | assert_se(is_idmapping_supported("/var/cache") >= 0); | |
246 | assert_se(is_idmapping_supported("/var/log") >= 0); | |
247 | assert_se(is_idmapping_supported("/etc") >= 0); | |
248 | } | |
249 | ||
250 | TEST(namespace_is_init) { | |
251 | int r; | |
252 | ||
253 | for (NamespaceType t = 0; t < _NAMESPACE_TYPE_MAX; t++) { | |
254 | r = namespace_is_init(t); | |
255 | if (r == -EBADR) | |
256 | log_info_errno(r, "In root namespace of type '%s': don't know", namespace_info[t].proc_name); | |
257 | else { | |
258 | ASSERT_OK(r); | |
259 | log_info("In root namespace of type '%s': %s", namespace_info[t].proc_name, yes_no(r)); | |
260 | } | |
261 | } | |
262 | } | |
263 | ||
264 | TEST(userns_get_base_uid) { | |
265 | _cleanup_close_ int fd = -EBADF; | |
266 | ||
267 | fd = userns_acquire("0 1 1", "0 2 1", /* setgroups_deny= */ true); | |
268 | if (ERRNO_IS_NEG_NOT_SUPPORTED(fd)) | |
269 | return (void) log_tests_skipped("userns is not supported"); | |
270 | if (ERRNO_IS_NEG_PRIVILEGE(fd)) | |
271 | return (void) log_tests_skipped("lacking userns privileges"); | |
272 | ||
273 | uid_t base_uid, base_gid; | |
274 | ASSERT_OK(userns_get_base_uid(fd, &base_uid, &base_gid)); | |
275 | ASSERT_EQ(base_uid, 1U); | |
276 | ASSERT_EQ(base_gid, 2U); | |
277 | ||
278 | ASSERT_ERROR(userns_get_base_uid(fd, &base_uid, NULL), EUCLEAN); | |
279 | ||
280 | fd = safe_close(fd); | |
281 | ||
282 | fd = userns_acquire_empty(); | |
283 | ASSERT_OK(fd); | |
284 | ||
285 | ASSERT_ERROR(userns_get_base_uid(fd, &base_uid, &base_gid), ENOMSG); | |
286 | } | |
287 | ||
288 | TEST(process_is_owned_by_uid) { | |
289 | int r; | |
290 | ||
291 | /* Test our own PID */ | |
292 | _cleanup_(pidref_done) PidRef pid = PIDREF_NULL; | |
293 | ASSERT_OK(pidref_set_self(&pid)); | |
294 | ASSERT_OK_POSITIVE(process_is_owned_by_uid(&pid, getuid())); | |
295 | pidref_done(&pid); | |
296 | ||
297 | if (getuid() != 0) | |
298 | return (void) log_tests_skipped("lacking userns privileges"); | |
299 | ||
300 | _cleanup_(uid_range_freep) UIDRange *range = NULL; | |
301 | ASSERT_OK(uid_range_load_userns(/* path= */ NULL, UID_RANGE_USERNS_INSIDE, &range)); | |
302 | if (!uid_range_contains(range, 1)) | |
303 | return (void) log_tests_skipped("UID 1 not included in userns UID delegation, skipping test"); | |
304 | ||
305 | /* Test a child that runs as uid 1 */ | |
306 | _cleanup_close_pair_ int p[2] = EBADF_PAIR; | |
307 | ASSERT_OK_ERRNO(pipe2(p, O_CLOEXEC)); | |
308 | ||
309 | r = pidref_safe_fork("(child)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL, &pid); | |
310 | ASSERT_OK(r); | |
311 | if (r == 0) { | |
312 | p[0] = safe_close(p[0]); | |
313 | ASSERT_OK(fully_set_uid_gid(1, 1, NULL, 0)); | |
314 | ASSERT_OK_EQ_ERRNO(write(p[1], &(const char[]) { 'x' }, 1), 1); | |
315 | p[1] = safe_close(p[1]); | |
316 | freeze(); | |
317 | } | |
318 | ||
319 | p[1] = safe_close(p[1]); | |
320 | char x = 0; | |
321 | ASSERT_OK_EQ_ERRNO(read(p[0], &x, 1), 1); | |
322 | ASSERT_EQ(x, 'x'); | |
323 | p[0] = safe_close(p[0]); | |
324 | ||
325 | ASSERT_OK_ZERO(process_is_owned_by_uid(&pid, getuid())); | |
326 | ||
327 | ASSERT_OK(pidref_kill(&pid, SIGKILL)); | |
328 | ASSERT_OK(pidref_wait_for_terminate(&pid, /* ret= */ NULL)); | |
329 | ||
330 | /* Test a child that runs in a userns as uid 1, but the userns is owned by us */ | |
331 | ASSERT_OK_ERRNO(pipe2(p, O_CLOEXEC)); | |
332 | ||
333 | _cleanup_close_pair_ int pp[2] = EBADF_PAIR; | |
334 | ASSERT_OK_ERRNO(pipe2(pp, O_CLOEXEC)); | |
335 | ||
336 | r = pidref_safe_fork("(child)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|FORK_NEW_USERNS, &pid); | |
337 | ASSERT_OK(r); | |
338 | if (r == 0) { | |
339 | p[0] = safe_close(p[0]); | |
340 | pp[1] = safe_close(pp[1]); | |
341 | ||
342 | x = 0; | |
343 | ASSERT_OK_EQ_ERRNO(read(pp[0], &x, 1), 1); | |
344 | ASSERT_EQ(x, 'x'); | |
345 | pp[0] = safe_close(pp[0]); | |
346 | ||
347 | ASSERT_OK(reset_uid_gid()); | |
348 | ||
349 | ASSERT_OK_EQ_ERRNO(write(p[1], &(const char[]) { 'x' }, 1), 1); | |
350 | p[1] = safe_close(p[1]); | |
351 | freeze(); | |
352 | } | |
353 | ||
354 | p[1] = safe_close(p[1]); | |
355 | pp[0] = safe_close(pp[0]); | |
356 | ||
357 | ASSERT_OK(write_string_file(procfs_file_alloca(pid.pid, "uid_map"), "0 1 1\n", 0)); | |
358 | ASSERT_OK(write_string_file(procfs_file_alloca(pid.pid, "setgroups"), "deny", 0)); | |
359 | ASSERT_OK(write_string_file(procfs_file_alloca(pid.pid, "gid_map"), "0 1 1\n", 0)); | |
360 | ||
361 | ASSERT_OK_EQ_ERRNO(write(pp[1], &(const char[]) { 'x' }, 1), 1); | |
362 | pp[1] = safe_close(pp[1]); | |
363 | ||
364 | x = 0; | |
365 | ASSERT_OK_EQ_ERRNO(read(p[0], &x, 1), 1); | |
366 | ASSERT_EQ(x, 'x'); | |
367 | p[0] = safe_close(p[0]); | |
368 | ||
369 | ASSERT_OK_POSITIVE(process_is_owned_by_uid(&pid, getuid())); | |
370 | ||
371 | ASSERT_OK(pidref_kill(&pid, SIGKILL)); | |
372 | ASSERT_OK(pidref_wait_for_terminate(&pid, /* ret= */ NULL)); | |
373 | } | |
374 | ||
375 | TEST(namespace_get_leader) { | |
376 | int r; | |
377 | ||
378 | _cleanup_(pidref_done) PidRef original = PIDREF_NULL; | |
379 | ASSERT_OK(pidref_set_self(&original)); | |
380 | ||
381 | _cleanup_(pidref_done) PidRef pid = PIDREF_NULL; | |
382 | r = pidref_safe_fork("(child)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|FORK_NEW_MOUNTNS|FORK_WAIT|FORK_LOG, &pid); | |
383 | ASSERT_OK(r); | |
384 | if (r == 0) { | |
385 | ||
386 | _cleanup_(pidref_done) PidRef pid2 = PIDREF_NULL; | |
387 | r = pidref_safe_fork("(child)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|FORK_WAIT|FORK_LOG, &pid2); | |
388 | ASSERT_OK(r); | |
389 | ||
390 | if (r == 0) { | |
391 | log_info("PID hierarchy: " PID_FMT " ← " PID_FMT " ← " PID_FMT, original.pid, pid.pid, pid2.pid); | |
392 | ||
393 | _cleanup_(pidref_done) PidRef self = PIDREF_NULL; | |
394 | ASSERT_OK(pidref_set_self(&self)); | |
395 | ASSERT_TRUE(pidref_equal(&self, &pid2)); | |
396 | ||
397 | _cleanup_(pidref_done) PidRef parent = PIDREF_NULL; | |
398 | ASSERT_OK(pidref_set_parent(&parent)); | |
399 | ASSERT_TRUE(pidref_equal(&parent, &pid)); | |
400 | ASSERT_TRUE(!pidref_equal(&self, &pid)); | |
401 | ASSERT_TRUE(!pidref_equal(&self, &parent)); | |
402 | ||
403 | _cleanup_(pidref_done) PidRef grandparent = PIDREF_NULL; | |
404 | ASSERT_OK(pidref_get_ppid_as_pidref(&parent, &grandparent)); | |
405 | ASSERT_TRUE(pidref_equal(&grandparent, &original)); | |
406 | ASSERT_TRUE(!pidref_equal(&grandparent, &self)); | |
407 | ASSERT_TRUE(!pidref_equal(&grandparent, &pid)); | |
408 | ASSERT_TRUE(!pidref_equal(&grandparent, &pid2)); | |
409 | ASSERT_TRUE(!pidref_equal(&grandparent, &parent)); | |
410 | ||
411 | _cleanup_(pidref_done) PidRef leader = PIDREF_NULL; | |
412 | ASSERT_OK(namespace_get_leader(&self, NAMESPACE_MOUNT, &leader)); | |
413 | ASSERT_TRUE(pidref_equal(&parent, &leader)); | |
414 | ASSERT_TRUE(pidref_equal(&pid, &leader)); | |
415 | ASSERT_TRUE(!pidref_equal(&self, &leader)); | |
416 | ASSERT_TRUE(!pidref_equal(&pid2, &leader)); | |
417 | ASSERT_TRUE(!pidref_equal(&original, &leader)); | |
418 | ASSERT_TRUE(!pidref_equal(&grandparent, &leader)); | |
419 | } | |
420 | } | |
421 | } | |
422 | ||
423 | TEST(detach_mount_namespace_harder) { | |
424 | _cleanup_(pidref_done) PidRef pid = PIDREF_NULL; | |
425 | _cleanup_close_pair_ int p[2] = EBADF_PAIR; | |
426 | char x = 0; | |
427 | int r; | |
428 | ||
429 | ASSERT_OK_ERRNO(pipe2(p, O_CLOEXEC)); | |
430 | ||
431 | ASSERT_OK(r = pidref_safe_fork("(child)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|FORK_LOG, &pid)); | |
432 | if (r == 0) { | |
433 | p[0] = safe_close(p[0]); | |
434 | ||
435 | ASSERT_OK(detach_mount_namespace_harder(0, 0)); | |
436 | ||
437 | ASSERT_OK_EQ_ERRNO(write(p[1], &(const char[]) { 'x' }, 1), 1); | |
438 | freeze(); | |
439 | } | |
440 | ||
441 | p[1] = safe_close(p[1]); | |
442 | ASSERT_OK_EQ_ERRNO(read(p[0], &x, 1), 1); | |
443 | ASSERT_EQ(x, 'x'); | |
444 | ||
445 | ASSERT_OK_POSITIVE(pidref_in_same_namespace(NULL, &pid, NAMESPACE_USER)); | |
446 | ASSERT_OK_ZERO(pidref_in_same_namespace(NULL, &pid, NAMESPACE_MOUNT)); | |
447 | } | |
448 | ||
449 | static int intro(void) { | |
450 | if (!have_namespaces()) | |
451 | return log_tests_skipped("Don't have namespace support or lacking privileges"); | |
452 | ||
453 | return EXIT_SUCCESS; | |
454 | } | |
455 | ||
456 | DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro); |