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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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