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