1*520b5a15SChristian Brauner // SPDX-License-Identifier: GPL-2.0
2*520b5a15SChristian Brauner /*
3*520b5a15SChristian Brauner * A synchronous umount first unmounts the shrinkable submounts of the
4*520b5a15SChristian Brauner * victim that aren't busy. Every one of them has to be checked right
5*520b5a15SChristian Brauner * before it is unmounted: unmounting one can slide a busy mount to where
6*520b5a15SChristian Brauner * the propagated copy of the next one is looked up.
7*520b5a15SChristian Brauner */
8*520b5a15SChristian Brauner #define _GNU_SOURCE
9*520b5a15SChristian Brauner #include <errno.h>
10*520b5a15SChristian Brauner #include <fcntl.h>
11*520b5a15SChristian Brauner #include <sched.h>
12*520b5a15SChristian Brauner #include <stdio.h>
13*520b5a15SChristian Brauner #include <stdlib.h>
14*520b5a15SChristian Brauner #include <string.h>
15*520b5a15SChristian Brauner #include <unistd.h>
16*520b5a15SChristian Brauner #include <sys/fanotify.h>
17*520b5a15SChristian Brauner #include <sys/mount.h>
18*520b5a15SChristian Brauner #include <sys/stat.h>
19*520b5a15SChristian Brauner #include <sys/statfs.h>
20*520b5a15SChristian Brauner #include <sys/vfs.h>
21*520b5a15SChristian Brauner #include <linux/magic.h>
22*520b5a15SChristian Brauner
23*520b5a15SChristian Brauner #include "../../kselftest_harness.h"
24*520b5a15SChristian Brauner
25*520b5a15SChristian Brauner #ifndef FAN_REPORT_MNT
26*520b5a15SChristian Brauner #define FAN_REPORT_MNT 0x00004000
27*520b5a15SChristian Brauner #endif
28*520b5a15SChristian Brauner #ifndef FAN_MARK_MNTNS
29*520b5a15SChristian Brauner #define FAN_MARK_MNTNS 0x00000110
30*520b5a15SChristian Brauner #endif
31*520b5a15SChristian Brauner #ifndef FAN_MNT_ATTACH
32*520b5a15SChristian Brauner #define FAN_MNT_ATTACH 0x01000000
33*520b5a15SChristian Brauner #endif
34*520b5a15SChristian Brauner #ifndef FAN_MNT_DETACH
35*520b5a15SChristian Brauner #define FAN_MNT_DETACH 0x02000000
36*520b5a15SChristian Brauner #endif
37*520b5a15SChristian Brauner
38*520b5a15SChristian Brauner #define DIR_LEN 64
39*520b5a15SChristian Brauner #define PATH_LEN 128
40*520b5a15SChristian Brauner
FIXTURE(shrink_submounts)41*520b5a15SChristian Brauner FIXTURE(shrink_submounts) {
42*520b5a15SChristian Brauner char base[DIR_LEN];
43*520b5a15SChristian Brauner char automount[PATH_LEN];
44*520b5a15SChristian Brauner bool mounted;
45*520b5a15SChristian Brauner int fan;
46*520b5a15SChristian Brauner };
47*520b5a15SChristian Brauner
48*520b5a15SChristian Brauner /*
49*520b5a15SChristian Brauner * Shrinkable mounts come from an automount. The tracefs mount below debugfs
50*520b5a15SChristian Brauner * is one and bind mounts inherit the flag.
51*520b5a15SChristian Brauner */
FIXTURE_SETUP(shrink_submounts)52*520b5a15SChristian Brauner FIXTURE_SETUP(shrink_submounts)
53*520b5a15SChristian Brauner {
54*520b5a15SChristian Brauner struct stat st;
55*520b5a15SChristian Brauner char p[PATH_LEN];
56*520b5a15SChristian Brauner
57*520b5a15SChristian Brauner self->mounted = false;
58*520b5a15SChristian Brauner self->fan = -1;
59*520b5a15SChristian Brauner
60*520b5a15SChristian Brauner if (geteuid() != 0)
61*520b5a15SChristian Brauner SKIP(return, "test requires CAP_SYS_ADMIN");
62*520b5a15SChristian Brauner
63*520b5a15SChristian Brauner ASSERT_EQ(unshare(CLONE_NEWNS), 0);
64*520b5a15SChristian Brauner ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0);
65*520b5a15SChristian Brauner
66*520b5a15SChristian Brauner snprintf(self->base, sizeof(self->base), "/tmp/shrink_submounts.XXXXXX");
67*520b5a15SChristian Brauner ASSERT_NE(mkdtemp(self->base), NULL);
68*520b5a15SChristian Brauner ASSERT_EQ(mount("tmpfs", self->base, "tmpfs", 0, NULL), 0);
69*520b5a15SChristian Brauner self->mounted = true;
70*520b5a15SChristian Brauner ASSERT_EQ(mount(NULL, self->base, NULL, MS_PRIVATE, NULL), 0);
71*520b5a15SChristian Brauner
72*520b5a15SChristian Brauner snprintf(p, sizeof(p), "%s/dbg", self->base);
73*520b5a15SChristian Brauner ASSERT_EQ(mkdir(p, 0755), 0);
74*520b5a15SChristian Brauner if (mount("debugfs", p, "debugfs", 0, NULL)) {
75*520b5a15SChristian Brauner umount2(self->base, MNT_DETACH);
76*520b5a15SChristian Brauner rmdir(self->base);
77*520b5a15SChristian Brauner SKIP(return, "test requires debugfs");
78*520b5a15SChristian Brauner }
79*520b5a15SChristian Brauner snprintf(self->automount, sizeof(self->automount), "%s/dbg/tracing",
80*520b5a15SChristian Brauner self->base);
81*520b5a15SChristian Brauner snprintf(p, sizeof(p), "%s/dbg/tracing/.", self->base);
82*520b5a15SChristian Brauner if (stat(p, &st)) {
83*520b5a15SChristian Brauner umount2(self->base, MNT_DETACH);
84*520b5a15SChristian Brauner rmdir(self->base);
85*520b5a15SChristian Brauner SKIP(return, "test requires the tracefs automount");
86*520b5a15SChristian Brauner }
87*520b5a15SChristian Brauner }
88*520b5a15SChristian Brauner
FIXTURE_TEARDOWN(shrink_submounts)89*520b5a15SChristian Brauner FIXTURE_TEARDOWN(shrink_submounts)
90*520b5a15SChristian Brauner {
91*520b5a15SChristian Brauner if (self->fan >= 0)
92*520b5a15SChristian Brauner close(self->fan);
93*520b5a15SChristian Brauner chdir("/");
94*520b5a15SChristian Brauner if (self->mounted)
95*520b5a15SChristian Brauner umount2(self->base, MNT_DETACH);
96*520b5a15SChristian Brauner rmdir(self->base);
97*520b5a15SChristian Brauner }
98*520b5a15SChristian Brauner
mounted_tmpfs(const char * path)99*520b5a15SChristian Brauner static bool mounted_tmpfs(const char *path)
100*520b5a15SChristian Brauner {
101*520b5a15SChristian Brauner struct statfs st;
102*520b5a15SChristian Brauner
103*520b5a15SChristian Brauner return !statfs(path, &st) && st.f_type == TMPFS_MAGIC;
104*520b5a15SChristian Brauner }
105*520b5a15SChristian Brauner
106*520b5a15SChristian Brauner /*
107*520b5a15SChristian Brauner * P is a shared bind mount of the automount, P1 a slave of P that is shared
108*520b5a15SChristian Brauner * in turn and P2 its peer. B, another bind mount of the automount, goes on
109*520b5a15SChristian Brauner * P/options and propagates copies Bc1 and Bc2 onto P1/options and
110*520b5a15SChristian Brauner * P2/options. R, a tmpfs and our working directory, sits on top of Bc2 which
111*520b5a15SChristian Brauner * is made private first. P, with B on it, is moved to P1/instances.
112*520b5a15SChristian Brauner *
113*520b5a15SChristian Brauner * A synchronous umount of P1 unmounts the shrinkable submounts Bc1 and B
114*520b5a15SChristian Brauner * first. Unmounting Bc1 takes Bc2 along and slides R to P2/options where
115*520b5a15SChristian Brauner * the propagated copy of B is looked up next. R is busy, so B has to stay
116*520b5a15SChristian Brauner * and the umount fails with EBUSY.
117*520b5a15SChristian Brauner */
TEST_F(shrink_submounts,busy_mount_moved_into_reach)118*520b5a15SChristian Brauner TEST_F(shrink_submounts, busy_mount_moved_into_reach)
119*520b5a15SChristian Brauner {
120*520b5a15SChristian Brauner char p[PATH_LEN], p1[PATH_LEN], p2[PATH_LEN], r[PATH_LEN], cwd[PATH_LEN];
121*520b5a15SChristian Brauner int nsfd;
122*520b5a15SChristian Brauner
123*520b5a15SChristian Brauner snprintf(p, sizeof(p), "%s/p", self->base);
124*520b5a15SChristian Brauner snprintf(p1, sizeof(p1), "%s/p1", self->base);
125*520b5a15SChristian Brauner snprintf(p2, sizeof(p2), "%s/p2", self->base);
126*520b5a15SChristian Brauner ASSERT_EQ(mkdir(p, 0755), 0);
127*520b5a15SChristian Brauner ASSERT_EQ(mkdir(p1, 0755), 0);
128*520b5a15SChristian Brauner ASSERT_EQ(mkdir(p2, 0755), 0);
129*520b5a15SChristian Brauner
130*520b5a15SChristian Brauner /* watch the mount namespace so that the detached mounts get queued */
131*520b5a15SChristian Brauner self->fan = fanotify_init(FAN_REPORT_MNT, O_RDONLY);
132*520b5a15SChristian Brauner if (self->fan >= 0) {
133*520b5a15SChristian Brauner nsfd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC);
134*520b5a15SChristian Brauner ASSERT_GE(nsfd, 0);
135*520b5a15SChristian Brauner EXPECT_EQ(fanotify_mark(self->fan, FAN_MARK_ADD | FAN_MARK_MNTNS,
136*520b5a15SChristian Brauner FAN_MNT_ATTACH | FAN_MNT_DETACH, nsfd, NULL), 0);
137*520b5a15SChristian Brauner close(nsfd);
138*520b5a15SChristian Brauner }
139*520b5a15SChristian Brauner
140*520b5a15SChristian Brauner ASSERT_EQ(mount(self->automount, p, NULL, MS_BIND, NULL), 0);
141*520b5a15SChristian Brauner ASSERT_EQ(mount(NULL, p, NULL, MS_SHARED, NULL), 0);
142*520b5a15SChristian Brauner ASSERT_EQ(mount(p, p1, NULL, MS_BIND, NULL), 0);
143*520b5a15SChristian Brauner ASSERT_EQ(mount(NULL, p1, NULL, MS_SLAVE, NULL), 0);
144*520b5a15SChristian Brauner ASSERT_EQ(mount(NULL, p1, NULL, MS_SHARED, NULL), 0);
145*520b5a15SChristian Brauner ASSERT_EQ(mount(p1, p2, NULL, MS_BIND, NULL), 0);
146*520b5a15SChristian Brauner
147*520b5a15SChristian Brauner /* B on P/options, copies on P1/options and P2/options */
148*520b5a15SChristian Brauner snprintf(r, sizeof(r), "%s/p/options", self->base);
149*520b5a15SChristian Brauner ASSERT_EQ(mount(self->automount, r, NULL, MS_BIND, NULL), 0);
150*520b5a15SChristian Brauner
151*520b5a15SChristian Brauner /* R on top of Bc2 */
152*520b5a15SChristian Brauner snprintf(r, sizeof(r), "%s/p2/options", self->base);
153*520b5a15SChristian Brauner ASSERT_EQ(mount(NULL, r, NULL, MS_PRIVATE, NULL), 0);
154*520b5a15SChristian Brauner ASSERT_EQ(mount("R", r, "tmpfs", 0, NULL), 0);
155*520b5a15SChristian Brauner ASSERT_EQ(chdir(r), 0);
156*520b5a15SChristian Brauner
157*520b5a15SChristian Brauner /* P, with B on it, below the victim */
158*520b5a15SChristian Brauner snprintf(cwd, sizeof(cwd), "%s/p1/instances", self->base);
159*520b5a15SChristian Brauner ASSERT_EQ(mount(p, cwd, NULL, MS_MOVE, NULL), 0);
160*520b5a15SChristian Brauner
161*520b5a15SChristian Brauner ASSERT_TRUE(mounted_tmpfs(r));
162*520b5a15SChristian Brauner ASSERT_EQ(umount2(p1, 0), -1);
163*520b5a15SChristian Brauner EXPECT_EQ(errno, EBUSY);
164*520b5a15SChristian Brauner
165*520b5a15SChristian Brauner /* R is still mounted and still our working directory */
166*520b5a15SChristian Brauner EXPECT_TRUE(mounted_tmpfs(r));
167*520b5a15SChristian Brauner ASSERT_NE(getcwd(cwd, sizeof(cwd)), NULL);
168*520b5a15SChristian Brauner EXPECT_STREQ(cwd, r);
169*520b5a15SChristian Brauner }
170*520b5a15SChristian Brauner
171*520b5a15SChristian Brauner /*
172*520b5a15SChristian Brauner * T is shared and Q, a slave of T, has T moved into it, so Q receives
173*520b5a15SChristian Brauner * propagation from its own child. M, another bind mount of the automount, is
174*520b5a15SChristian Brauner * on T/options and that is the dentry T sits on in Q. Unmounting M makes T
175*520b5a15SChristian Brauner * the propagated victim at that dentry in Q and takes T along. The shrink
176*520b5a15SChristian Brauner * walk of V has just unmounted M and continues in the children of T.
177*520b5a15SChristian Brauner */
TEST_F(shrink_submounts,parent_goes_with_child)178*520b5a15SChristian Brauner TEST_F(shrink_submounts, parent_goes_with_child)
179*520b5a15SChristian Brauner {
180*520b5a15SChristian Brauner char v[PATH_LEN], t[PATH_LEN], q[PATH_LEN], p[PATH_LEN];
181*520b5a15SChristian Brauner struct stat before, after;
182*520b5a15SChristian Brauner
183*520b5a15SChristian Brauner snprintf(v, sizeof(v), "%s/v", self->base);
184*520b5a15SChristian Brauner snprintf(t, sizeof(t), "%s/v/t", self->base);
185*520b5a15SChristian Brauner snprintf(q, sizeof(q), "%s/v/q", self->base);
186*520b5a15SChristian Brauner ASSERT_EQ(mkdir(v, 0755), 0);
187*520b5a15SChristian Brauner ASSERT_EQ(mount("V", v, "tmpfs", 0, NULL), 0);
188*520b5a15SChristian Brauner ASSERT_EQ(mount(NULL, v, NULL, MS_PRIVATE, NULL), 0);
189*520b5a15SChristian Brauner ASSERT_EQ(stat(v, &before), 0);
190*520b5a15SChristian Brauner ASSERT_EQ(mkdir(t, 0755), 0);
191*520b5a15SChristian Brauner ASSERT_EQ(mkdir(q, 0755), 0);
192*520b5a15SChristian Brauner
193*520b5a15SChristian Brauner /* T shared, M on T/options before anything receives from T */
194*520b5a15SChristian Brauner ASSERT_EQ(mount(self->automount, t, NULL, MS_BIND, NULL), 0);
195*520b5a15SChristian Brauner ASSERT_EQ(mount(NULL, t, NULL, MS_SHARED, NULL), 0);
196*520b5a15SChristian Brauner snprintf(p, sizeof(p), "%s/v/t/options", self->base);
197*520b5a15SChristian Brauner ASSERT_EQ(mount(self->automount, p, NULL, MS_BIND, NULL), 0);
198*520b5a15SChristian Brauner
199*520b5a15SChristian Brauner /* Q, a slave of T, and T moved into Q at the dentry M sits on */
200*520b5a15SChristian Brauner ASSERT_EQ(mount(t, q, NULL, MS_BIND, NULL), 0);
201*520b5a15SChristian Brauner ASSERT_EQ(mount(NULL, q, NULL, MS_SLAVE, NULL), 0);
202*520b5a15SChristian Brauner snprintf(p, sizeof(p), "%s/v/q/options", self->base);
203*520b5a15SChristian Brauner ASSERT_EQ(mount(t, p, NULL, MS_MOVE, NULL), 0);
204*520b5a15SChristian Brauner
205*520b5a15SChristian Brauner /* M, T and then Q go, V is empty and can be unmounted */
206*520b5a15SChristian Brauner ASSERT_EQ(umount2(v, 0), 0);
207*520b5a15SChristian Brauner ASSERT_EQ(stat(v, &after), 0);
208*520b5a15SChristian Brauner EXPECT_NE(before.st_dev, after.st_dev);
209*520b5a15SChristian Brauner }
210*520b5a15SChristian Brauner
211*520b5a15SChristian Brauner TEST_HARNESS_MAIN
212