Skip to content

Commit 8374f43

Browse files
SnorchChristian Brauner
authored andcommitted
tests: add move_mount(MOVE_MOUNT_SET_GROUP) selftest
Add a simple selftest for a move_mount(MOVE_MOUNT_SET_GROUP). This tests that one can copy sharing from one mount from nested mntns with nested userns owner to another mount from other nested mntns with other nested userns owner while in their parent userns. TAP version 13 1..1 # Starting 1 tests from 2 test cases. # RUN move_mount_set_group.complex_sharing_copying ... # OK move_mount_set_group.complex_sharing_copying ok 1 move_mount_set_group.complex_sharing_copying # PASSED: 1 / 1 tests passed. # Totals: pass:1 fail:0 xfail:0 xpass:0 skip:0 error:0 Link: https://lore.kernel.org/r/[email protected] Cc: Shuah Khan <[email protected]> Cc: Eric W. Biederman <[email protected]> Cc: Alexander Viro <[email protected]> Cc: Christian Brauner <[email protected]> Cc: Mattias Nissler <[email protected]> Cc: Aleksa Sarai <[email protected]> Cc: Andrei Vagin <[email protected]> Cc: [email protected] Cc: [email protected] Cc: lkml <[email protected]> Signed-off-by: Pavel Tikhomirov <[email protected]> Signed-off-by: Christian Brauner <[email protected]>
1 parent 9ffb14e commit 8374f43

File tree

5 files changed

+385
-0
lines changed

5 files changed

+385
-0
lines changed

