Path: blob/master/tools/testing/selftests/filesystems/mount-notify/mount-notify_test_ns.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_types[] = {28FAN_MARK_FILESYSTEM,29FAN_MARK_MOUNT,30FAN_MARK_INODE31};3233static const int mark_cmds[] = {34FAN_MARK_ADD,35FAN_MARK_REMOVE,36FAN_MARK_FLUSH37};3839#define NUM_FAN_FDS ARRAY_SIZE(mark_cmds)4041FIXTURE(fanotify) {42int fan_fd[NUM_FAN_FDS];43char buf[256];44unsigned int rem;45void *next;46char root_mntpoint[sizeof(root_mntpoint_templ)];47int orig_root;48int orig_ns_fd;49int ns_fd;50uint64_t root_id;51};5253FIXTURE_SETUP(fanotify)54{55int i, ret;5657self->orig_ns_fd = open("/proc/self/ns/mnt", O_RDONLY);58ASSERT_GE(self->orig_ns_fd, 0);5960ret = setup_userns();61ASSERT_EQ(ret, 0);6263self->ns_fd = open("/proc/self/ns/mnt", O_RDONLY);64ASSERT_GE(self->ns_fd, 0);6566strcpy(self->root_mntpoint, root_mntpoint_templ);67ASSERT_NE(mkdtemp(self->root_mntpoint), NULL);6869self->orig_root = open("/", O_PATH | O_CLOEXEC);70ASSERT_GE(self->orig_root, 0);7172ASSERT_EQ(mount("tmpfs", self->root_mntpoint, "tmpfs", 0, NULL), 0);7374ASSERT_EQ(chroot(self->root_mntpoint), 0);7576ASSERT_EQ(chdir("/"), 0);7778ASSERT_EQ(mkdir("a", 0700), 0);7980ASSERT_EQ(mkdir("b", 0700), 0);8182self->root_id = get_unique_mnt_id("/");83ASSERT_NE(self->root_id, 0);8485for (i = 0; i < NUM_FAN_FDS; i++) {86int fan_fd = fanotify_init(FAN_REPORT_FID, 0);87// Verify that watching tmpfs mounted inside userns is allowed88ret = fanotify_mark(fan_fd, FAN_MARK_ADD | mark_types[i],89FAN_OPEN, AT_FDCWD, "/");90ASSERT_EQ(ret, 0);91// ...but watching entire orig root filesystem is not allowed92ret = fanotify_mark(fan_fd, FAN_MARK_ADD | FAN_MARK_FILESYSTEM,93FAN_OPEN, self->orig_root, ".");94ASSERT_NE(ret, 0);95close(fan_fd);9697self->fan_fd[i] = fanotify_init(FAN_REPORT_MNT | FAN_NONBLOCK,980);99ASSERT_GE(self->fan_fd[i], 0);100// Verify that watching mntns where group was created is allowed101ret = fanotify_mark(self->fan_fd[i], FAN_MARK_ADD |102FAN_MARK_MNTNS,103FAN_MNT_ATTACH | FAN_MNT_DETACH,104self->ns_fd, NULL);105ASSERT_EQ(ret, 0);106// ...but watching orig mntns is not allowed107ret = fanotify_mark(self->fan_fd[i], FAN_MARK_ADD |108FAN_MARK_MNTNS,109FAN_MNT_ATTACH | FAN_MNT_DETACH,110self->orig_ns_fd, NULL);111ASSERT_NE(ret, 0);112// On fd[0] we do an extra ADD that changes nothing.113// On fd[1]/fd[2] we REMOVE/FLUSH which removes the mark.114ret = fanotify_mark(self->fan_fd[i], mark_cmds[i] |115FAN_MARK_MNTNS,116FAN_MNT_ATTACH | FAN_MNT_DETACH,117self->ns_fd, NULL);118ASSERT_EQ(ret, 0);119}120121self->rem = 0;122}123124FIXTURE_TEARDOWN(fanotify)125{126int i;127128ASSERT_EQ(self->rem, 0);129for (i = 0; i < NUM_FAN_FDS; i++)130close(self->fan_fd[i]);131132ASSERT_EQ(fchdir(self->orig_root), 0);133134ASSERT_EQ(chroot("."), 0);135136EXPECT_EQ(umount2(self->root_mntpoint, MNT_DETACH), 0);137EXPECT_EQ(chdir(self->root_mntpoint), 0);138EXPECT_EQ(chdir("/"), 0);139EXPECT_EQ(rmdir(self->root_mntpoint), 0);140}141142static uint64_t expect_notify(struct __test_metadata *const _metadata,143FIXTURE_DATA(fanotify) *self,144uint64_t *mask)145{146struct fanotify_event_metadata *meta;147struct fanotify_event_info_mnt *mnt;148unsigned int thislen;149150if (!self->rem) {151ssize_t len;152int i;153154for (i = NUM_FAN_FDS - 1; i >= 0; i--) {155len = read(self->fan_fd[i], self->buf,156sizeof(self->buf));157if (i > 0) {158// Groups 1,2 should get EAGAIN159ASSERT_EQ(len, -1);160ASSERT_EQ(errno, EAGAIN);161} else {162// Group 0 should get events163ASSERT_GT(len, 0);164}165}166167self->rem = len;168self->next = (void *) self->buf;169}170171meta = self->next;172ASSERT_TRUE(FAN_EVENT_OK(meta, self->rem));173174thislen = meta->event_len;175self->rem -= thislen;176self->next += thislen;177178*mask = meta->mask;179thislen -= sizeof(*meta);180181mnt = ((void *) meta) + meta->event_len - thislen;182183ASSERT_EQ(thislen, sizeof(*mnt));184185return mnt->mnt_id;186}187188static void expect_notify_n(struct __test_metadata *const _metadata,189FIXTURE_DATA(fanotify) *self,190unsigned int n, uint64_t mask[], uint64_t mnts[])191{192unsigned int i;193194for (i = 0; i < n; i++)195mnts[i] = expect_notify(_metadata, self, &mask[i]);196}197198static uint64_t expect_notify_mask(struct __test_metadata *const _metadata,199FIXTURE_DATA(fanotify) *self,200uint64_t expect_mask)201{202uint64_t mntid, mask;203204mntid = expect_notify(_metadata, self, &mask);205ASSERT_EQ(expect_mask, mask);206207return mntid;208}209210211static void expect_notify_mask_n(struct __test_metadata *const _metadata,212FIXTURE_DATA(fanotify) *self,213uint64_t mask, unsigned int n, uint64_t mnts[])214{215unsigned int i;216217for (i = 0; i < n; i++)218mnts[i] = expect_notify_mask(_metadata, self, mask);219}220221static void verify_mount_ids(struct __test_metadata *const _metadata,222const uint64_t list1[], const uint64_t list2[],223size_t num)224{225unsigned int i, j;226227// Check that neither list has any duplicates228for (i = 0; i < num; i++) {229for (j = 0; j < num; j++) {230if (i != j) {231ASSERT_NE(list1[i], list1[j]);232ASSERT_NE(list2[i], list2[j]);233}234}235}236// Check that all list1 memebers can be found in list2. Together with237// the above it means that the list1 and list2 represent the same sets.238for (i = 0; i < num; i++) {239for (j = 0; j < num; j++) {240if (list1[i] == list2[j])241break;242}243ASSERT_NE(j, num);244}245}246247static void check_mounted(struct __test_metadata *const _metadata,248const uint64_t mnts[], size_t num)249{250ssize_t ret;251uint64_t *list;252253list = malloc((num + 1) * sizeof(list[0]));254ASSERT_NE(list, NULL);255256ret = listmount(LSMT_ROOT, 0, 0, list, num + 1, 0);257ASSERT_EQ(ret, num);258259verify_mount_ids(_metadata, mnts, list, num);260261free(list);262}263264static void setup_mount_tree(struct __test_metadata *const _metadata,265int log2_num)266{267int ret, i;268269ret = mount("", "/", NULL, MS_SHARED, NULL);270ASSERT_EQ(ret, 0);271272for (i = 0; i < log2_num; i++) {273ret = mount("/", "/", NULL, MS_BIND, NULL);274ASSERT_EQ(ret, 0);275}276}277278TEST_F(fanotify, bind)279{280int ret;281uint64_t mnts[2] = { self->root_id };282283ret = mount("/", "/", NULL, MS_BIND, NULL);284ASSERT_EQ(ret, 0);285286mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);287ASSERT_NE(mnts[0], mnts[1]);288289check_mounted(_metadata, mnts, 2);290291// Cleanup292uint64_t detach_id;293ret = umount("/");294ASSERT_EQ(ret, 0);295296detach_id = expect_notify_mask(_metadata, self, FAN_MNT_DETACH);297ASSERT_EQ(detach_id, mnts[1]);298299check_mounted(_metadata, mnts, 1);300}301302TEST_F(fanotify, move)303{304int ret;305uint64_t mnts[2] = { self->root_id };306uint64_t move_id;307308ret = mount("/", "/a", NULL, MS_BIND, NULL);309ASSERT_EQ(ret, 0);310311mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);312ASSERT_NE(mnts[0], mnts[1]);313314check_mounted(_metadata, mnts, 2);315316ret = move_mount(AT_FDCWD, "/a", AT_FDCWD, "/b", 0);317ASSERT_EQ(ret, 0);318319move_id = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH | FAN_MNT_DETACH);320ASSERT_EQ(move_id, mnts[1]);321322// Cleanup323ret = umount("/b");324ASSERT_EQ(ret, 0);325326check_mounted(_metadata, mnts, 1);327}328329TEST_F(fanotify, propagate)330{331const unsigned int log2_num = 4;332const unsigned int num = (1 << log2_num);333uint64_t mnts[num];334335setup_mount_tree(_metadata, log2_num);336337expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, num - 1, mnts + 1);338339mnts[0] = self->root_id;340check_mounted(_metadata, mnts, num);341342// Cleanup343int ret;344uint64_t mnts2[num];345ret = umount2("/", MNT_DETACH);346ASSERT_EQ(ret, 0);347348ret = mount("", "/", NULL, MS_PRIVATE, NULL);349ASSERT_EQ(ret, 0);350351mnts2[0] = self->root_id;352expect_notify_mask_n(_metadata, self, FAN_MNT_DETACH, num - 1, mnts2 + 1);353verify_mount_ids(_metadata, mnts, mnts2, num);354355check_mounted(_metadata, mnts, 1);356}357358TEST_F(fanotify, fsmount)359{360int ret, fs, mnt;361uint64_t mnts[2] = { self->root_id };362363fs = fsopen("tmpfs", 0);364ASSERT_GE(fs, 0);365366ret = fsconfig(fs, FSCONFIG_CMD_CREATE, 0, 0, 0);367ASSERT_EQ(ret, 0);368369mnt = fsmount(fs, 0, 0);370ASSERT_GE(mnt, 0);371372close(fs);373374ret = move_mount(mnt, "", AT_FDCWD, "/a", MOVE_MOUNT_F_EMPTY_PATH);375ASSERT_EQ(ret, 0);376377close(mnt);378379mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);380ASSERT_NE(mnts[0], mnts[1]);381382check_mounted(_metadata, mnts, 2);383384// Cleanup385uint64_t detach_id;386ret = umount("/a");387ASSERT_EQ(ret, 0);388389detach_id = expect_notify_mask(_metadata, self, FAN_MNT_DETACH);390ASSERT_EQ(detach_id, mnts[1]);391392check_mounted(_metadata, mnts, 1);393}394395TEST_F(fanotify, reparent)396{397uint64_t mnts[6] = { self->root_id };398uint64_t dmnts[3];399uint64_t masks[3];400unsigned int i;401int ret;402403// Create setup with a[1] -> b[2] propagation404ret = mount("/", "/a", NULL, MS_BIND, NULL);405ASSERT_EQ(ret, 0);406407ret = mount("", "/a", NULL, MS_SHARED, NULL);408ASSERT_EQ(ret, 0);409410ret = mount("/a", "/b", NULL, MS_BIND, NULL);411ASSERT_EQ(ret, 0);412413ret = mount("", "/b", NULL, MS_SLAVE, NULL);414ASSERT_EQ(ret, 0);415416expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, 2, mnts + 1);417418check_mounted(_metadata, mnts, 3);419420// Mount on a[3], which is propagated to b[4]421ret = mount("/", "/a", NULL, MS_BIND, NULL);422ASSERT_EQ(ret, 0);423424expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, 2, mnts + 3);425426check_mounted(_metadata, mnts, 5);427428// Mount on b[5], not propagated429ret = mount("/", "/b", NULL, MS_BIND, NULL);430ASSERT_EQ(ret, 0);431432mnts[5] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);433434check_mounted(_metadata, mnts, 6);435436// Umount a[3], which is propagated to b[4], but not b[5]437// This will result in b[5] "falling" on b[2]438ret = umount("/a");439ASSERT_EQ(ret, 0);440441expect_notify_n(_metadata, self, 3, masks, dmnts);442verify_mount_ids(_metadata, mnts + 3, dmnts, 3);443444for (i = 0; i < 3; i++) {445if (dmnts[i] == mnts[5]) {446ASSERT_EQ(masks[i], FAN_MNT_ATTACH | FAN_MNT_DETACH);447} else {448ASSERT_EQ(masks[i], FAN_MNT_DETACH);449}450}451452mnts[3] = mnts[5];453check_mounted(_metadata, mnts, 4);454455// Cleanup456ret = umount("/b");457ASSERT_EQ(ret, 0);458459ret = umount("/a");460ASSERT_EQ(ret, 0);461462ret = umount("/b");463ASSERT_EQ(ret, 0);464465expect_notify_mask_n(_metadata, self, FAN_MNT_DETACH, 3, dmnts);466verify_mount_ids(_metadata, mnts + 1, dmnts, 3);467468check_mounted(_metadata, mnts, 1);469}470471TEST_F(fanotify, rmdir)472{473uint64_t mnts[3] = { self->root_id };474int ret;475476ret = mount("/", "/a", NULL, MS_BIND, NULL);477ASSERT_EQ(ret, 0);478479ret = mount("/", "/a/b", NULL, MS_BIND, NULL);480ASSERT_EQ(ret, 0);481482expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH, 2, mnts + 1);483484check_mounted(_metadata, mnts, 3);485486ret = chdir("/a");487ASSERT_EQ(ret, 0);488489ret = fork();490ASSERT_GE(ret, 0);491492if (ret == 0) {493chdir("/");494unshare(CLONE_NEWNS);495mount("", "/", NULL, MS_REC|MS_PRIVATE, NULL);496umount2("/a", MNT_DETACH);497// This triggers a detach in the other namespace498rmdir("/a");499exit(0);500}501wait(NULL);502503expect_notify_mask_n(_metadata, self, FAN_MNT_DETACH, 2, mnts + 1);504check_mounted(_metadata, mnts, 1);505506// Cleanup507ret = chdir("/");508ASSERT_EQ(ret, 0);509}510511TEST_F(fanotify, pivot_root)512{513uint64_t mnts[3] = { self->root_id };514uint64_t mnts2[3];515int ret;516517ret = mount("tmpfs", "/a", "tmpfs", 0, NULL);518ASSERT_EQ(ret, 0);519520mnts[2] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);521522ret = mkdir("/a/new", 0700);523ASSERT_EQ(ret, 0);524525ret = mkdir("/a/old", 0700);526ASSERT_EQ(ret, 0);527528ret = mount("/a", "/a/new", NULL, MS_BIND, NULL);529ASSERT_EQ(ret, 0);530531mnts[1] = expect_notify_mask(_metadata, self, FAN_MNT_ATTACH);532check_mounted(_metadata, mnts, 3);533534ret = syscall(SYS_pivot_root, "/a/new", "/a/new/old");535ASSERT_EQ(ret, 0);536537expect_notify_mask_n(_metadata, self, FAN_MNT_ATTACH | FAN_MNT_DETACH, 2, mnts2);538verify_mount_ids(_metadata, mnts, mnts2, 2);539check_mounted(_metadata, mnts, 3);540541// Cleanup542ret = syscall(SYS_pivot_root, "/old", "/old/a/new");543ASSERT_EQ(ret, 0);544545ret = umount("/a/new");546ASSERT_EQ(ret, 0);547548ret = umount("/a");549ASSERT_EQ(ret, 0);550551check_mounted(_metadata, mnts, 1);552}553554TEST_HARNESS_MAIN555556557