]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
83555251 LP |
2 | |
3 | #include <sys/mount.h> | |
10cdbb83 | 4 | #include <sys/statvfs.h> |
83555251 | 5 | |
c2a986d5 | 6 | #include "alloc-util.h" |
c75370cc | 7 | #include "capability-util.h" |
10cdbb83 LP |
8 | #include "fd-util.h" |
9 | #include "fileio.h" | |
9c653536 | 10 | #include "fs-util.h" |
4e9ef660 | 11 | #include "libmount-util.h" |
f63a2c48 | 12 | #include "missing_magic.h" |
51bb6a10 | 13 | #include "missing_mount.h" |
9c653536 | 14 | #include "mkdir.h" |
83555251 | 15 | #include "mount-util.h" |
61ef3051 | 16 | #include "mountpoint-util.h" |
10cdbb83 LP |
17 | #include "namespace-util.h" |
18 | #include "path-util.h" | |
19 | #include "process-util.h" | |
ea0f3289 | 20 | #include "random-util.h" |
10cdbb83 | 21 | #include "rm-rf.h" |
f63a2c48 | 22 | #include "stat-util.h" |
83555251 | 23 | #include "string-util.h" |
10cdbb83 | 24 | #include "strv.h" |
6d7c4033 | 25 | #include "tests.h" |
10cdbb83 | 26 | #include "tmpfile-util.h" |
83555251 | 27 | |
f63a2c48 YW |
28 | TEST(remount_and_move_sub_mounts) { |
29 | int r; | |
30 | ||
31 | if (geteuid() != 0 || have_effective_cap(CAP_SYS_ADMIN) <= 0) | |
32 | return (void) log_tests_skipped("not running privileged"); | |
33 | ||
34 | r = safe_fork("(remount-and-move-sub-mounts)", | |
35 | FORK_RESET_SIGNALS | | |
36 | FORK_CLOSE_ALL_FDS | | |
37 | FORK_DEATHSIG | | |
38 | FORK_WAIT | | |
39 | FORK_REOPEN_LOG | | |
40 | FORK_LOG | | |
41 | FORK_NEW_MOUNTNS | | |
42 | FORK_MOUNTNS_SLAVE, | |
43 | NULL); | |
44 | assert_se(r >= 0); | |
45 | if (r == 0) { | |
46 | _cleanup_free_ char *d = NULL, *fn = NULL; | |
47 | ||
48 | assert_se(mkdtemp_malloc(NULL, &d) >= 0); | |
49 | ||
50 | assert_se(mount_nofollow_verbose(LOG_DEBUG, "tmpfs", d, "tmpfs", MS_NOSUID|MS_NODEV, NULL) >= 0); | |
51 | ||
52 | assert_se(fn = path_join(d, "memo")); | |
53 | assert_se(write_string_file(fn, d, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_AVOID_NEWLINE) >= 0); | |
54 | assert_se(access(fn, F_OK) >= 0); | |
55 | ||
56 | /* Create fs tree */ | |
57 | FOREACH_STRING(p, "sub1", "sub1/hoge", "sub1/foo", "sub2", "sub2/aaa", "sub2/bbb") { | |
58 | _cleanup_free_ char *where = NULL, *filename = NULL; | |
59 | ||
60 | assert_se(where = path_join(d, p)); | |
61 | assert_se(mkdir_p(where, 0755) >= 0); | |
62 | assert_se(mount_nofollow_verbose(LOG_DEBUG, "tmpfs", where, "tmpfs", MS_NOSUID|MS_NODEV, NULL) >= 0); | |
63 | ||
64 | assert_se(filename = path_join(where, "memo")); | |
65 | assert_se(write_string_file(filename, where, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_AVOID_NEWLINE) >= 0); | |
66 | assert_se(access(filename, F_OK) >= 0); | |
67 | } | |
68 | ||
69 | /* Hide sub1. */ | |
70 | FOREACH_STRING(p, "sub1", "sub1/hogehoge", "sub1/foofoo") { | |
71 | _cleanup_free_ char *where = NULL, *filename = NULL; | |
72 | ||
73 | assert_se(where = path_join(d, p)); | |
74 | assert_se(mkdir_p(where, 0755) >= 0); | |
75 | assert_se(mount_nofollow_verbose(LOG_DEBUG, "tmpfs", where, "tmpfs", MS_NOSUID|MS_NODEV, NULL) >= 0); | |
76 | ||
77 | assert_se(filename = path_join(where, "memo")); | |
78 | assert_se(write_string_file(filename, where, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_AVOID_NEWLINE) >= 0); | |
79 | assert_se(access(filename, F_OK) >= 0); | |
80 | } | |
81 | ||
82 | /* Remount the main fs. */ | |
83 | r = remount_and_move_sub_mounts("tmpfs", d, "tmpfs", MS_NOSUID|MS_NODEV, NULL); | |
84 | if (r == -EINVAL || (r < 0 && ERRNO_IS_NOT_SUPPORTED(r))) { | |
85 | log_tests_skipped_errno(r, "The kernel seems too old: %m"); | |
86 | _exit(EXIT_SUCCESS); | |
87 | } | |
88 | ||
89 | /* Check the file in the main fs does not exist. */ | |
90 | assert_se(access(fn, F_OK) < 0 && errno == ENOENT); | |
91 | ||
92 | /* Check the files in sub-mounts are kept. */ | |
93 | FOREACH_STRING(p, "sub1", "sub1/hogehoge", "sub1/foofoo", "sub2", "sub2/aaa", "sub2/bbb") { | |
94 | _cleanup_free_ char *where = NULL, *filename = NULL, *content = NULL; | |
95 | ||
96 | assert_se(where = path_join(d, p)); | |
97 | assert_se(filename = path_join(where, "memo")); | |
98 | assert_se(read_full_file(filename, &content, NULL) >= 0); | |
99 | assert_se(streq(content, where)); | |
100 | } | |
101 | ||
102 | /* umount sub1, and check if the previously hidden sub-mounts are dropped. */ | |
103 | FOREACH_STRING(p, "sub1/hoge", "sub1/foo") { | |
104 | _cleanup_free_ char *where = NULL; | |
105 | ||
106 | assert_se(where = path_join(d, p)); | |
107 | assert_se(access(where, F_OK) < 0 && errno == ENOENT); | |
108 | } | |
109 | ||
110 | _exit(EXIT_SUCCESS); | |
111 | } | |
112 | } | |
113 | ||
114 | TEST(remount_sysfs) { | |
115 | int r; | |
116 | ||
117 | if (geteuid() != 0 || have_effective_cap(CAP_SYS_ADMIN) <= 0) | |
118 | return (void) log_tests_skipped("not running privileged"); | |
119 | ||
120 | if (path_is_fs_type("/sys", SYSFS_MAGIC) <= 0) | |
121 | return (void) log_tests_skipped("sysfs is not mounted on /sys"); | |
122 | ||
123 | if (access("/sys/class/net/dummy-test-mnt", F_OK) < 0) | |
124 | return (void) log_tests_skipped_errno(errno, "The network interface dummy-test-mnt does not exit"); | |
125 | ||
126 | r = safe_fork("(remount-sysfs)", | |
127 | FORK_RESET_SIGNALS | | |
128 | FORK_CLOSE_ALL_FDS | | |
129 | FORK_DEATHSIG | | |
130 | FORK_WAIT | | |
131 | FORK_REOPEN_LOG | | |
132 | FORK_LOG | | |
133 | FORK_NEW_MOUNTNS | | |
134 | FORK_MOUNTNS_SLAVE, | |
135 | NULL); | |
136 | assert_se(r >= 0); | |
137 | if (r == 0) { | |
138 | assert_se(unshare(CLONE_NEWNET) >= 0); | |
139 | ||
140 | /* Even unshare()ed, the interfaces in the main namespace can be accessed through sysfs. */ | |
141 | assert_se(access("/sys/class/net/lo", F_OK) >= 0); | |
142 | assert_se(access("/sys/class/net/dummy-test-mnt", F_OK) >= 0); | |
143 | ||
144 | r = remount_sysfs("/sys"); | |
145 | if (r == -EINVAL || (r < 0 && ERRNO_IS_NOT_SUPPORTED(r))) { | |
146 | log_tests_skipped_errno(r, "The kernel seems too old: %m"); | |
147 | _exit(EXIT_SUCCESS); | |
148 | } | |
149 | ||
150 | /* After remounting sysfs, the interfaces in the main namespace cannot be accessed. */ | |
151 | assert_se(access("/sys/class/net/lo", F_OK) >= 0); | |
152 | assert_se(access("/sys/class/net/dummy-test-mnt", F_OK) < 0 && errno == ENOENT); | |
153 | ||
154 | _exit(EXIT_SUCCESS); | |
155 | } | |
156 | } | |
157 | ||
4f7452a8 | 158 | TEST(mount_option_mangle) { |
f27b437b YW |
159 | char *opts = NULL; |
160 | unsigned long f; | |
161 | ||
162 | assert_se(mount_option_mangle(NULL, MS_RDONLY|MS_NOSUID, &f, &opts) == 0); | |
163 | assert_se(f == (MS_RDONLY|MS_NOSUID)); | |
164 | assert_se(opts == NULL); | |
165 | ||
166 | assert_se(mount_option_mangle("", MS_RDONLY|MS_NOSUID, &f, &opts) == 0); | |
167 | assert_se(f == (MS_RDONLY|MS_NOSUID)); | |
168 | assert_se(opts == NULL); | |
169 | ||
170 | assert_se(mount_option_mangle("ro,nosuid,nodev,noexec", 0, &f, &opts) == 0); | |
171 | assert_se(f == (MS_RDONLY|MS_NOSUID|MS_NODEV|MS_NOEXEC)); | |
172 | assert_se(opts == NULL); | |
173 | ||
9f563f27 | 174 | assert_se(mount_option_mangle("ro,nosuid,nodev,noexec,mode=0755", 0, &f, &opts) == 0); |
f27b437b | 175 | assert_se(f == (MS_RDONLY|MS_NOSUID|MS_NODEV|MS_NOEXEC)); |
9f563f27 | 176 | assert_se(streq(opts, "mode=0755")); |
f27b437b YW |
177 | opts = mfree(opts); |
178 | ||
9f563f27 | 179 | assert_se(mount_option_mangle("rw,nosuid,foo,hogehoge,nodev,mode=0755", 0, &f, &opts) == 0); |
f27b437b | 180 | assert_se(f == (MS_NOSUID|MS_NODEV)); |
9f563f27 | 181 | assert_se(streq(opts, "foo,hogehoge,mode=0755")); |
f27b437b YW |
182 | opts = mfree(opts); |
183 | ||
184 | assert_se(mount_option_mangle("rw,nosuid,nodev,noexec,relatime,net_cls,net_prio", MS_RDONLY, &f, &opts) == 0); | |
185 | assert_se(f == (MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_RELATIME)); | |
186 | assert_se(streq(opts, "net_cls,net_prio")); | |
187 | opts = mfree(opts); | |
188 | ||
9f563f27 | 189 | assert_se(mount_option_mangle("rw,nosuid,nodev,relatime,size=1630748k,mode=0700,uid=1000,gid=1000", MS_RDONLY, &f, &opts) == 0); |
f27b437b | 190 | assert_se(f == (MS_NOSUID|MS_NODEV|MS_RELATIME)); |
9f563f27 | 191 | assert_se(streq(opts, "size=1630748k,mode=0700,uid=1000,gid=1000")); |
f27b437b YW |
192 | opts = mfree(opts); |
193 | ||
9f563f27 | 194 | assert_se(mount_option_mangle("size=1630748k,rw,gid=1000,,,nodev,relatime,,mode=0700,nosuid,uid=1000", MS_RDONLY, &f, &opts) == 0); |
f27b437b | 195 | assert_se(f == (MS_NOSUID|MS_NODEV|MS_RELATIME)); |
9f563f27 | 196 | assert_se(streq(opts, "size=1630748k,gid=1000,mode=0700,uid=1000")); |
f27b437b YW |
197 | opts = mfree(opts); |
198 | ||
9f563f27 | 199 | assert_se(mount_option_mangle("rw,exec,size=8143984k,nr_inodes=2035996,mode=0755", MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, &f, &opts) == 0); |
f27b437b | 200 | assert_se(f == (MS_NOSUID|MS_NODEV)); |
9f563f27 | 201 | assert_se(streq(opts, "size=8143984k,nr_inodes=2035996,mode=0755")); |
f27b437b YW |
202 | opts = mfree(opts); |
203 | ||
204 | assert_se(mount_option_mangle("rw,relatime,fmask=0022,,,dmask=0022", MS_RDONLY, &f, &opts) == 0); | |
205 | assert_se(f == MS_RELATIME); | |
206 | assert_se(streq(opts, "fmask=0022,dmask=0022")); | |
207 | opts = mfree(opts); | |
208 | ||
209 | assert_se(mount_option_mangle("rw,relatime,fmask=0022,dmask=0022,\"hogehoge", MS_RDONLY, &f, &opts) < 0); | |
9b23679e | 210 | |
9f563f27 | 211 | assert_se(mount_option_mangle("mode=01777,size=10%,nr_inodes=400k,uid=496107520,gid=496107520,context=\"system_u:object_r:svirt_sandbox_file_t:s0:c0,c1\"", 0, &f, &opts) == 0); |
9b23679e | 212 | assert_se(f == 0); |
9f563f27 | 213 | assert_se(streq(opts, "mode=01777,size=10%,nr_inodes=400k,uid=496107520,gid=496107520,context=\"system_u:object_r:svirt_sandbox_file_t:s0:c0,c1\"")); |
9b23679e | 214 | opts = mfree(opts); |
f27b437b YW |
215 | } |
216 | ||
51bb6a10 ZJS |
217 | static void test_mount_flags_to_string_one(unsigned long flags, const char *expected) { |
218 | _cleanup_free_ char *x = NULL; | |
219 | int r; | |
220 | ||
221 | r = mount_flags_to_string(flags, &x); | |
222 | log_info("flags: %#lX → %d/\"%s\"", flags, r, strnull(x)); | |
223 | assert_se(r >= 0); | |
224 | assert_se(streq(x, expected)); | |
225 | } | |
226 | ||
4f7452a8 | 227 | TEST(mount_flags_to_string) { |
51bb6a10 ZJS |
228 | test_mount_flags_to_string_one(0, "0"); |
229 | test_mount_flags_to_string_one(MS_RDONLY, "MS_RDONLY"); | |
230 | test_mount_flags_to_string_one(MS_NOSUID, "MS_NOSUID"); | |
231 | test_mount_flags_to_string_one(MS_NODEV, "MS_NODEV"); | |
232 | test_mount_flags_to_string_one(MS_NOEXEC, "MS_NOEXEC"); | |
233 | test_mount_flags_to_string_one(MS_SYNCHRONOUS, "MS_SYNCHRONOUS"); | |
234 | test_mount_flags_to_string_one(MS_REMOUNT, "MS_REMOUNT"); | |
235 | test_mount_flags_to_string_one(MS_MANDLOCK, "MS_MANDLOCK"); | |
236 | test_mount_flags_to_string_one(MS_DIRSYNC, "MS_DIRSYNC"); | |
237 | test_mount_flags_to_string_one(MS_NOSYMFOLLOW, "MS_NOSYMFOLLOW"); | |
238 | test_mount_flags_to_string_one(MS_NOATIME, "MS_NOATIME"); | |
239 | test_mount_flags_to_string_one(MS_NODIRATIME, "MS_NODIRATIME"); | |
240 | test_mount_flags_to_string_one(MS_BIND, "MS_BIND"); | |
241 | test_mount_flags_to_string_one(MS_MOVE, "MS_MOVE"); | |
242 | test_mount_flags_to_string_one(MS_REC, "MS_REC"); | |
243 | test_mount_flags_to_string_one(MS_SILENT, "MS_SILENT"); | |
244 | test_mount_flags_to_string_one(MS_POSIXACL, "MS_POSIXACL"); | |
245 | test_mount_flags_to_string_one(MS_UNBINDABLE, "MS_UNBINDABLE"); | |
246 | test_mount_flags_to_string_one(MS_PRIVATE, "MS_PRIVATE"); | |
247 | test_mount_flags_to_string_one(MS_SLAVE, "MS_SLAVE"); | |
248 | test_mount_flags_to_string_one(MS_SHARED, "MS_SHARED"); | |
249 | test_mount_flags_to_string_one(MS_RELATIME, "MS_RELATIME"); | |
250 | test_mount_flags_to_string_one(MS_KERNMOUNT, "MS_KERNMOUNT"); | |
251 | test_mount_flags_to_string_one(MS_I_VERSION, "MS_I_VERSION"); | |
252 | test_mount_flags_to_string_one(MS_STRICTATIME, "MS_STRICTATIME"); | |
253 | test_mount_flags_to_string_one(MS_LAZYTIME, "MS_LAZYTIME"); | |
254 | test_mount_flags_to_string_one(MS_LAZYTIME|MS_STRICTATIME, "MS_STRICTATIME|MS_LAZYTIME"); | |
255 | test_mount_flags_to_string_one(UINT_MAX, | |
256 | "MS_RDONLY|MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_SYNCHRONOUS|MS_REMOUNT|" | |
257 | "MS_MANDLOCK|MS_DIRSYNC|MS_NOSYMFOLLOW|MS_NOATIME|MS_NODIRATIME|" | |
258 | "MS_BIND|MS_MOVE|MS_REC|MS_SILENT|MS_POSIXACL|MS_UNBINDABLE|" | |
259 | "MS_PRIVATE|MS_SLAVE|MS_SHARED|MS_RELATIME|MS_KERNMOUNT|" | |
260 | "MS_I_VERSION|MS_STRICTATIME|MS_LAZYTIME|fc000200"); | |
261 | } | |
262 | ||
4f7452a8 | 263 | TEST(bind_remount_recursive) { |
10cdbb83 LP |
264 | _cleanup_(rm_rf_physical_and_freep) char *tmp = NULL; |
265 | _cleanup_free_ char *subdir = NULL; | |
10cdbb83 | 266 | |
c75370cc LP |
267 | if (geteuid() != 0 || have_effective_cap(CAP_SYS_ADMIN) <= 0) { |
268 | (void) log_tests_skipped("not running privileged"); | |
10cdbb83 LP |
269 | return; |
270 | } | |
271 | ||
272 | assert_se(mkdtemp_malloc("/tmp/XXXXXX", &tmp) >= 0); | |
273 | subdir = path_join(tmp, "subdir"); | |
274 | assert_se(subdir); | |
275 | assert_se(mkdir(subdir, 0755) >= 0); | |
276 | ||
277 | FOREACH_STRING(p, "/usr", "/sys", "/", tmp) { | |
278 | pid_t pid; | |
279 | ||
280 | pid = fork(); | |
281 | assert_se(pid >= 0); | |
282 | ||
283 | if (pid == 0) { | |
284 | struct statvfs svfs; | |
285 | /* child */ | |
286 | assert_se(detach_mount_namespace() >= 0); | |
287 | ||
288 | /* Check that the subdir is writable (it must be because it's in /tmp) */ | |
289 | assert_se(statvfs(subdir, &svfs) >= 0); | |
290 | assert_se(!FLAGS_SET(svfs.f_flag, ST_RDONLY)); | |
291 | ||
292 | /* Make the subdir a bind mount */ | |
293 | assert_se(mount_nofollow(subdir, subdir, NULL, MS_BIND|MS_REC, NULL) >= 0); | |
294 | ||
295 | /* Ensure it's still writable */ | |
296 | assert_se(statvfs(subdir, &svfs) >= 0); | |
297 | assert_se(!FLAGS_SET(svfs.f_flag, ST_RDONLY)); | |
298 | ||
299 | /* Now mark the path we currently run for read-only */ | |
874052c5 | 300 | assert_se(bind_remount_recursive(p, MS_RDONLY, MS_RDONLY, path_equal(p, "/sys") ? STRV_MAKE("/sys/kernel") : NULL) >= 0); |
10cdbb83 LP |
301 | |
302 | /* Ensure that this worked on the top-level */ | |
303 | assert_se(statvfs(p, &svfs) >= 0); | |
304 | assert_se(FLAGS_SET(svfs.f_flag, ST_RDONLY)); | |
305 | ||
306 | /* And ensure this had an effect on the subdir exactly if we are talking about a path above the subdir */ | |
307 | assert_se(statvfs(subdir, &svfs) >= 0); | |
308 | assert_se(FLAGS_SET(svfs.f_flag, ST_RDONLY) == !!path_startswith(subdir, p)); | |
309 | ||
310 | _exit(EXIT_SUCCESS); | |
311 | } | |
312 | ||
313 | assert_se(wait_for_terminate_and_check("test-remount-rec", pid, WAIT_LOG) == EXIT_SUCCESS); | |
314 | } | |
315 | } | |
316 | ||
4f7452a8 | 317 | TEST(bind_remount_one) { |
67d22a36 LP |
318 | pid_t pid; |
319 | ||
c75370cc LP |
320 | if (geteuid() != 0 || have_effective_cap(CAP_SYS_ADMIN) <= 0) { |
321 | (void) log_tests_skipped("not running privileged"); | |
67d22a36 LP |
322 | return; |
323 | } | |
324 | ||
325 | pid = fork(); | |
326 | assert_se(pid >= 0); | |
327 | ||
328 | if (pid == 0) { | |
329 | /* child */ | |
330 | ||
331 | _cleanup_fclose_ FILE *proc_self_mountinfo = NULL; | |
332 | ||
333 | assert_se(detach_mount_namespace() >= 0); | |
334 | ||
335 | assert_se(fopen_unlocked("/proc/self/mountinfo", "re", &proc_self_mountinfo) >= 0); | |
336 | ||
337 | assert_se(bind_remount_one_with_mountinfo("/run", MS_RDONLY, MS_RDONLY, proc_self_mountinfo) >= 0); | |
4f5644db | 338 | assert_se(bind_remount_one_with_mountinfo("/run", MS_NOEXEC, MS_RDONLY|MS_NOEXEC, proc_self_mountinfo) >= 0); |
67d22a36 LP |
339 | assert_se(bind_remount_one_with_mountinfo("/proc/idontexist", MS_RDONLY, MS_RDONLY, proc_self_mountinfo) == -ENOENT); |
340 | assert_se(bind_remount_one_with_mountinfo("/proc/self", MS_RDONLY, MS_RDONLY, proc_self_mountinfo) == -EINVAL); | |
341 | assert_se(bind_remount_one_with_mountinfo("/", MS_RDONLY, MS_RDONLY, proc_self_mountinfo) >= 0); | |
342 | ||
343 | _exit(EXIT_SUCCESS); | |
344 | } | |
345 | ||
346 | assert_se(wait_for_terminate_and_check("test-remount-one", pid, WAIT_LOG) == EXIT_SUCCESS); | |
347 | } | |
348 | ||
4f7452a8 | 349 | TEST(make_mount_point_inode) { |
9c653536 ZJS |
350 | _cleanup_(rm_rf_physical_and_freep) char *d = NULL; |
351 | const char *src_file, *src_dir, *dst_file, *dst_dir; | |
352 | struct stat st; | |
353 | ||
9c653536 ZJS |
354 | assert_se(mkdtemp_malloc(NULL, &d) >= 0); |
355 | ||
356 | src_file = strjoina(d, "/src/file"); | |
357 | src_dir = strjoina(d, "/src/dir"); | |
358 | dst_file = strjoina(d, "/dst/file"); | |
359 | dst_dir = strjoina(d, "/dst/dir"); | |
360 | ||
361 | assert_se(mkdir_p(src_dir, 0755) >= 0); | |
362 | assert_se(mkdir_parents(dst_file, 0755) >= 0); | |
363 | assert_se(touch(src_file) >= 0); | |
364 | ||
365 | assert_se(make_mount_point_inode_from_path(src_file, dst_file, 0755) >= 0); | |
366 | assert_se(make_mount_point_inode_from_path(src_dir, dst_dir, 0755) >= 0); | |
367 | ||
368 | assert_se(stat(dst_dir, &st) == 0); | |
369 | assert_se(S_ISDIR(st.st_mode)); | |
370 | assert_se(stat(dst_file, &st) == 0); | |
371 | assert_se(S_ISREG(st.st_mode)); | |
372 | assert_se(!(S_IXUSR & st.st_mode)); | |
373 | assert_se(!(S_IXGRP & st.st_mode)); | |
374 | assert_se(!(S_IXOTH & st.st_mode)); | |
375 | ||
376 | assert_se(unlink(dst_file) == 0); | |
377 | assert_se(rmdir(dst_dir) == 0); | |
378 | ||
379 | assert_se(stat(src_file, &st) == 0); | |
380 | assert_se(make_mount_point_inode_from_stat(&st, dst_file, 0755) >= 0); | |
381 | assert_se(stat(src_dir, &st) == 0); | |
382 | assert_se(make_mount_point_inode_from_stat(&st, dst_dir, 0755) >= 0); | |
383 | ||
384 | assert_se(stat(dst_dir, &st) == 0); | |
385 | assert_se(S_ISDIR(st.st_mode)); | |
386 | assert_se(stat(dst_file, &st) == 0); | |
387 | assert_se(S_ISREG(st.st_mode)); | |
388 | assert_se(!(S_IXUSR & st.st_mode)); | |
389 | assert_se(!(S_IXGRP & st.st_mode)); | |
390 | assert_se(!(S_IXOTH & st.st_mode)); | |
391 | } | |
392 | ||
ea0f3289 LP |
393 | TEST(make_mount_switch_root) { |
394 | _cleanup_(rm_rf_physical_and_freep) char *t = NULL; | |
395 | _cleanup_free_ char *s = NULL; | |
396 | int r; | |
397 | ||
398 | if (geteuid() != 0 || have_effective_cap(CAP_SYS_ADMIN) <= 0) { | |
399 | (void) log_tests_skipped("not running privileged"); | |
400 | return; | |
401 | } | |
402 | ||
403 | assert_se(mkdtemp_malloc(NULL, &t) >= 0); | |
404 | ||
405 | assert_se(asprintf(&s, "%s/somerandomname%" PRIu64, t, random_u64()) >= 0); | |
406 | assert_se(s); | |
407 | assert_se(touch(s) >= 0); | |
408 | ||
409 | for (int force_ms_move = 0; force_ms_move < 2; force_ms_move++) { | |
4e9ef660 | 410 | r = safe_fork("(switch-root)", |
ea0f3289 LP |
411 | FORK_RESET_SIGNALS | |
412 | FORK_CLOSE_ALL_FDS | | |
413 | FORK_DEATHSIG | | |
414 | FORK_WAIT | | |
415 | FORK_REOPEN_LOG | | |
416 | FORK_LOG | | |
417 | FORK_NEW_MOUNTNS | | |
418 | FORK_MOUNTNS_SLAVE, | |
419 | NULL); | |
420 | assert_se(r >= 0); | |
421 | ||
422 | if (r == 0) { | |
423 | assert_se(make_mount_point(t) >= 0); | |
424 | assert_se(mount_switch_root_full(t, /* mount_propagation_flag= */ 0, force_ms_move) >= 0); | |
425 | ||
426 | assert_se(access(ASSERT_PTR(strrchr(s, '/')), F_OK) >= 0); /* absolute */ | |
427 | assert_se(access(ASSERT_PTR(strrchr(s, '/')) + 1, F_OK) >= 0); /* relative */ | |
428 | assert_se(access(s, F_OK) < 0 && errno == ENOENT); /* doesn't exist in our new environment */ | |
429 | ||
430 | _exit(EXIT_SUCCESS); | |
431 | } | |
432 | } | |
433 | } | |
434 | ||
4e9ef660 LP |
435 | TEST(umount_recursive) { |
436 | static const struct { | |
437 | const char *prefix; | |
438 | const char * const keep[3]; | |
439 | } test_table[] = { | |
440 | { | |
441 | .prefix = NULL, | |
442 | .keep = {}, | |
443 | }, | |
444 | { | |
445 | .prefix = "/run", | |
446 | .keep = {}, | |
447 | }, | |
448 | { | |
449 | .prefix = NULL, | |
450 | .keep = { "/dev/shm", NULL }, | |
451 | }, | |
452 | { | |
453 | .prefix = "/dev", | |
454 | .keep = { "/dev/pts", "/dev/shm", NULL }, | |
455 | }, | |
456 | }; | |
457 | ||
458 | int r; | |
459 | ||
460 | FOREACH_ARRAY(t, test_table, ELEMENTSOF(test_table)) { | |
461 | ||
462 | r = safe_fork("(umount-rec)", | |
463 | FORK_RESET_SIGNALS | | |
464 | FORK_CLOSE_ALL_FDS | | |
465 | FORK_DEATHSIG | | |
466 | FORK_WAIT | | |
467 | FORK_REOPEN_LOG | | |
468 | FORK_LOG | | |
469 | FORK_NEW_MOUNTNS | | |
470 | FORK_MOUNTNS_SLAVE, | |
471 | NULL); | |
472 | ||
473 | if (r < 0 && ERRNO_IS_PRIVILEGE(r)) { | |
474 | log_notice("Skipping umount_recursive() test, lacking privileges"); | |
475 | return; | |
476 | } | |
477 | ||
478 | assert_se(r >= 0); | |
479 | if (r == 0) { /* child */ | |
480 | _cleanup_(mnt_free_tablep) struct libmnt_table *table = NULL; | |
481 | _cleanup_(mnt_free_iterp) struct libmnt_iter *iter = NULL; | |
482 | _cleanup_fclose_ FILE *f = NULL; | |
483 | _cleanup_free_ char *k = NULL; | |
484 | ||
485 | /* Open /p/s/m file before we unmount everything (which might include /proc/) */ | |
486 | f = fopen("/proc/self/mountinfo", "re"); | |
487 | if (!f) { | |
9a27ef09 | 488 | log_error_errno(errno, "Failed to open /proc/self/mountinfo: %m"); |
4e9ef660 LP |
489 | _exit(EXIT_FAILURE); |
490 | } | |
491 | ||
492 | assert_se(k = strv_join((char**) t->keep, " ")); | |
493 | log_info("detaching just %s (keep: %s)", strna(t->prefix), strna(empty_to_null(k))); | |
494 | ||
495 | assert_se(umount_recursive_full(t->prefix, MNT_DETACH, (char**) t->keep) >= 0); | |
496 | ||
497 | r = libmount_parse("/proc/self/mountinfo", f, &table, &iter); | |
498 | if (r < 0) { | |
499 | log_error_errno(r, "Failed to parse /proc/self/mountinfo: %m"); | |
500 | _exit(EXIT_FAILURE); | |
501 | } | |
502 | ||
503 | for (;;) { | |
504 | struct libmnt_fs *fs; | |
505 | ||
506 | r = mnt_table_next_fs(table, iter, &fs); | |
507 | if (r == 1) | |
508 | break; | |
509 | if (r < 0) { | |
510 | log_error_errno(r, "Failed to get next entry from /proc/self/mountinfo: %m"); | |
511 | _exit(EXIT_FAILURE); | |
512 | } | |
513 | ||
514 | log_debug("left after complete umount: %s", mnt_fs_get_target(fs)); | |
515 | } | |
516 | ||
517 | _exit(EXIT_SUCCESS); | |
518 | } | |
519 | } | |
520 | } | |
521 | ||
f9ad896e LP |
522 | TEST(fd_make_mount_point) { |
523 | _cleanup_(rm_rf_physical_and_freep) char *t = NULL; | |
524 | _cleanup_free_ char *s = NULL; | |
525 | int r; | |
526 | ||
527 | if (geteuid() != 0 || have_effective_cap(CAP_SYS_ADMIN) <= 0) { | |
528 | (void) log_tests_skipped("not running privileged"); | |
529 | return; | |
530 | } | |
531 | ||
532 | assert_se(mkdtemp_malloc(NULL, &t) >= 0); | |
533 | ||
534 | assert_se(asprintf(&s, "%s/somerandomname%" PRIu64, t, random_u64()) >= 0); | |
535 | assert_se(s); | |
536 | assert_se(mkdir(s, 0700) >= 0); | |
537 | ||
538 | r = safe_fork("(make_mount-point)", | |
539 | FORK_RESET_SIGNALS | | |
540 | FORK_CLOSE_ALL_FDS | | |
541 | FORK_DEATHSIG | | |
542 | FORK_WAIT | | |
543 | FORK_REOPEN_LOG | | |
544 | FORK_LOG | | |
545 | FORK_NEW_MOUNTNS | | |
546 | FORK_MOUNTNS_SLAVE, | |
547 | NULL); | |
548 | assert_se(r >= 0); | |
549 | ||
550 | if (r == 0) { | |
551 | _cleanup_close_ int fd = -EBADF, fd2 = -EBADF; | |
552 | ||
553 | fd = open(s, O_PATH|O_CLOEXEC); | |
554 | assert_se(fd >= 0); | |
555 | ||
556 | assert_se(fd_is_mount_point(fd, NULL, AT_SYMLINK_FOLLOW) == 0); | |
557 | ||
558 | assert_se(fd_make_mount_point(fd) > 0); | |
559 | ||
560 | /* Reopen the inode so that we end up on the new mount */ | |
561 | fd2 = open(s, O_PATH|O_CLOEXEC); | |
562 | ||
563 | assert_se(fd_is_mount_point(fd2, NULL, AT_SYMLINK_FOLLOW) > 0); | |
564 | ||
565 | assert_se(fd_make_mount_point(fd2) == 0); | |
566 | ||
567 | _exit(EXIT_SUCCESS); | |
568 | } | |
569 | } | |
570 | ||
f63a2c48 YW |
571 | static int intro(void) { |
572 | /* Create a dummy network interface for testing remount_sysfs(). */ | |
573 | (void) system("ip link add dummy-test-mnt type dummy"); | |
574 | ||
575 | return 0; | |
576 | } | |
577 | ||
578 | static int outro(void) { | |
579 | (void) system("ip link del dummy-test-mnt"); | |
580 | ||
581 | return 0; | |
582 | } | |
583 | ||
584 | DEFINE_TEST_MAIN_FULL(LOG_DEBUG, intro, outro); |