tools/testing/selftests/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ TARGETS += memory-hotplug
3535
TARGETS += mincore
3636
TARGETS += mount
3737
TARGETS += mount_setattr
38+
TARGETS += move_mount_set_group
3839
TARGETS += mqueue
3940
TARGETS += nci
4041
TARGETS += net
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
move_mount_set_group_test
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# SPDX-License-Identifier: GPL-2.0
2+
# Makefile for mount selftests.
3+
CFLAGS = -g -I../../../../usr/include/ -Wall -O2
4+
5+
TEST_GEN_FILES += move_mount_set_group_test
6+
7+
include ../lib.mk
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
CONFIG_USER_NS=y
Lines changed: 375 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,375 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
#define _GNU_SOURCE
3+
#include <sched.h>
4+
#include <stdio.h>
5+
#include <errno.h>
6+
#include <string.h>
7+
#include <sys/stat.h>
8+
#include <sys/types.h>
9+
#include <sys/mount.h>
10+
#include <sys/wait.h>
11+
#include <stdlib.h>
12+
#include <unistd.h>
13+
#include <fcntl.h>
14+
#include <stdbool.h>
15+
#include <stdarg.h>
16+
#include <sys/syscall.h>
17+
18+
#include "../kselftest_harness.h"
19+
20+
#ifndef CLONE_NEWNS
21+
#define CLONE_NEWNS 0x00020000
22+
#endif
23+
24+
#ifndef CLONE_NEWUSER
25+
#define CLONE_NEWUSER 0x10000000
26+
#endif
27+
28+
#ifndef MS_SHARED
29+
#define MS_SHARED (1 << 20)
30+
#endif
31+
32+
#ifndef MS_PRIVATE
33+
#define MS_PRIVATE (1<<18)
34+
#endif
35+
36+
#ifndef MOVE_MOUNT_SET_GROUP
37+
#define MOVE_MOUNT_SET_GROUP 0x00000100
38+
#endif
39+
40+
#ifndef MOVE_MOUNT_F_EMPTY_PATH
41+
#define MOVE_MOUNT_F_EMPTY_PATH 0x00000004
42+
#endif
43+
44+
#ifndef MOVE_MOUNT_T_EMPTY_PATH
45+
#define MOVE_MOUNT_T_EMPTY_PATH 0x00000040
46+
#endif
47+
48+
static ssize_t write_nointr(int fd, const void *buf, size_t count)
49+
{
50+
ssize_t ret;
51+
52+
do {
53+
ret = write(fd, buf, count);
54+
} while (ret < 0 && errno == EINTR);
55+
56+
return ret;
57+
}
58+
59+
static int write_file(const char *path, const void *buf, size_t count)
60+
{
61+
int fd;
62+
ssize_t ret;
63+
64+
fd = open(path, O_WRONLY | O_CLOEXEC | O_NOCTTY | O_NOFOLLOW);
65+
if (fd < 0)
66+
return -1;
67+
68+
ret = write_nointr(fd, buf, count);
69+
close(fd);
70+
if (ret < 0 || (size_t)ret != count)
71+
return -1;
72+
73+
return 0;
74+
}
75+
76+
static int create_and_enter_userns(void)
77+
{
78+
uid_t uid;
79+
gid_t gid;
80+
char map[100];
81+
82+
uid = getuid();
83+
gid = getgid();
84+
85+
if (unshare(CLONE_NEWUSER))
86+
return -1;
87+
88+
if (write_file("/proc/self/setgroups", "deny", sizeof("deny") - 1) &&
89+
errno != ENOENT)
90+
return -1;
91+
92+
snprintf(map, sizeof(map), "0 %d 1", uid);
93+
if (write_file("/proc/self/uid_map", map, strlen(map)))
94+
return -1;
95+
96+
97+
snprintf(map, sizeof(map), "0 %d 1", gid);
98+
if (write_file("/proc/self/gid_map", map, strlen(map)))
99+
return -1;
100+
101+
if (setgid(0))
102+
return -1;
103+
104+
if (setuid(0))
105+
return -1;
106+
107+
return 0;
108+
}
109+
110+
static int prepare_unpriv_mountns(void)
111+
{
112+
if (create_and_enter_userns())
113+
return -1;
114+
115+
if (unshare(CLONE_NEWNS))
116+
return -1;
117+
118+
if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, 0))
119+
return -1;
120+
121+
return 0;
122+
}
123+
124+
static char *get_field(char *src, int nfields)
125+
{
126+
int i;
127+
char *p = src;
128+
129+
for (i = 0; i < nfields; i++) {
130+
while (*p && *p != ' ' && *p != '\t')
131+
p++;
132+
133+
if (!*p)
134+
break;
135+
136+
p++;
137+
}
138+
139+
return p;
140+
}
141+
142+
static void null_endofword(char *word)
143+
{
144+
while (*word && *word != ' ' && *word != '\t')
145+
word++;
146+
*word = '\0';
147+
}
148+
149+
static bool is_shared_mount(const char *path)
150+
{
151+
size_t len = 0;
152+
char *line = NULL;
153+
FILE *f = NULL;
154+
155+
f = fopen("/proc/self/mountinfo", "re");
156+
if (!f)
157+
return false;
158+
159+
while (getline(&line, &len, f) != -1) {
160+
char *opts, *target;
161+
162+
target = get_field(line, 4);
163+
if (!target)
164+
continue;
165+
166+
opts = get_field(target, 2);
167+
if (!opts)
168+
continue;
169+
170+
null_endofword(target);
171+
172+
if (strcmp(target, path) != 0)
173+
continue;
174+
175+
null_endofword(opts);
176+
if (strstr(opts, "shared:"))
177+
return true;
178+
}
179+
180+
free(line);
181+
fclose(f);
182+
183+
return false;
184+
}
185+
186+
/* Attempt to de-conflict with the selftests tree. */
187+
#ifndef SKIP
188+
#define SKIP(s, ...) XFAIL(s, ##__VA_ARGS__)
189+
#endif
190+
191+
#define SET_GROUP_FROM "/tmp/move_mount_set_group_supported_from"
192+
#define SET_GROUP_TO "/tmp/move_mount_set_group_supported_to"
193+
194+
static int move_mount_set_group_supported(void)
195+
{
196+
int ret;
197+
198+
if (mount("testing", "/tmp", "tmpfs", MS_NOATIME | MS_NODEV,
199+
"size=100000,mode=700"))
200+
return -1;
201+
202+
if (mount(NULL, "/tmp", NULL, MS_PRIVATE, 0))
203+
return -1;
204+
205+
if (mkdir(SET_GROUP_FROM, 0777))
206+
return -1;
207+
208+
if (mkdir(SET_GROUP_TO, 0777))
209+
return -1;
210+
211+
if (mount("testing", SET_GROUP_FROM, "tmpfs", MS_NOATIME | MS_NODEV,
212+
"size=100000,mode=700"))
213+
return -1;
214+
215+
if (mount(SET_GROUP_FROM, SET_GROUP_TO, NULL, MS_BIND, NULL))
216+
return -1;
217+
218+
if (mount(NULL, SET_GROUP_FROM, NULL, MS_SHARED, 0))
219+
return -1;
220+
221+
ret = syscall(SYS_move_mount, AT_FDCWD, SET_GROUP_FROM,
222+
AT_FDCWD, SET_GROUP_TO, MOVE_MOUNT_SET_GROUP);
223+
umount2("/tmp", MNT_DETACH);
224+
225+
return ret < 0 ? false : true;
226+
}
227+
228+
FIXTURE(move_mount_set_group) {
229+
};
230+
231+
#define SET_GROUP_A "/tmp/A"
232+
233+
FIXTURE_SETUP(move_mount_set_group)
234+
{
235+
int ret;
236+
237+
ASSERT_EQ(prepare_unpriv_mountns(), 0);
238+
239+
ret = move_mount_set_group_supported();
240+
ASSERT_GE(ret, 0);
241+
if (!ret)
242+
SKIP(return, "move_mount(MOVE_MOUNT_SET_GROUP) is not supported");
243+
244+
umount2("/tmp", MNT_DETACH);
245+
246+
ASSERT_EQ(mount("testing", "/tmp", "tmpfs", MS_NOATIME | MS_NODEV,
247+
"size=100000,mode=700"), 0);
248+
249+
ASSERT_EQ(mkdir(SET_GROUP_A, 0777), 0);
250+
251+
ASSERT_EQ(mount("testing", SET_GROUP_A, "tmpfs", MS_NOATIME | MS_NODEV,
252+
"size=100000,mode=700"), 0);
253+
}
254+
255+
FIXTURE_TEARDOWN(move_mount_set_group)
256+
{
257+
int ret;
258+
259+
ret = move_mount_set_group_supported();
260+
ASSERT_GE(ret, 0);
261+
if (!ret)
262+
SKIP(return, "move_mount(MOVE_MOUNT_SET_GROUP) is not supported");
263+
264+
umount2("/tmp", MNT_DETACH);
265+
}
266+
267+
#define __STACK_SIZE (8 * 1024 * 1024)
268+
static pid_t do_clone(int (*fn)(void *), void *arg, int flags)
269+
{
270+
void *stack;
271+
272+
stack = malloc(__STACK_SIZE);
273+
if (!stack)
274+
return -ENOMEM;
275+
276+
#ifdef __ia64__
277+
return __clone2(fn, stack, __STACK_SIZE, flags | SIGCHLD, arg, NULL);
278+
#else
279+
return clone(fn, stack + __STACK_SIZE, flags | SIGCHLD, arg, NULL);
280+
#endif
281+
}
282+
283+
static int wait_for_pid(pid_t pid)
284+
{
285+
int status, ret;
286+
287+
again:
288+
ret = waitpid(pid, &status, 0);
289+
if (ret == -1) {
290+
if (errno == EINTR)
291+
goto again;
292+
293+
return -1;
294+
}
295+
296+
if (!WIFEXITED(status))
297+
return -1;
298+
299+
return WEXITSTATUS(status);
300+
}
301+
302+
struct child_args {
303+
int unsfd;
304+
int mntnsfd;
305+
bool shared;
306+
int mntfd;
307+
};
308+
309+
static int get_nestedns_mount_cb(void *data)
310+
{
311+
struct child_args *ca = (struct child_args *)data;
312+
int ret;
313+
314+
ret = prepare_unpriv_mountns();
315+
if (ret)
316+
return 1;
317+
318+
if (ca->shared) {
319+
ret = mount(NULL, SET_GROUP_A, NULL, MS_SHARED, 0);
320+
if (ret)
321+
return 1;
322+
}
323+
324+
ret = open("/proc/self/ns/user", O_RDONLY);
325+
if (ret < 0)
326+
return 1;
327+
ca->unsfd = ret;
328+
329+
ret = open("/proc/self/ns/mnt", O_RDONLY);
330+
if (ret < 0)
331+
return 1;
332+
ca->mntnsfd = ret;
333+
334+
ret = open(SET_GROUP_A, O_RDONLY);
335+
if (ret < 0)
336+
return 1;
337+
ca->mntfd = ret;
338+
339+
return 0;
340+
}
341+
342+
TEST_F(move_mount_set_group, complex_sharing_copying)
343+
{
344+
struct child_args ca_from = {
345+
.shared = true,
346+
};
347+
struct child_args ca_to = {
348+
.shared = false,
349+
};
350+
pid_t pid;
351+
int ret;
352+
353+
ret = move_mount_set_group_supported();
354+
ASSERT_GE(ret, 0);
355+
if (!ret)
356+
SKIP(return, "move_mount(MOVE_MOUNT_SET_GROUP) is not supported");
357+
358+
pid = do_clone(get_nestedns_mount_cb, (void *)&ca_from, CLONE_VFORK |
359+
CLONE_VM | CLONE_FILES); ASSERT_GT(pid, 0);
360+
ASSERT_EQ(wait_for_pid(pid), 0);
361+
362+
pid = do_clone(get_nestedns_mount_cb, (void *)&ca_to, CLONE_VFORK |
363+
CLONE_VM | CLONE_FILES); ASSERT_GT(pid, 0);
364+
ASSERT_EQ(wait_for_pid(pid), 0);
365+
366+
ASSERT_EQ(syscall(SYS_move_mount, ca_from.mntfd, "",
367+
ca_to.mntfd, "", MOVE_MOUNT_SET_GROUP
368+
| MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH),
369+
0);
370+
371+
ASSERT_EQ(setns(ca_to.mntnsfd, CLONE_NEWNS), 0);
372+
ASSERT_EQ(is_shared_mount(SET_GROUP_A), 1);
373+
}
374+
375+
TEST_HARNESS_MAIN

0 commit comments

Comments
 (0)