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