| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555 |
- // SPDX-License-Identifier: GPL-2.0-or-later
- // Copyright (c) 2025 Miklos Szeredi <miklos@szeredi.hu>
- #define _GNU_SOURCE
- // Needed for linux/fanotify.h
- typedef struct {
- int val[2];
- } __kernel_fsid_t;
- #define __kernel_fsid_t __kernel_fsid_t
- #include <fcntl.h>
- #include <sched.h>
- #include <stdio.h>
- #include <string.h>
- #include <sys/stat.h>
- #include <sys/mount.h>
- #include <unistd.h>
- #include <sys/syscall.h>
- #include <sys/fanotify.h>
- #include "kselftest_harness.h"
- #include "../statmount/statmount.h"
- #include "../utils.h"
- static const char root_mntpoint_templ[] = "/tmp/mount-notify_test_root.XXXXXX";
- static const int mark_types[] = {
- FAN_MARK_FILESYSTEM,
- FAN_MARK_MOUNT,
- FAN_MARK_INODE
- };
- static const int mark_cmds[] = {
- FAN_MARK_ADD,
- FAN_MARK_REMOVE,
- FAN_MARK_FLUSH
- };
- #define NUM_FAN_FDS ARRAY_SIZE(mark_cmds)
- FIXTURE(fanotify) {
- int fan_fd[NUM_FAN_FDS];
- char buf[256];
- unsigned int rem;
- void *next;
- char root_mntpoint[sizeof(root_mntpoint_templ)];
- int orig_root;
- int orig_ns_fd;
- int ns_fd;
- uint64_t root_id;
- };
- FIXTURE_SETUP(fanotify)
- {
- int i, ret;
- self->orig_ns_fd = open("/proc/self/ns/mnt", O_RDONLY);
- ASSERT_GE(self->orig_ns_fd, 0);
- ret = setup_userns();
- ASSERT_EQ(ret, 0);
- self->ns_fd = open("/proc/self/ns/mnt", O_RDONLY);
- ASSERT_GE(self->ns_fd, 0);
- strcpy(self->root_mntpoint, root_mntpoint_templ);
- ASSERT_NE(mkdtemp(self->root_mntpoint), NULL);
- self->orig_root = open("/", O_PATH | O_CLOEXEC);
- ASSERT_GE(self->orig_root, 0);
- ASSERT_EQ(mount("tmpfs", self->root_mntpoint, "tmpfs", 0, NULL), 0);
- ASSERT_EQ(chroot(self->root_mntpoint), 0);
- ASSERT_EQ(chdir("/"), 0);
- ASSERT_EQ(mkdir("a", 0700), 0);
- ASSERT_EQ(mkdir("b", 0700), 0);
- self->root_id = get_unique_mnt_id("/");
- ASSERT_NE(self->root_id, 0);
- for (i = 0; i < NUM_FAN_FDS; i++) {
- int fan_fd = fanotify_init(FAN_REPORT_FID, 0);
- // Verify that watching tmpfs mounted inside userns is allowed
- ret = fanotify_mark(fan_fd, FAN_MARK_ADD | mark_types[i],
- FAN_OPEN, AT_FDCWD, "/");
- ASSERT_EQ(ret, 0);
- // ...but watching entire orig root filesystem is not allowed
- ret = fanotify_mark(fan_fd, FAN_MARK_ADD | FAN_MARK_FILESYSTEM,
- FAN_OPEN, self->orig_root, ".");
- ASSERT_NE(ret, 0);
- close(fan_fd);
- self->fan_fd[i] = fanotify_init(FAN_REPORT_MNT | FAN_NONBLOCK,
- 0);
- ASSERT_GE(self->fan_fd[i], 0);
- // Verify that watching mntns where group was created is allowed
- ret = fanotify_mark(self->fan_fd[i], FAN_MARK_ADD |
- FAN_MARK_MNTNS,
- FAN_MNT_ATTACH | FAN_MNT_DETACH,
- self->ns_fd, NULL);
- ASSERT_EQ(ret, 0);
- // ...but watching orig mntns is not allowed
- ret = fanotify_mark(self->fan_fd[i], FAN_MARK_ADD |
- FAN_MARK_MNTNS,
- FAN_MNT_ATTACH | FAN_MNT_DETACH,
- self->orig_ns_fd, NULL);
- ASSERT_NE(ret, 0);
- // On fd[0] we do an extra ADD that changes nothing.
- // On fd[1]/fd[2] we REMOVE/FLUSH which removes the mark.
- ret = fanotify_mark(self->fan_fd[i], mark_cmds[i] |
- FAN_MARK_MNTNS,
- FAN_MNT_ATTACH | FAN_MNT_DETACH,
- self->ns_fd, NULL);
- ASSERT_EQ(ret, 0);
- }
- self->rem = 0;
- }
- FIXTURE_TEARDOWN(fanotify)
- {
- int i;
- ASSERT_EQ(self->rem, 0);
- for (i = 0; i < NUM_FAN_FDS; i++)
- close(self->fan_fd[i]);
- ASSERT_EQ(fchdir(self->orig_root), 0);
- ASSERT_EQ(chroot("."), 0);
- EXPECT_EQ(umount2(self->root_mntpoint, MNT_DETACH), 0);
- EXPECT_EQ(chdir(self->root_mntpoint), 0);
- EXPECT_EQ(chdir("/"), 0);
- EXPECT_EQ(rmdir(self->root_mntpoint), 0);
- }
- static uint64_t expect_notify(struct __test_metadata *const _metadata,
- FIXTURE_DATA(fanotify) *self,
- uint64_t *mask)
- {
- struct fanotify_event_metadata *meta;
- struct fanotify_event_info_mnt *mnt;
- unsigned int thislen;
- if (!self->rem) {
- ssize_t len;
- int i;
- for (i = NUM_FAN_FDS - 1; i >= 0; i--) {
- len = read(self->fan_fd[i], self->buf,
- sizeof(self->buf));
- if (i > 0) {
- // Groups 1,2 should get EAGAIN
- ASSERT_EQ(len, -1);
- ASSERT_EQ(errno, EAGAIN);
- } else {
- // Group 0 should get events
- ASSERT_GT(len, 0);
- }
- }
- self->rem = len;
- self->next = (void *) self->buf;
- }
- meta = self->next;
- ASSERT_TRUE(FAN_EVENT_OK(meta, self->rem));
- thislen = meta->event_len;
- self->rem -= thislen;
- self->next += thislen;
- *mask = meta->mask;
- thislen -= sizeof(*meta);
- mnt = ((void *) meta) + meta->event_len - thislen;
- ASSERT_EQ(thislen, sizeof(*mnt));
- return mnt->mnt_id;
- }
- static void expect_notify_n(struct __test_metadata *const _metadata,
- FIXTURE_DATA(fanotify) *self,
- unsigned int n, uint64_t mask[], uint64_t mnts[])
- {
- unsigned int i;
- for (i = 0; i < n; i++)
- mnts[i] = expect_notify(_metadata, self, &mask[i]);
- }
- static uint64_t expect_notify_mask(struct __test_metadata *const _metadata,
- FIXTURE_DATA(fanotify) *self,
- uint64_t expect_mask)
- {
- uint64_t mntid, mask;
- mntid = expect_notify(_metadata, self, &mask);
- ASSERT_EQ(expect_mask, mask);
- return mntid;
- }
- static void expect_notify_mask_n(struct __test_metadata *const _metadata,
- FIXTURE_DATA(fanotify) *self,
- uint64_t mask, unsigned int n, uint64_t mnts[])
- {
- unsigned int i;
- for (i = 0; i < n; i++)
- mnts[i] = expect_notify_mask(_metadata, self, mask);
- }
- static void verify_mount_ids(struct __test_metadata *const _metadata,
- const uint64_t list1[], const uint64_t list2[],
- size_t num)
- {
- unsigned int i, j;
- // Check that neither list has any duplicates
- for (i = 0; i < num; i++) {
- for (j = 0; j < num; j++) {
- if (i != j) {
- ASSERT_NE(list1[i], list1[j]);
- ASSERT_NE(list2[i], list2[j]);
- }
- }
- }
- // Check that all list1 memebers can be found in list2. Together with
- // the above it means that the list1 and list2 represent the same sets.
- for (i = 0; i < num; i++) {
- for (j = 0; j < num; j++) {
- if (list1[i] == list2[j])
- break;
- }
- ASSERT_NE(j, num);
- }
- }
- static void check_mounted(struct __test_metadata *const _metadata,
- const uint64_t mnts[], size_t num)
- {
- ssize_t ret;
- uint64_t *list;
- list = malloc((num + 1) * sizeof(list[0]));
- ASSERT_NE(list, NULL);
- ret = listmount(LSMT_ROOT, 0, 0, list, num + 1, 0);
- ASSERT_EQ(ret, num);
- verify_mount_ids(_metadata, mnts, list, num);
- free(list);
- }
- static void setup_mount_tree(struct __test_metadata *const _metadata,
- int log2_num)
- {
- int ret, i;
- ret = mount("", "/", NULL, MS_SHARED, NULL);
- ASSERT_EQ(ret, 0);
- for (i = 0; i < log2_num; i++) {
- ret = mount("/", "/", NULL, MS_BIND, NULL);
- ASSERT_EQ(ret, 0);
- }
- }
- TEST_F(fanotify, bind)
- {
- int ret;
- uint64_t mnts[2] = { self->root_id };
- ret = mount("/", "/", NULL, MS_BIND, NULL);
- ASSERT_EQ(ret, 0);
- mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);
- ASSERT_NE(mnts[0], mnts[1]);
- check_mounted(_metadata, mnts, 2);
- // Cleanup
- uint64_t detach_id;
- ret = umount("/");
- ASSERT_EQ(ret, 0);
- detach_id = expect_notify_mask(_metadata, self, FAN_MNT_DETACH);
- ASSERT_EQ(detach_id, mnts[1]);
- check_mounted(_metadata, mnts, 1);
- }
- TEST_F(fanotify, move)
- {
- int ret;
- uint64_t mnts[2] = { self->root_id };
- uint64_t move_id;
- ret = mount("/", "/a", NULL, MS_BIND, NULL);
- ASSERT_EQ(ret, 0);
- mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);
- ASSERT_NE(mnts[0], mnts[1]);
- check_mounted(_metadata, mnts, 2);
- ret = move_mount(AT_FDCWD, "/a", AT_FDCWD, "/b", 0);
- ASSERT_EQ(ret, 0);
- move_id = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH | FAN_MNT_DETACH);
- ASSERT_EQ(move_id, mnts[1]);
- // Cleanup
- ret = umount("/b");
- ASSERT_EQ(ret, 0);
- check_mounted(_metadata, mnts, 1);
- }
- TEST_F(fanotify, propagate)
- {
- const unsigned int log2_num = 4;
- const unsigned int num = (1 << log2_num);
- uint64_t mnts[num];
- setup_mount_tree(_metadata, log2_num);
- expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, num - 1, mnts + 1);
- mnts[0] = self->root_id;
- check_mounted(_metadata, mnts, num);
- // Cleanup
- int ret;
- uint64_t mnts2[num];
- ret = umount2("/", MNT_DETACH);
- ASSERT_EQ(ret, 0);
- ret = mount("", "/", NULL, MS_PRIVATE, NULL);
- ASSERT_EQ(ret, 0);
- mnts2[0] = self->root_id;
- expect_notify_mask_n(_metadata, self, FAN_MNT_DETACH, num - 1, mnts2 + 1);
- verify_mount_ids(_metadata, mnts, mnts2, num);
- check_mounted(_metadata, mnts, 1);
- }
- TEST_F(fanotify, fsmount)
- {
- int ret, fs, mnt;
- uint64_t mnts[2] = { self->root_id };
- fs = fsopen("tmpfs", 0);
- ASSERT_GE(fs, 0);
- ret = fsconfig(fs, FSCONFIG_CMD_CREATE, 0, 0, 0);
- ASSERT_EQ(ret, 0);
- mnt = fsmount(fs, 0, 0);
- ASSERT_GE(mnt, 0);
- close(fs);
- ret = move_mount(mnt, "", AT_FDCWD, "/a", MOVE_MOUNT_F_EMPTY_PATH);
- ASSERT_EQ(ret, 0);
- close(mnt);
- mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);
- ASSERT_NE(mnts[0], mnts[1]);
- check_mounted(_metadata, mnts, 2);
- // Cleanup
- uint64_t detach_id;
- ret = umount("/a");
- ASSERT_EQ(ret, 0);
- detach_id = expect_notify_mask(_metadata, self, FAN_MNT_DETACH);
- ASSERT_EQ(detach_id, mnts[1]);
- check_mounted(_metadata, mnts, 1);
- }
- TEST_F(fanotify, reparent)
- {
- uint64_t mnts[6] = { self->root_id };
- uint64_t dmnts[3];
- uint64_t masks[3];
- unsigned int i;
- int ret;
- // Create setup with a[1] -> b[2] propagation
- ret = mount("/", "/a", NULL, MS_BIND, NULL);
- ASSERT_EQ(ret, 0);
- ret = mount("", "/a", NULL, MS_SHARED, NULL);
- ASSERT_EQ(ret, 0);
- ret = mount("/a", "/b", NULL, MS_BIND, NULL);
- ASSERT_EQ(ret, 0);
- ret = mount("", "/b", NULL, MS_SLAVE, NULL);
- ASSERT_EQ(ret, 0);
- expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, 2, mnts + 1);
- check_mounted(_metadata, mnts, 3);
- // Mount on a[3], which is propagated to b[4]
- ret = mount("/", "/a", NULL, MS_BIND, NULL);
- ASSERT_EQ(ret, 0);
- expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, 2, mnts + 3);
- check_mounted(_metadata, mnts, 5);
- // Mount on b[5], not propagated
- ret = mount("/", "/b", NULL, MS_BIND, NULL);
- ASSERT_EQ(ret, 0);
- mnts[5] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);
- check_mounted(_metadata, mnts, 6);
- // Umount a[3], which is propagated to b[4], but not b[5]
- // This will result in b[5] "falling" on b[2]
- ret = umount("/a");
- ASSERT_EQ(ret, 0);
- expect_notify_n(_metadata, self, 3, masks, dmnts);
- verify_mount_ids(_metadata, mnts + 3, dmnts, 3);
- for (i = 0; i < 3; i++) {
- if (dmnts[i] == mnts[5]) {
- ASSERT_EQ(masks[i], FAN_MNT_ATTACH | FAN_MNT_DETACH);
- } else {
- ASSERT_EQ(masks[i], FAN_MNT_DETACH);
- }
- }
- mnts[3] = mnts[5];
- check_mounted(_metadata, mnts, 4);
- // Cleanup
- ret = umount("/b");
- ASSERT_EQ(ret, 0);
- ret = umount("/a");
- ASSERT_EQ(ret, 0);
- ret = umount("/b");
- ASSERT_EQ(ret, 0);
- expect_notify_mask_n(_metadata, self, FAN_MNT_DETACH, 3, dmnts);
- verify_mount_ids(_metadata, mnts + 1, dmnts, 3);
- check_mounted(_metadata, mnts, 1);
- }
- TEST_F(fanotify, rmdir)
- {
- uint64_t mnts[3] = { self->root_id };
- int ret;
- ret = mount("/", "/a", NULL, MS_BIND, NULL);
- ASSERT_EQ(ret, 0);
- ret = mount("/", "/a/b", NULL, MS_BIND, NULL);
- ASSERT_EQ(ret, 0);
- expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, 2, mnts + 1);
- check_mounted(_metadata, mnts, 3);
- ret = chdir("/a");
- ASSERT_EQ(ret, 0);
- ret = fork();
- ASSERT_GE(ret, 0);
- if (ret == 0) {
- chdir("/");
- unshare(CLONE_NEWNS);
- mount("", "/", NULL, MS_REC|MS_PRIVATE, NULL);
- umount2("/a", MNT_DETACH);
- // This triggers a detach in the other namespace
- rmdir("/a");
- exit(0);
- }
- wait(NULL);
- expect_notify_mask_n(_metadata, self, FAN_MNT_DETACH, 2, mnts + 1);
- check_mounted(_metadata, mnts, 1);
- // Cleanup
- ret = chdir("/");
- ASSERT_EQ(ret, 0);
- }
- TEST_F(fanotify, pivot_root)
- {
- uint64_t mnts[3] = { self->root_id };
- uint64_t mnts2[3];
- int ret;
- ret = mount("tmpfs", "/a", "tmpfs", 0, NULL);
- ASSERT_EQ(ret, 0);
- mnts[2] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);
- ret = mkdir("/a/new", 0700);
- ASSERT_EQ(ret, 0);
- ret = mkdir("/a/old", 0700);
- ASSERT_EQ(ret, 0);
- ret = mount("/a", "/a/new", NULL, MS_BIND, NULL);
- ASSERT_EQ(ret, 0);
- mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);
- check_mounted(_metadata, mnts, 3);
- ret = syscall(SYS_pivot_root, "/a/new", "/a/new/old");
- ASSERT_EQ(ret, 0);
- expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH | FAN_MNT_DETACH, 2, mnts2);
- verify_mount_ids(_metadata, mnts, mnts2, 2);
- check_mounted(_metadata, mnts, 3);
- // Cleanup
- ret = syscall(SYS_pivot_root, "/old", "/old/a/new");
- ASSERT_EQ(ret, 0);
- ret = umount("/a/new");
- ASSERT_EQ(ret, 0);
- ret = umount("/a");
- ASSERT_EQ(ret, 0);
- check_mounted(_metadata, mnts, 1);
- }
- TEST_HARNESS_MAIN
|