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