Path: blob/master/tools/testing/selftests/filesystems/mount-notify/mount-notify_test_ns.c
26304 views
// SPDX-License-Identifier: GPL-2.0-or-later1// Copyright (c) 2025 Miklos Szeredi <[email protected]>23#define _GNU_SOURCE4#include <fcntl.h>5#include <sched.h>6#include <stdio.h>7#include <string.h>8#include <sys/stat.h>9#include <sys/mount.h>10#include <unistd.h>11#include <sys/syscall.h>1213#include "../../kselftest_harness.h"14#include "../../pidfd/pidfd.h"15#include "../statmount/statmount.h"16#include "../utils.h"1718// Needed for linux/fanotify.h19#ifndef __kernel_fsid_t20typedef struct {21int val[2];22} __kernel_fsid_t;23#endif2425#include <sys/fanotify.h>2627static const char root_mntpoint_templ[] = "/tmp/mount-notify_test_root.XXXXXX";2829static const int mark_types[] = {30FAN_MARK_FILESYSTEM,31FAN_MARK_MOUNT,32FAN_MARK_INODE33};3435static const int mark_cmds[] = {36FAN_MARK_ADD,37FAN_MARK_REMOVE,38FAN_MARK_FLUSH39};4041#define NUM_FAN_FDS ARRAY_SIZE(mark_cmds)4243FIXTURE(fanotify) {44int fan_fd[NUM_FAN_FDS];45char buf[256];46unsigned int rem;47void *next;48char root_mntpoint[sizeof(root_mntpoint_templ)];49int orig_root;50int orig_ns_fd;51int ns_fd;52uint64_t root_id;53};5455FIXTURE_SETUP(fanotify)56{57int i, ret;5859self->orig_ns_fd = open("/proc/self/ns/mnt", O_RDONLY);60ASSERT_GE(self->orig_ns_fd, 0);6162ret = setup_userns();63ASSERT_EQ(ret, 0);6465self->ns_fd = open("/proc/self/ns/mnt", O_RDONLY);66ASSERT_GE(self->ns_fd, 0);6768strcpy(self->root_mntpoint, root_mntpoint_templ);69ASSERT_NE(mkdtemp(self->root_mntpoint), NULL);7071self->orig_root = open("/", O_PATH | O_CLOEXEC);72ASSERT_GE(self->orig_root, 0);7374ASSERT_EQ(mount("tmpfs", self->root_mntpoint, "tmpfs", 0, NULL), 0);7576ASSERT_EQ(chroot(self->root_mntpoint), 0);7778ASSERT_EQ(chdir("/"), 0);7980ASSERT_EQ(mkdir("a", 0700), 0);8182ASSERT_EQ(mkdir("b", 0700), 0);8384self->root_id = get_unique_mnt_id("/");85ASSERT_NE(self->root_id, 0);8687for (i = 0; i < NUM_FAN_FDS; i++) {88int fan_fd = fanotify_init(FAN_REPORT_FID, 0);89// Verify that watching tmpfs mounted inside userns is allowed90ret = fanotify_mark(fan_fd, FAN_MARK_ADD | mark_types[i],91FAN_OPEN, AT_FDCWD, "/");92ASSERT_EQ(ret, 0);93// ...but watching entire orig root filesystem is not allowed94ret = fanotify_mark(fan_fd, FAN_MARK_ADD | FAN_MARK_FILESYSTEM,95FAN_OPEN, self->orig_root, ".");96ASSERT_NE(ret, 0);97close(fan_fd);9899self->fan_fd[i] = fanotify_init(FAN_REPORT_MNT | FAN_NONBLOCK,1000);101ASSERT_GE(self->fan_fd[i], 0);102// Verify that watching mntns where group was created is allowed103ret = fanotify_mark(self->fan_fd[i], FAN_MARK_ADD |104FAN_MARK_MNTNS,105FAN_MNT_ATTACH | FAN_MNT_DETACH,106self->ns_fd, NULL);107ASSERT_EQ(ret, 0);108// ...but watching orig mntns is not allowed109ret = fanotify_mark(self->fan_fd[i], FAN_MARK_ADD |110FAN_MARK_MNTNS,111FAN_MNT_ATTACH | FAN_MNT_DETACH,112self->orig_ns_fd, NULL);113ASSERT_NE(ret, 0);114// On fd[0] we do an extra ADD that changes nothing.115// On fd[1]/fd[2] we REMOVE/FLUSH which removes the mark.116ret = fanotify_mark(self->fan_fd[i], mark_cmds[i] |117FAN_MARK_MNTNS,118FAN_MNT_ATTACH | FAN_MNT_DETACH,119self->ns_fd, NULL);120ASSERT_EQ(ret, 0);121}122123self->rem = 0;124}125126FIXTURE_TEARDOWN(fanotify)127{128int i;129130ASSERT_EQ(self->rem, 0);131for (i = 0; i < NUM_FAN_FDS; i++)132close(self->fan_fd[i]);133134ASSERT_EQ(fchdir(self->orig_root), 0);135136ASSERT_EQ(chroot("."), 0);137138EXPECT_EQ(umount2(self->root_mntpoint, MNT_DETACH), 0);139EXPECT_EQ(chdir(self->root_mntpoint), 0);140EXPECT_EQ(chdir("/"), 0);141EXPECT_EQ(rmdir(self->root_mntpoint), 0);142}143144static uint64_t expect_notify(struct __test_metadata *const _metadata,145FIXTURE_DATA(fanotify) *self,146uint64_t *mask)147{148struct fanotify_event_metadata *meta;149struct fanotify_event_info_mnt *mnt;150unsigned int thislen;151152if (!self->rem) {153ssize_t len;154int i;155156for (i = NUM_FAN_FDS - 1; i >= 0; i--) {157len = read(self->fan_fd[i], self->buf,158sizeof(self->buf));159if (i > 0) {160// Groups 1,2 should get EAGAIN161ASSERT_EQ(len, -1);162ASSERT_EQ(errno, EAGAIN);163} else {164// Group 0 should get events165ASSERT_GT(len, 0);166}167}168169self->rem = len;170self->next = (void *) self->buf;171}172173meta = self->next;174ASSERT_TRUE(FAN_EVENT_OK(meta, self->rem));175176thislen = meta->event_len;177self->rem -= thislen;178self->next += thislen;179180*mask = meta->mask;181thislen -= sizeof(*meta);182183mnt = ((void *) meta) + meta->event_len - thislen;184185ASSERT_EQ(thislen, sizeof(*mnt));186187return mnt->mnt_id;188}189190static void expect_notify_n(struct __test_metadata *const _metadata,191FIXTURE_DATA(fanotify) *self,192unsigned int n, uint64_t mask[], uint64_t mnts[])193{194unsigned int i;195196for (i = 0; i < n; i++)197mnts[i] = expect_notify(_metadata, self, &mask[i]);198}199200static uint64_t expect_notify_mask(struct __test_metadata *const _metadata,201FIXTURE_DATA(fanotify) *self,202uint64_t expect_mask)203{204uint64_t mntid, mask;205206mntid = expect_notify(_metadata, self, &mask);207ASSERT_EQ(expect_mask, mask);208209return mntid;210}211212213static void expect_notify_mask_n(struct __test_metadata *const _metadata,214FIXTURE_DATA(fanotify) *self,215uint64_t mask, unsigned int n, uint64_t mnts[])216{217unsigned int i;218219for (i = 0; i < n; i++)220mnts[i] = expect_notify_mask(_metadata, self, mask);221}222223static void verify_mount_ids(struct __test_metadata *const _metadata,224const uint64_t list1[], const uint64_t list2[],225size_t num)226{227unsigned int i, j;228229// Check that neither list has any duplicates230for (i = 0; i < num; i++) {231for (j = 0; j < num; j++) {232if (i != j) {233ASSERT_NE(list1[i], list1[j]);234ASSERT_NE(list2[i], list2[j]);235}236}237}238// Check that all list1 memebers can be found in list2. Together with239// the above it means that the list1 and list2 represent the same sets.240for (i = 0; i < num; i++) {241for (j = 0; j < num; j++) {242if (list1[i] == list2[j])243break;244}245ASSERT_NE(j, num);246}247}248249static void check_mounted(struct __test_metadata *const _metadata,250const uint64_t mnts[], size_t num)251{252ssize_t ret;253uint64_t *list;254255list = malloc((num + 1) * sizeof(list[0]));256ASSERT_NE(list, NULL);257258ret = listmount(LSMT_ROOT, 0, 0, list, num + 1, 0);259ASSERT_EQ(ret, num);260261verify_mount_ids(_metadata, mnts, list, num);262263free(list);264}265266static void setup_mount_tree(struct __test_metadata *const _metadata,267int log2_num)268{269int ret, i;270271ret = mount("", "/", NULL, MS_SHARED, NULL);272ASSERT_EQ(ret, 0);273274for (i = 0; i < log2_num; i++) {275ret = mount("/", "/", NULL, MS_BIND, NULL);276ASSERT_EQ(ret, 0);277}278}279280TEST_F(fanotify, bind)281{282int ret;283uint64_t mnts[2] = { self->root_id };284285ret = mount("/", "/", NULL, MS_BIND, NULL);286ASSERT_EQ(ret, 0);287288mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);289ASSERT_NE(mnts[0], mnts[1]);290291check_mounted(_metadata, mnts, 2);292293// Cleanup294uint64_t detach_id;295ret = umount("/");296ASSERT_EQ(ret, 0);297298detach_id = expect_notify_mask(_metadata, self, FAN_MNT_DETACH);299ASSERT_EQ(detach_id, mnts[1]);300301check_mounted(_metadata, mnts, 1);302}303304TEST_F(fanotify, move)305{306int ret;307uint64_t mnts[2] = { self->root_id };308uint64_t move_id;309310ret = mount("/", "/a", NULL, MS_BIND, NULL);311ASSERT_EQ(ret, 0);312313mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);314ASSERT_NE(mnts[0], mnts[1]);315316check_mounted(_metadata, mnts, 2);317318ret = move_mount(AT_FDCWD, "/a", AT_FDCWD, "/b", 0);319ASSERT_EQ(ret, 0);320321move_id = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH | FAN_MNT_DETACH);322ASSERT_EQ(move_id, mnts[1]);323324// Cleanup325ret = umount("/b");326ASSERT_EQ(ret, 0);327328check_mounted(_metadata, mnts, 1);329}330331TEST_F(fanotify, propagate)332{333const unsigned int log2_num = 4;334const unsigned int num = (1 << log2_num);335uint64_t mnts[num];336337setup_mount_tree(_metadata, log2_num);338339expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, num - 1, mnts + 1);340341mnts[0] = self->root_id;342check_mounted(_metadata, mnts, num);343344// Cleanup345int ret;346uint64_t mnts2[num];347ret = umount2("/", MNT_DETACH);348ASSERT_EQ(ret, 0);349350ret = mount("", "/", NULL, MS_PRIVATE, NULL);351ASSERT_EQ(ret, 0);352353mnts2[0] = self->root_id;354expect_notify_mask_n(_metadata, self, FAN_MNT_DETACH, num - 1, mnts2 + 1);355verify_mount_ids(_metadata, mnts, mnts2, num);356357check_mounted(_metadata, mnts, 1);358}359360TEST_F(fanotify, fsmount)361{362int ret, fs, mnt;363uint64_t mnts[2] = { self->root_id };364365fs = fsopen("tmpfs", 0);366ASSERT_GE(fs, 0);367368ret = fsconfig(fs, FSCONFIG_CMD_CREATE, 0, 0, 0);369ASSERT_EQ(ret, 0);370371mnt = fsmount(fs, 0, 0);372ASSERT_GE(mnt, 0);373374close(fs);375376ret = move_mount(mnt, "", AT_FDCWD, "/a", MOVE_MOUNT_F_EMPTY_PATH);377ASSERT_EQ(ret, 0);378379close(mnt);380381mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);382ASSERT_NE(mnts[0], mnts[1]);383384check_mounted(_metadata, mnts, 2);385386// Cleanup387uint64_t detach_id;388ret = umount("/a");389ASSERT_EQ(ret, 0);390391detach_id = expect_notify_mask(_metadata, self, FAN_MNT_DETACH);392ASSERT_EQ(detach_id, mnts[1]);393394check_mounted(_metadata, mnts, 1);395}396397TEST_F(fanotify, reparent)398{399uint64_t mnts[6] = { self->root_id };400uint64_t dmnts[3];401uint64_t masks[3];402unsigned int i;403int ret;404405// Create setup with a[1] -> b[2] propagation406ret = mount("/", "/a", NULL, MS_BIND, NULL);407ASSERT_EQ(ret, 0);408409ret = mount("", "/a", NULL, MS_SHARED, NULL);410ASSERT_EQ(ret, 0);411412ret = mount("/a", "/b", NULL, MS_BIND, NULL);413ASSERT_EQ(ret, 0);414415ret = mount("", "/b", NULL, MS_SLAVE, NULL);416ASSERT_EQ(ret, 0);417418expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, 2, mnts + 1);419420check_mounted(_metadata, mnts, 3);421422// Mount on a[3], which is propagated to b[4]423ret = mount("/", "/a", NULL, MS_BIND, NULL);424ASSERT_EQ(ret, 0);425426expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, 2, mnts + 3);427428check_mounted(_metadata, mnts, 5);429430// Mount on b[5], not propagated431ret = mount("/", "/b", NULL, MS_BIND, NULL);432ASSERT_EQ(ret, 0);433434mnts[5] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);435436check_mounted(_metadata, mnts, 6);437438// Umount a[3], which is propagated to b[4], but not b[5]439// This will result in b[5] "falling" on b[2]440ret = umount("/a");441ASSERT_EQ(ret, 0);442443expect_notify_n(_metadata, self, 3, masks, dmnts);444verify_mount_ids(_metadata, mnts + 3, dmnts, 3);445446for (i = 0; i < 3; i++) {447if (dmnts[i] == mnts[5]) {448ASSERT_EQ(masks[i], FAN_MNT_ATTACH | FAN_MNT_DETACH);449} else {450ASSERT_EQ(masks[i], FAN_MNT_DETACH);451}452}453454mnts[3] = mnts[5];455check_mounted(_metadata, mnts, 4);456457// Cleanup458ret = umount("/b");459ASSERT_EQ(ret, 0);460461ret = umount("/a");462ASSERT_EQ(ret, 0);463464ret = umount("/b");465ASSERT_EQ(ret, 0);466467expect_notify_mask_n(_metadata, self, FAN_MNT_DETACH, 3, dmnts);468verify_mount_ids(_metadata, mnts + 1, dmnts, 3);469470check_mounted(_metadata, mnts, 1);471}472473TEST_F(fanotify, rmdir)474{475uint64_t mnts[3] = { self->root_id };476int ret;477478ret = mount("/", "/a", NULL, MS_BIND, NULL);479ASSERT_EQ(ret, 0);480481ret = mount("/", "/a/b", NULL, MS_BIND, NULL);482ASSERT_EQ(ret, 0);483484expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, 2, mnts + 1);485486check_mounted(_metadata, mnts, 3);487488ret = chdir("/a");489ASSERT_EQ(ret, 0);490491ret = fork();492ASSERT_GE(ret, 0);493494if (ret == 0) {495chdir("/");496unshare(CLONE_NEWNS);497mount("", "/", NULL, MS_REC|MS_PRIVATE, NULL);498umount2("/a", MNT_DETACH);499// This triggers a detach in the other namespace500rmdir("/a");501exit(0);502}503wait(NULL);504505expect_notify_mask_n(_metadata, self, FAN_MNT_DETACH, 2, mnts + 1);506check_mounted(_metadata, mnts, 1);507508// Cleanup509ret = chdir("/");510ASSERT_EQ(ret, 0);511}512513TEST_F(fanotify, pivot_root)514{515uint64_t mnts[3] = { self->root_id };516uint64_t mnts2[3];517int ret;518519ret = mount("tmpfs", "/a", "tmpfs", 0, NULL);520ASSERT_EQ(ret, 0);521522mnts[2] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);523524ret = mkdir("/a/new", 0700);525ASSERT_EQ(ret, 0);526527ret = mkdir("/a/old", 0700);528ASSERT_EQ(ret, 0);529530ret = mount("/a", "/a/new", NULL, MS_BIND, NULL);531ASSERT_EQ(ret, 0);532533mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);534check_mounted(_metadata, mnts, 3);535536ret = syscall(SYS_pivot_root, "/a/new", "/a/new/old");537ASSERT_EQ(ret, 0);538539expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH | FAN_MNT_DETACH, 2, mnts2);540verify_mount_ids(_metadata, mnts, mnts2, 2);541check_mounted(_metadata, mnts, 3);542543// Cleanup544ret = syscall(SYS_pivot_root, "/old", "/old/a/new");545ASSERT_EQ(ret, 0);546547ret = umount("/a/new");548ASSERT_EQ(ret, 0);549550ret = umount("/a");551ASSERT_EQ(ret, 0);552553check_mounted(_metadata, mnts, 1);554}555556TEST_HARNESS_MAIN557558559