1 // SPDX-License-Identifier: GPL-2.0-or-later
2 // Copyright (c) 2025 Miklos Szeredi <miklos@szeredi.hu>
3
4 #define _GNU_SOURCE
5 #include <fcntl.h>
6 #include <sched.h>
7 #include <stdio.h>
8 #include <string.h>
9 #include <sys/stat.h>
10 #include <sys/mount.h>
11 #include <unistd.h>
12 #include <sys/syscall.h>
13
14 #include "../../kselftest_harness.h"
15 #include "../statmount/statmount.h"
16 #include "../utils.h"
17
18 // Needed for linux/fanotify.h
19 #ifndef __kernel_fsid_t
20 typedef struct {
21 int val[2];
22 } __kernel_fsid_t;
23 #endif
24
25 #include <sys/fanotify.h>
26
27 static const char root_mntpoint_templ[] = "/tmp/mount-notify_test_root.XXXXXX";
28
29 static const int mark_cmds[] = {
30 FAN_MARK_ADD,
31 FAN_MARK_REMOVE,
32 FAN_MARK_FLUSH
33 };
34
35 #define NUM_FAN_FDS ARRAY_SIZE(mark_cmds)
36
FIXTURE(fanotify)37 FIXTURE(fanotify) {
38 int fan_fd[NUM_FAN_FDS];
39 char buf[256];
40 unsigned int rem;
41 void *next;
42 char root_mntpoint[sizeof(root_mntpoint_templ)];
43 int orig_root;
44 int ns_fd;
45 uint64_t root_id;
46 };
47
FIXTURE_SETUP(fanotify)48 FIXTURE_SETUP(fanotify)
49 {
50 int i, ret;
51
52 ASSERT_EQ(unshare(CLONE_NEWNS), 0);
53
54 self->ns_fd = open("/proc/self/ns/mnt", O_RDONLY);
55 ASSERT_GE(self->ns_fd, 0);
56
57 ASSERT_EQ(mount("", "/", NULL, MS_REC|MS_PRIVATE, NULL), 0);
58
59 strcpy(self->root_mntpoint, root_mntpoint_templ);
60 ASSERT_NE(mkdtemp(self->root_mntpoint), NULL);
61
62 self->orig_root = open("/", O_PATH | O_CLOEXEC);
63 ASSERT_GE(self->orig_root, 0);
64
65 ASSERT_EQ(mount("tmpfs", self->root_mntpoint, "tmpfs", 0, NULL), 0);
66
67 ASSERT_EQ(chroot(self->root_mntpoint), 0);
68
69 ASSERT_EQ(chdir("/"), 0);
70
71 ASSERT_EQ(mkdir("a", 0700), 0);
72
73 ASSERT_EQ(mkdir("b", 0700), 0);
74
75 self->root_id = get_unique_mnt_id("/");
76 ASSERT_NE(self->root_id, 0);
77
78 for (i = 0; i < NUM_FAN_FDS; i++) {
79 self->fan_fd[i] = fanotify_init(FAN_REPORT_MNT | FAN_NONBLOCK,
80 0);
81 ASSERT_GE(self->fan_fd[i], 0);
82 ret = fanotify_mark(self->fan_fd[i], FAN_MARK_ADD |
83 FAN_MARK_MNTNS,
84 FAN_MNT_ATTACH | FAN_MNT_DETACH,
85 self->ns_fd, NULL);
86 ASSERT_EQ(ret, 0);
87 // On fd[0] we do an extra ADD that changes nothing.
88 // On fd[1]/fd[2] we REMOVE/FLUSH which removes the mark.
89 ret = fanotify_mark(self->fan_fd[i], mark_cmds[i] |
90 FAN_MARK_MNTNS,
91 FAN_MNT_ATTACH | FAN_MNT_DETACH,
92 self->ns_fd, NULL);
93 ASSERT_EQ(ret, 0);
94 }
95
96 self->rem = 0;
97 }
98
FIXTURE_TEARDOWN(fanotify)99 FIXTURE_TEARDOWN(fanotify)
100 {
101 int i;
102
103 ASSERT_EQ(self->rem, 0);
104 for (i = 0; i < NUM_FAN_FDS; i++)
105 close(self->fan_fd[i]);
106
107 ASSERT_EQ(fchdir(self->orig_root), 0);
108
109 ASSERT_EQ(chroot("."), 0);
110
111 EXPECT_EQ(umount2(self->root_mntpoint, MNT_DETACH), 0);
112 EXPECT_EQ(chdir(self->root_mntpoint), 0);
113 EXPECT_EQ(chdir("/"), 0);
114 EXPECT_EQ(rmdir(self->root_mntpoint), 0);
115 }
116
expect_notify(struct __test_metadata * const _metadata,FIXTURE_DATA (fanotify)* self,uint64_t * mask)117 static uint64_t expect_notify(struct __test_metadata *const _metadata,
118 FIXTURE_DATA(fanotify) *self,
119 uint64_t *mask)
120 {
121 struct fanotify_event_metadata *meta;
122 struct fanotify_event_info_mnt *mnt;
123 unsigned int thislen;
124
125 if (!self->rem) {
126 ssize_t len;
127 int i;
128
129 for (i = NUM_FAN_FDS - 1; i >= 0; i--) {
130 len = read(self->fan_fd[i], self->buf,
131 sizeof(self->buf));
132 if (i > 0) {
133 // Groups 1,2 should get EAGAIN
134 ASSERT_EQ(len, -1);
135 ASSERT_EQ(errno, EAGAIN);
136 } else {
137 // Group 0 should get events
138 ASSERT_GT(len, 0);
139 }
140 }
141
142 self->rem = len;
143 self->next = (void *) self->buf;
144 }
145
146 meta = self->next;
147 ASSERT_TRUE(FAN_EVENT_OK(meta, self->rem));
148
149 thislen = meta->event_len;
150 self->rem -= thislen;
151 self->next += thislen;
152
153 *mask = meta->mask;
154 thislen -= sizeof(*meta);
155
156 mnt = ((void *) meta) + meta->event_len - thislen;
157
158 ASSERT_EQ(thislen, sizeof(*mnt));
159
160 return mnt->mnt_id;
161 }
162
expect_notify_n(struct __test_metadata * const _metadata,FIXTURE_DATA (fanotify)* self,unsigned int n,uint64_t mask[],uint64_t mnts[])163 static void expect_notify_n(struct __test_metadata *const _metadata,
164 FIXTURE_DATA(fanotify) *self,
165 unsigned int n, uint64_t mask[], uint64_t mnts[])
166 {
167 unsigned int i;
168
169 for (i = 0; i < n; i++)
170 mnts[i] = expect_notify(_metadata, self, &mask[i]);
171 }
172
expect_notify_mask(struct __test_metadata * const _metadata,FIXTURE_DATA (fanotify)* self,uint64_t expect_mask)173 static uint64_t expect_notify_mask(struct __test_metadata *const _metadata,
174 FIXTURE_DATA(fanotify) *self,
175 uint64_t expect_mask)
176 {
177 uint64_t mntid, mask;
178
179 mntid = expect_notify(_metadata, self, &mask);
180 ASSERT_EQ(expect_mask, mask);
181
182 return mntid;
183 }
184
185
expect_notify_mask_n(struct __test_metadata * const _metadata,FIXTURE_DATA (fanotify)* self,uint64_t mask,unsigned int n,uint64_t mnts[])186 static void expect_notify_mask_n(struct __test_metadata *const _metadata,
187 FIXTURE_DATA(fanotify) *self,
188 uint64_t mask, unsigned int n, uint64_t mnts[])
189 {
190 unsigned int i;
191
192 for (i = 0; i < n; i++)
193 mnts[i] = expect_notify_mask(_metadata, self, mask);
194 }
195
verify_mount_ids(struct __test_metadata * const _metadata,const uint64_t list1[],const uint64_t list2[],size_t num)196 static void verify_mount_ids(struct __test_metadata *const _metadata,
197 const uint64_t list1[], const uint64_t list2[],
198 size_t num)
199 {
200 unsigned int i, j;
201
202 // Check that neither list has any duplicates
203 for (i = 0; i < num; i++) {
204 for (j = 0; j < num; j++) {
205 if (i != j) {
206 ASSERT_NE(list1[i], list1[j]);
207 ASSERT_NE(list2[i], list2[j]);
208 }
209 }
210 }
211 // Check that all list1 memebers can be found in list2. Together with
212 // the above it means that the list1 and list2 represent the same sets.
213 for (i = 0; i < num; i++) {
214 for (j = 0; j < num; j++) {
215 if (list1[i] == list2[j])
216 break;
217 }
218 ASSERT_NE(j, num);
219 }
220 }
221
check_mounted(struct __test_metadata * const _metadata,const uint64_t mnts[],size_t num)222 static void check_mounted(struct __test_metadata *const _metadata,
223 const uint64_t mnts[], size_t num)
224 {
225 ssize_t ret;
226 uint64_t *list;
227
228 list = malloc((num + 1) * sizeof(list[0]));
229 ASSERT_NE(list, NULL);
230
231 ret = listmount(LSMT_ROOT, 0, 0, list, num + 1, 0);
232 ASSERT_EQ(ret, num);
233
234 verify_mount_ids(_metadata, mnts, list, num);
235
236 free(list);
237 }
238
setup_mount_tree(struct __test_metadata * const _metadata,int log2_num)239 static void setup_mount_tree(struct __test_metadata *const _metadata,
240 int log2_num)
241 {
242 int ret, i;
243
244 ret = mount("", "/", NULL, MS_SHARED, NULL);
245 ASSERT_EQ(ret, 0);
246
247 for (i = 0; i < log2_num; i++) {
248 ret = mount("/", "/", NULL, MS_BIND, NULL);
249 ASSERT_EQ(ret, 0);
250 }
251 }
252
TEST_F(fanotify,bind)253 TEST_F(fanotify, bind)
254 {
255 int ret;
256 uint64_t mnts[2] = { self->root_id };
257
258 ret = mount("/", "/", NULL, MS_BIND, NULL);
259 ASSERT_EQ(ret, 0);
260
261 mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);
262 ASSERT_NE(mnts[0], mnts[1]);
263
264 check_mounted(_metadata, mnts, 2);
265
266 // Cleanup
267 uint64_t detach_id;
268 ret = umount("/");
269 ASSERT_EQ(ret, 0);
270
271 detach_id = expect_notify_mask(_metadata, self, FAN_MNT_DETACH);
272 ASSERT_EQ(detach_id, mnts[1]);
273
274 check_mounted(_metadata, mnts, 1);
275 }
276
TEST_F(fanotify,move)277 TEST_F(fanotify, move)
278 {
279 int ret;
280 uint64_t mnts[2] = { self->root_id };
281 uint64_t move_id;
282
283 ret = mount("/", "/a", NULL, MS_BIND, NULL);
284 ASSERT_EQ(ret, 0);
285
286 mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);
287 ASSERT_NE(mnts[0], mnts[1]);
288
289 check_mounted(_metadata, mnts, 2);
290
291 ret = move_mount(AT_FDCWD, "/a", AT_FDCWD, "/b", 0);
292 ASSERT_EQ(ret, 0);
293
294 move_id = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH | FAN_MNT_DETACH);
295 ASSERT_EQ(move_id, mnts[1]);
296
297 // Cleanup
298 ret = umount("/b");
299 ASSERT_EQ(ret, 0);
300
301 check_mounted(_metadata, mnts, 1);
302 }
303
TEST_F(fanotify,propagate)304 TEST_F(fanotify, propagate)
305 {
306 const unsigned int log2_num = 4;
307 const unsigned int num = (1 << log2_num);
308 uint64_t mnts[num];
309
310 setup_mount_tree(_metadata, log2_num);
311
312 expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, num - 1, mnts + 1);
313
314 mnts[0] = self->root_id;
315 check_mounted(_metadata, mnts, num);
316
317 // Cleanup
318 int ret;
319 uint64_t mnts2[num];
320 ret = umount2("/", MNT_DETACH);
321 ASSERT_EQ(ret, 0);
322
323 ret = mount("", "/", NULL, MS_PRIVATE, NULL);
324 ASSERT_EQ(ret, 0);
325
326 mnts2[0] = self->root_id;
327 expect_notify_mask_n(_metadata, self, FAN_MNT_DETACH, num - 1, mnts2 + 1);
328 verify_mount_ids(_metadata, mnts, mnts2, num);
329
330 check_mounted(_metadata, mnts, 1);
331 }
332
TEST_F(fanotify,fsmount)333 TEST_F(fanotify, fsmount)
334 {
335 int ret, fs, mnt;
336 uint64_t mnts[2] = { self->root_id };
337
338 fs = fsopen("tmpfs", 0);
339 ASSERT_GE(fs, 0);
340
341 ret = fsconfig(fs, FSCONFIG_CMD_CREATE, 0, 0, 0);
342 ASSERT_EQ(ret, 0);
343
344 mnt = fsmount(fs, 0, 0);
345 ASSERT_GE(mnt, 0);
346
347 close(fs);
348
349 ret = move_mount(mnt, "", AT_FDCWD, "/a", MOVE_MOUNT_F_EMPTY_PATH);
350 ASSERT_EQ(ret, 0);
351
352 close(mnt);
353
354 mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);
355 ASSERT_NE(mnts[0], mnts[1]);
356
357 check_mounted(_metadata, mnts, 2);
358
359 // Cleanup
360 uint64_t detach_id;
361 ret = umount("/a");
362 ASSERT_EQ(ret, 0);
363
364 detach_id = expect_notify_mask(_metadata, self, FAN_MNT_DETACH);
365 ASSERT_EQ(detach_id, mnts[1]);
366
367 check_mounted(_metadata, mnts, 1);
368 }
369
TEST_F(fanotify,reparent)370 TEST_F(fanotify, reparent)
371 {
372 uint64_t mnts[6] = { self->root_id };
373 uint64_t dmnts[3];
374 uint64_t masks[3];
375 unsigned int i;
376 int ret;
377
378 // Create setup with a[1] -> b[2] propagation
379 ret = mount("/", "/a", NULL, MS_BIND, NULL);
380 ASSERT_EQ(ret, 0);
381
382 ret = mount("", "/a", NULL, MS_SHARED, NULL);
383 ASSERT_EQ(ret, 0);
384
385 ret = mount("/a", "/b", NULL, MS_BIND, NULL);
386 ASSERT_EQ(ret, 0);
387
388 ret = mount("", "/b", NULL, MS_SLAVE, NULL);
389 ASSERT_EQ(ret, 0);
390
391 expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, 2, mnts + 1);
392
393 check_mounted(_metadata, mnts, 3);
394
395 // Mount on a[3], which is propagated to b[4]
396 ret = mount("/", "/a", NULL, MS_BIND, NULL);
397 ASSERT_EQ(ret, 0);
398
399 expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, 2, mnts + 3);
400
401 check_mounted(_metadata, mnts, 5);
402
403 // Mount on b[5], not propagated
404 ret = mount("/", "/b", NULL, MS_BIND, NULL);
405 ASSERT_EQ(ret, 0);
406
407 mnts[5] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);
408
409 check_mounted(_metadata, mnts, 6);
410
411 // Umount a[3], which is propagated to b[4], but not b[5]
412 // This will result in b[5] "falling" on b[2]
413 ret = umount("/a");
414 ASSERT_EQ(ret, 0);
415
416 expect_notify_n(_metadata, self, 3, masks, dmnts);
417 verify_mount_ids(_metadata, mnts + 3, dmnts, 3);
418
419 for (i = 0; i < 3; i++) {
420 if (dmnts[i] == mnts[5]) {
421 ASSERT_EQ(masks[i], FAN_MNT_ATTACH | FAN_MNT_DETACH);
422 } else {
423 ASSERT_EQ(masks[i], FAN_MNT_DETACH);
424 }
425 }
426
427 mnts[3] = mnts[5];
428 check_mounted(_metadata, mnts, 4);
429
430 // Cleanup
431 ret = umount("/b");
432 ASSERT_EQ(ret, 0);
433
434 ret = umount("/a");
435 ASSERT_EQ(ret, 0);
436
437 ret = umount("/b");
438 ASSERT_EQ(ret, 0);
439
440 expect_notify_mask_n(_metadata, self, FAN_MNT_DETACH, 3, dmnts);
441 verify_mount_ids(_metadata, mnts + 1, dmnts, 3);
442
443 check_mounted(_metadata, mnts, 1);
444 }
445
TEST_F(fanotify,rmdir)446 TEST_F(fanotify, rmdir)
447 {
448 uint64_t mnts[3] = { self->root_id };
449 int ret;
450
451 ret = mount("/", "/a", NULL, MS_BIND, NULL);
452 ASSERT_EQ(ret, 0);
453
454 ret = mount("/", "/a/b", NULL, MS_BIND, NULL);
455 ASSERT_EQ(ret, 0);
456
457 expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, 2, mnts + 1);
458
459 check_mounted(_metadata, mnts, 3);
460
461 ret = chdir("/a");
462 ASSERT_EQ(ret, 0);
463
464 ret = fork();
465 ASSERT_GE(ret, 0);
466
467 if (ret == 0) {
468 chdir("/");
469 unshare(CLONE_NEWNS);
470 mount("", "/", NULL, MS_REC|MS_PRIVATE, NULL);
471 umount2("/a", MNT_DETACH);
472 // This triggers a detach in the other namespace
473 rmdir("/a");
474 exit(0);
475 }
476 wait(NULL);
477
478 expect_notify_mask_n(_metadata, self, FAN_MNT_DETACH, 2, mnts + 1);
479 check_mounted(_metadata, mnts, 1);
480
481 // Cleanup
482 ret = chdir("/");
483 ASSERT_EQ(ret, 0);
484 }
485
TEST_F(fanotify,pivot_root)486 TEST_F(fanotify, pivot_root)
487 {
488 uint64_t mnts[3] = { self->root_id };
489 uint64_t mnts2[3];
490 int ret;
491
492 ret = mount("tmpfs", "/a", "tmpfs", 0, NULL);
493 ASSERT_EQ(ret, 0);
494
495 mnts[2] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);
496
497 ret = mkdir("/a/new", 0700);
498 ASSERT_EQ(ret, 0);
499
500 ret = mkdir("/a/old", 0700);
501 ASSERT_EQ(ret, 0);
502
503 ret = mount("/a", "/a/new", NULL, MS_BIND, NULL);
504 ASSERT_EQ(ret, 0);
505
506 mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);
507 check_mounted(_metadata, mnts, 3);
508
509 ret = syscall(SYS_pivot_root, "/a/new", "/a/new/old");
510 ASSERT_EQ(ret, 0);
511
512 expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH | FAN_MNT_DETACH, 2, mnts2);
513 verify_mount_ids(_metadata, mnts, mnts2, 2);
514 check_mounted(_metadata, mnts, 3);
515
516 // Cleanup
517 ret = syscall(SYS_pivot_root, "/old", "/old/a/new");
518 ASSERT_EQ(ret, 0);
519
520 ret = umount("/a/new");
521 ASSERT_EQ(ret, 0);
522
523 ret = umount("/a");
524 ASSERT_EQ(ret, 0);
525
526 check_mounted(_metadata, mnts, 1);
527 }
528
529 TEST_HARNESS_MAIN
530