Path: blob/master/tools/testing/selftests/filesystems/mount-notify/mount-notify_test.c
29271 views
// SPDX-License-Identifier: GPL-2.0-or-later1// Copyright (c) 2025 Miklos Szeredi <[email protected]>23#define _GNU_SOURCE45// Needed for linux/fanotify.h6typedef struct {7int val[2];8} __kernel_fsid_t;9#define __kernel_fsid_t __kernel_fsid_t1011#include <fcntl.h>12#include <sched.h>13#include <stdio.h>14#include <string.h>15#include <sys/stat.h>16#include <sys/mount.h>17#include <unistd.h>18#include <sys/syscall.h>19#include <sys/fanotify.h>2021#include "../../kselftest_harness.h"22#include "../statmount/statmount.h"23#include "../utils.h"2425static const char root_mntpoint_templ[] = "/tmp/mount-notify_test_root.XXXXXX";2627static const int mark_cmds[] = {28FAN_MARK_ADD,29FAN_MARK_REMOVE,30FAN_MARK_FLUSH31};3233#define NUM_FAN_FDS ARRAY_SIZE(mark_cmds)3435FIXTURE(fanotify) {36int fan_fd[NUM_FAN_FDS];37char buf[256];38unsigned int rem;39void *next;40char root_mntpoint[sizeof(root_mntpoint_templ)];41int orig_root;42int ns_fd;43uint64_t root_id;44};4546FIXTURE_SETUP(fanotify)47{48int i, ret;4950ASSERT_EQ(unshare(CLONE_NEWNS), 0);5152self->ns_fd = open("/proc/self/ns/mnt", O_RDONLY);53ASSERT_GE(self->ns_fd, 0);5455ASSERT_EQ(mount("", "/", NULL, MS_REC|MS_PRIVATE, NULL), 0);5657strcpy(self->root_mntpoint, root_mntpoint_templ);58ASSERT_NE(mkdtemp(self->root_mntpoint), NULL);5960self->orig_root = open("/", O_PATH | O_CLOEXEC);61ASSERT_GE(self->orig_root, 0);6263ASSERT_EQ(mount("tmpfs", self->root_mntpoint, "tmpfs", 0, NULL), 0);6465ASSERT_EQ(chroot(self->root_mntpoint), 0);6667ASSERT_EQ(chdir("/"), 0);6869ASSERT_EQ(mkdir("a", 0700), 0);7071ASSERT_EQ(mkdir("b", 0700), 0);7273self->root_id = get_unique_mnt_id("/");74ASSERT_NE(self->root_id, 0);7576for (i = 0; i < NUM_FAN_FDS; i++) {77self->fan_fd[i] = fanotify_init(FAN_REPORT_MNT | FAN_NONBLOCK,780);79ASSERT_GE(self->fan_fd[i], 0);80ret = fanotify_mark(self->fan_fd[i], FAN_MARK_ADD |81FAN_MARK_MNTNS,82FAN_MNT_ATTACH | FAN_MNT_DETACH,83self->ns_fd, NULL);84ASSERT_EQ(ret, 0);85// On fd[0] we do an extra ADD that changes nothing.86// On fd[1]/fd[2] we REMOVE/FLUSH which removes the mark.87ret = fanotify_mark(self->fan_fd[i], mark_cmds[i] |88FAN_MARK_MNTNS,89FAN_MNT_ATTACH | FAN_MNT_DETACH,90self->ns_fd, NULL);91ASSERT_EQ(ret, 0);92}9394self->rem = 0;95}9697FIXTURE_TEARDOWN(fanotify)98{99int i;100101ASSERT_EQ(self->rem, 0);102for (i = 0; i < NUM_FAN_FDS; i++)103close(self->fan_fd[i]);104105ASSERT_EQ(fchdir(self->orig_root), 0);106107ASSERT_EQ(chroot("."), 0);108109EXPECT_EQ(umount2(self->root_mntpoint, MNT_DETACH), 0);110EXPECT_EQ(chdir(self->root_mntpoint), 0);111EXPECT_EQ(chdir("/"), 0);112EXPECT_EQ(rmdir(self->root_mntpoint), 0);113}114115static uint64_t expect_notify(struct __test_metadata *const _metadata,116FIXTURE_DATA(fanotify) *self,117uint64_t *mask)118{119struct fanotify_event_metadata *meta;120struct fanotify_event_info_mnt *mnt;121unsigned int thislen;122123if (!self->rem) {124ssize_t len;125int i;126127for (i = NUM_FAN_FDS - 1; i >= 0; i--) {128len = read(self->fan_fd[i], self->buf,129sizeof(self->buf));130if (i > 0) {131// Groups 1,2 should get EAGAIN132ASSERT_EQ(len, -1);133ASSERT_EQ(errno, EAGAIN);134} else {135// Group 0 should get events136ASSERT_GT(len, 0);137}138}139140self->rem = len;141self->next = (void *) self->buf;142}143144meta = self->next;145ASSERT_TRUE(FAN_EVENT_OK(meta, self->rem));146147thislen = meta->event_len;148self->rem -= thislen;149self->next += thislen;150151*mask = meta->mask;152thislen -= sizeof(*meta);153154mnt = ((void *) meta) + meta->event_len - thislen;155156ASSERT_EQ(thislen, sizeof(*mnt));157158return mnt->mnt_id;159}160161static void expect_notify_n(struct __test_metadata *const _metadata,162FIXTURE_DATA(fanotify) *self,163unsigned int n, uint64_t mask[], uint64_t mnts[])164{165unsigned int i;166167for (i = 0; i < n; i++)168mnts[i] = expect_notify(_metadata, self, &mask[i]);169}170171static uint64_t expect_notify_mask(struct __test_metadata *const _metadata,172FIXTURE_DATA(fanotify) *self,173uint64_t expect_mask)174{175uint64_t mntid, mask;176177mntid = expect_notify(_metadata, self, &mask);178ASSERT_EQ(expect_mask, mask);179180return mntid;181}182183184static void expect_notify_mask_n(struct __test_metadata *const _metadata,185FIXTURE_DATA(fanotify) *self,186uint64_t mask, unsigned int n, uint64_t mnts[])187{188unsigned int i;189190for (i = 0; i < n; i++)191mnts[i] = expect_notify_mask(_metadata, self, mask);192}193194static void verify_mount_ids(struct __test_metadata *const _metadata,195const uint64_t list1[], const uint64_t list2[],196size_t num)197{198unsigned int i, j;199200// Check that neither list has any duplicates201for (i = 0; i < num; i++) {202for (j = 0; j < num; j++) {203if (i != j) {204ASSERT_NE(list1[i], list1[j]);205ASSERT_NE(list2[i], list2[j]);206}207}208}209// Check that all list1 memebers can be found in list2. Together with210// the above it means that the list1 and list2 represent the same sets.211for (i = 0; i < num; i++) {212for (j = 0; j < num; j++) {213if (list1[i] == list2[j])214break;215}216ASSERT_NE(j, num);217}218}219220static void check_mounted(struct __test_metadata *const _metadata,221const uint64_t mnts[], size_t num)222{223ssize_t ret;224uint64_t *list;225226list = malloc((num + 1) * sizeof(list[0]));227ASSERT_NE(list, NULL);228229ret = listmount(LSMT_ROOT, 0, 0, list, num + 1, 0);230ASSERT_EQ(ret, num);231232verify_mount_ids(_metadata, mnts, list, num);233234free(list);235}236237static void setup_mount_tree(struct __test_metadata *const _metadata,238int log2_num)239{240int ret, i;241242ret = mount("", "/", NULL, MS_SHARED, NULL);243ASSERT_EQ(ret, 0);244245for (i = 0; i < log2_num; i++) {246ret = mount("/", "/", NULL, MS_BIND, NULL);247ASSERT_EQ(ret, 0);248}249}250251TEST_F(fanotify, bind)252{253int ret;254uint64_t mnts[2] = { self->root_id };255256ret = mount("/", "/", NULL, MS_BIND, NULL);257ASSERT_EQ(ret, 0);258259mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);260ASSERT_NE(mnts[0], mnts[1]);261262check_mounted(_metadata, mnts, 2);263264// Cleanup265uint64_t detach_id;266ret = umount("/");267ASSERT_EQ(ret, 0);268269detach_id = expect_notify_mask(_metadata, self, FAN_MNT_DETACH);270ASSERT_EQ(detach_id, mnts[1]);271272check_mounted(_metadata, mnts, 1);273}274275TEST_F(fanotify, move)276{277int ret;278uint64_t mnts[2] = { self->root_id };279uint64_t move_id;280281ret = mount("/", "/a", NULL, MS_BIND, NULL);282ASSERT_EQ(ret, 0);283284mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);285ASSERT_NE(mnts[0], mnts[1]);286287check_mounted(_metadata, mnts, 2);288289ret = move_mount(AT_FDCWD, "/a", AT_FDCWD, "/b", 0);290ASSERT_EQ(ret, 0);291292move_id = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH | FAN_MNT_DETACH);293ASSERT_EQ(move_id, mnts[1]);294295// Cleanup296ret = umount("/b");297ASSERT_EQ(ret, 0);298299check_mounted(_metadata, mnts, 1);300}301302TEST_F(fanotify, propagate)303{304const unsigned int log2_num = 4;305const unsigned int num = (1 << log2_num);306uint64_t mnts[num];307308setup_mount_tree(_metadata, log2_num);309310expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, num - 1, mnts + 1);311312mnts[0] = self->root_id;313check_mounted(_metadata, mnts, num);314315// Cleanup316int ret;317uint64_t mnts2[num];318ret = umount2("/", MNT_DETACH);319ASSERT_EQ(ret, 0);320321ret = mount("", "/", NULL, MS_PRIVATE, NULL);322ASSERT_EQ(ret, 0);323324mnts2[0] = self->root_id;325expect_notify_mask_n(_metadata, self, FAN_MNT_DETACH, num - 1, mnts2 + 1);326verify_mount_ids(_metadata, mnts, mnts2, num);327328check_mounted(_metadata, mnts, 1);329}330331TEST_F(fanotify, fsmount)332{333int ret, fs, mnt;334uint64_t mnts[2] = { self->root_id };335336fs = fsopen("tmpfs", 0);337ASSERT_GE(fs, 0);338339ret = fsconfig(fs, FSCONFIG_CMD_CREATE, 0, 0, 0);340ASSERT_EQ(ret, 0);341342mnt = fsmount(fs, 0, 0);343ASSERT_GE(mnt, 0);344345close(fs);346347ret = move_mount(mnt, "", AT_FDCWD, "/a", MOVE_MOUNT_F_EMPTY_PATH);348ASSERT_EQ(ret, 0);349350close(mnt);351352mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);353ASSERT_NE(mnts[0], mnts[1]);354355check_mounted(_metadata, mnts, 2);356357// Cleanup358uint64_t detach_id;359ret = umount("/a");360ASSERT_EQ(ret, 0);361362detach_id = expect_notify_mask(_metadata, self, FAN_MNT_DETACH);363ASSERT_EQ(detach_id, mnts[1]);364365check_mounted(_metadata, mnts, 1);366}367368TEST_F(fanotify, reparent)369{370uint64_t mnts[6] = { self->root_id };371uint64_t dmnts[3];372uint64_t masks[3];373unsigned int i;374int ret;375376// Create setup with a[1] -> b[2] propagation377ret = mount("/", "/a", NULL, MS_BIND, NULL);378ASSERT_EQ(ret, 0);379380ret = mount("", "/a", NULL, MS_SHARED, NULL);381ASSERT_EQ(ret, 0);382383ret = mount("/a", "/b", NULL, MS_BIND, NULL);384ASSERT_EQ(ret, 0);385386ret = mount("", "/b", NULL, MS_SLAVE, NULL);387ASSERT_EQ(ret, 0);388389expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, 2, mnts + 1);390391check_mounted(_metadata, mnts, 3);392393// Mount on a[3], which is propagated to b[4]394ret = mount("/", "/a", NULL, MS_BIND, NULL);395ASSERT_EQ(ret, 0);396397expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, 2, mnts + 3);398399check_mounted(_metadata, mnts, 5);400401// Mount on b[5], not propagated402ret = mount("/", "/b", NULL, MS_BIND, NULL);403ASSERT_EQ(ret, 0);404405mnts[5] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);406407check_mounted(_metadata, mnts, 6);408409// Umount a[3], which is propagated to b[4], but not b[5]410// This will result in b[5] "falling" on b[2]411ret = umount("/a");412ASSERT_EQ(ret, 0);413414expect_notify_n(_metadata, self, 3, masks, dmnts);415verify_mount_ids(_metadata, mnts + 3, dmnts, 3);416417for (i = 0; i < 3; i++) {418if (dmnts[i] == mnts[5]) {419ASSERT_EQ(masks[i], FAN_MNT_ATTACH | FAN_MNT_DETACH);420} else {421ASSERT_EQ(masks[i], FAN_MNT_DETACH);422}423}424425mnts[3] = mnts[5];426check_mounted(_metadata, mnts, 4);427428// Cleanup429ret = umount("/b");430ASSERT_EQ(ret, 0);431432ret = umount("/a");433ASSERT_EQ(ret, 0);434435ret = umount("/b");436ASSERT_EQ(ret, 0);437438expect_notify_mask_n(_metadata, self, FAN_MNT_DETACH, 3, dmnts);439verify_mount_ids(_metadata, mnts + 1, dmnts, 3);440441check_mounted(_metadata, mnts, 1);442}443444TEST_F(fanotify, rmdir)445{446uint64_t mnts[3] = { self->root_id };447int ret;448449ret = mount("/", "/a", NULL, MS_BIND, NULL);450ASSERT_EQ(ret, 0);451452ret = mount("/", "/a/b", NULL, MS_BIND, NULL);453ASSERT_EQ(ret, 0);454455expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, 2, mnts + 1);456457check_mounted(_metadata, mnts, 3);458459ret = chdir("/a");460ASSERT_EQ(ret, 0);461462ret = fork();463ASSERT_GE(ret, 0);464465if (ret == 0) {466chdir("/");467unshare(CLONE_NEWNS);468mount("", "/", NULL, MS_REC|MS_PRIVATE, NULL);469umount2("/a", MNT_DETACH);470// This triggers a detach in the other namespace471rmdir("/a");472exit(0);473}474wait(NULL);475476expect_notify_mask_n(_metadata, self, FAN_MNT_DETACH, 2, mnts + 1);477check_mounted(_metadata, mnts, 1);478479// Cleanup480ret = chdir("/");481ASSERT_EQ(ret, 0);482}483484TEST_F(fanotify, pivot_root)485{486uint64_t mnts[3] = { self->root_id };487uint64_t mnts2[3];488int ret;489490ret = mount("tmpfs", "/a", "tmpfs", 0, NULL);491ASSERT_EQ(ret, 0);492493mnts[2] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);494495ret = mkdir("/a/new", 0700);496ASSERT_EQ(ret, 0);497498ret = mkdir("/a/old", 0700);499ASSERT_EQ(ret, 0);500501ret = mount("/a", "/a/new", NULL, MS_BIND, NULL);502ASSERT_EQ(ret, 0);503504mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);505check_mounted(_metadata, mnts, 3);506507ret = syscall(SYS_pivot_root, "/a/new", "/a/new/old");508ASSERT_EQ(ret, 0);509510expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH | FAN_MNT_DETACH, 2, mnts2);511verify_mount_ids(_metadata, mnts, mnts2, 2);512check_mounted(_metadata, mnts, 3);513514// Cleanup515ret = syscall(SYS_pivot_root, "/old", "/old/a/new");516ASSERT_EQ(ret, 0);517518ret = umount("/a/new");519ASSERT_EQ(ret, 0);520521ret = umount("/a");522ASSERT_EQ(ret, 0);523524check_mounted(_metadata, mnts, 1);525}526527TEST_HARNESS_MAIN528529530