xref: /linux/tools/testing/selftests/filesystems/empty_mntns/empty_mntns_test.c (revision 7c8a4671dc3247a26a702e5f5996e9f453d7070d)
132f54f2bSChristian Brauner // SPDX-License-Identifier: GPL-2.0-or-later
232f54f2bSChristian Brauner /*
332f54f2bSChristian Brauner  * Tests for empty mount namespace creation via UNSHARE_EMPTY_MNTNS
432f54f2bSChristian Brauner  *
532f54f2bSChristian Brauner  * Copyright (c) 2024 Christian Brauner <brauner@kernel.org>
632f54f2bSChristian Brauner  */
732f54f2bSChristian Brauner 
832f54f2bSChristian Brauner #define _GNU_SOURCE
932f54f2bSChristian Brauner #include <fcntl.h>
1032f54f2bSChristian Brauner #include <linux/mount.h>
1132f54f2bSChristian Brauner #include <linux/stat.h>
1232f54f2bSChristian Brauner #include <sched.h>
1332f54f2bSChristian Brauner #include <stdio.h>
1432f54f2bSChristian Brauner #include <string.h>
1532f54f2bSChristian Brauner #include <sys/mount.h>
1632f54f2bSChristian Brauner #include <sys/stat.h>
1732f54f2bSChristian Brauner #include <sys/types.h>
1832f54f2bSChristian Brauner #include <sys/wait.h>
1932f54f2bSChristian Brauner #include <unistd.h>
2032f54f2bSChristian Brauner 
2132f54f2bSChristian Brauner #include "../utils.h"
2232f54f2bSChristian Brauner #include "../wrappers.h"
2332f54f2bSChristian Brauner #include "empty_mntns.h"
2432f54f2bSChristian Brauner #include "kselftest_harness.h"
2532f54f2bSChristian Brauner 
2632f54f2bSChristian Brauner static bool unshare_empty_mntns_supported(void)
2732f54f2bSChristian Brauner {
2832f54f2bSChristian Brauner 	pid_t pid;
2932f54f2bSChristian Brauner 	int status;
3032f54f2bSChristian Brauner 
3132f54f2bSChristian Brauner 	pid = fork();
3232f54f2bSChristian Brauner 	if (pid < 0)
3332f54f2bSChristian Brauner 		return false;
3432f54f2bSChristian Brauner 
3532f54f2bSChristian Brauner 	if (pid == 0) {
3632f54f2bSChristian Brauner 		if (enter_userns())
3732f54f2bSChristian Brauner 			_exit(1);
3832f54f2bSChristian Brauner 
3932f54f2bSChristian Brauner 		if (unshare(UNSHARE_EMPTY_MNTNS) && errno == EINVAL)
4032f54f2bSChristian Brauner 			_exit(1);
4132f54f2bSChristian Brauner 		_exit(0);
4232f54f2bSChristian Brauner 	}
4332f54f2bSChristian Brauner 
4432f54f2bSChristian Brauner 	if (waitpid(pid, &status, 0) != pid)
4532f54f2bSChristian Brauner 		return false;
4632f54f2bSChristian Brauner 
4732f54f2bSChristian Brauner 	if (!WIFEXITED(status))
4832f54f2bSChristian Brauner 		return false;
4932f54f2bSChristian Brauner 
5032f54f2bSChristian Brauner 	return WEXITSTATUS(status) == 0;
5132f54f2bSChristian Brauner }
5232f54f2bSChristian Brauner 
5332f54f2bSChristian Brauner 
5432f54f2bSChristian Brauner FIXTURE(empty_mntns) {};
5532f54f2bSChristian Brauner 
5632f54f2bSChristian Brauner FIXTURE_SETUP(empty_mntns)
5732f54f2bSChristian Brauner {
5832f54f2bSChristian Brauner 	if (!unshare_empty_mntns_supported())
5932f54f2bSChristian Brauner 		SKIP(return, "UNSHARE_EMPTY_MNTNS not supported");
6032f54f2bSChristian Brauner }
6132f54f2bSChristian Brauner 
6232f54f2bSChristian Brauner FIXTURE_TEARDOWN(empty_mntns) {}
6332f54f2bSChristian Brauner 
6432f54f2bSChristian Brauner /* Verify unshare succeeds, produces exactly 1 mount, and root == cwd */
6532f54f2bSChristian Brauner TEST_F(empty_mntns, basic)
6632f54f2bSChristian Brauner {
6732f54f2bSChristian Brauner 	pid_t pid;
6832f54f2bSChristian Brauner 
6932f54f2bSChristian Brauner 	pid = fork();
7032f54f2bSChristian Brauner 	ASSERT_GE(pid, 0);
7132f54f2bSChristian Brauner 
7232f54f2bSChristian Brauner 	if (pid == 0) {
7332f54f2bSChristian Brauner 		uint64_t root_id, cwd_id;
7432f54f2bSChristian Brauner 
7532f54f2bSChristian Brauner 		if (enter_userns())
7632f54f2bSChristian Brauner 			_exit(1);
7732f54f2bSChristian Brauner 
7832f54f2bSChristian Brauner 		if (unshare(UNSHARE_EMPTY_MNTNS))
7932f54f2bSChristian Brauner 			_exit(2);
8032f54f2bSChristian Brauner 
8132f54f2bSChristian Brauner 		if (count_mounts() != 1)
8232f54f2bSChristian Brauner 			_exit(3);
8332f54f2bSChristian Brauner 
8432f54f2bSChristian Brauner 		root_id = get_unique_mnt_id("/");
8532f54f2bSChristian Brauner 		cwd_id = get_unique_mnt_id(".");
8632f54f2bSChristian Brauner 		if (root_id == 0 || cwd_id == 0)
8732f54f2bSChristian Brauner 			_exit(4);
8832f54f2bSChristian Brauner 
8932f54f2bSChristian Brauner 		if (root_id != cwd_id)
9032f54f2bSChristian Brauner 			_exit(5);
9132f54f2bSChristian Brauner 
9232f54f2bSChristian Brauner 		_exit(0);
9332f54f2bSChristian Brauner 	}
9432f54f2bSChristian Brauner 
9532f54f2bSChristian Brauner 	ASSERT_EQ(wait_for_pid(pid), 0);
9632f54f2bSChristian Brauner }
9732f54f2bSChristian Brauner 
9832f54f2bSChristian Brauner /*
9932f54f2bSChristian Brauner  * UNSHARE_EMPTY_MNTNS combined with CLONE_NEWUSER.
10032f54f2bSChristian Brauner  *
10132f54f2bSChristian Brauner  * The user namespace must be created first so /proc is still accessible
10232f54f2bSChristian Brauner  * for writing uid_map/gid_map.  The empty mount namespace is created
10332f54f2bSChristian Brauner  * afterwards.
10432f54f2bSChristian Brauner  */
10532f54f2bSChristian Brauner TEST_F(empty_mntns, with_clone_newuser)
10632f54f2bSChristian Brauner {
10732f54f2bSChristian Brauner 	pid_t pid;
10832f54f2bSChristian Brauner 
10932f54f2bSChristian Brauner 	pid = fork();
11032f54f2bSChristian Brauner 	ASSERT_GE(pid, 0);
11132f54f2bSChristian Brauner 
11232f54f2bSChristian Brauner 	if (pid == 0) {
11332f54f2bSChristian Brauner 		uid_t uid = getuid();
11432f54f2bSChristian Brauner 		gid_t gid = getgid();
11532f54f2bSChristian Brauner 		char map[100];
11632f54f2bSChristian Brauner 
11732f54f2bSChristian Brauner 		if (unshare(CLONE_NEWUSER))
11832f54f2bSChristian Brauner 			_exit(1);
11932f54f2bSChristian Brauner 
12032f54f2bSChristian Brauner 		snprintf(map, sizeof(map), "0 %d 1", uid);
12132f54f2bSChristian Brauner 		if (write_file("/proc/self/uid_map", map))
12232f54f2bSChristian Brauner 			_exit(2);
12332f54f2bSChristian Brauner 
12432f54f2bSChristian Brauner 		if (write_file("/proc/self/setgroups", "deny"))
12532f54f2bSChristian Brauner 			_exit(3);
12632f54f2bSChristian Brauner 
12732f54f2bSChristian Brauner 		snprintf(map, sizeof(map), "0 %d 1", gid);
12832f54f2bSChristian Brauner 		if (write_file("/proc/self/gid_map", map))
12932f54f2bSChristian Brauner 			_exit(4);
13032f54f2bSChristian Brauner 
13132f54f2bSChristian Brauner 		if (unshare(UNSHARE_EMPTY_MNTNS))
13232f54f2bSChristian Brauner 			_exit(5);
13332f54f2bSChristian Brauner 
13432f54f2bSChristian Brauner 		if (count_mounts() != 1)
13532f54f2bSChristian Brauner 			_exit(6);
13632f54f2bSChristian Brauner 
13732f54f2bSChristian Brauner 		_exit(0);
13832f54f2bSChristian Brauner 	}
13932f54f2bSChristian Brauner 
14032f54f2bSChristian Brauner 	ASSERT_EQ(wait_for_pid(pid), 0);
14132f54f2bSChristian Brauner }
14232f54f2bSChristian Brauner 
14332f54f2bSChristian Brauner /* UNSHARE_EMPTY_MNTNS combined with other namespace flags */
14432f54f2bSChristian Brauner TEST_F(empty_mntns, with_other_ns_flags)
14532f54f2bSChristian Brauner {
14632f54f2bSChristian Brauner 	pid_t pid;
14732f54f2bSChristian Brauner 
14832f54f2bSChristian Brauner 	pid = fork();
14932f54f2bSChristian Brauner 	ASSERT_GE(pid, 0);
15032f54f2bSChristian Brauner 
15132f54f2bSChristian Brauner 	if (pid == 0) {
15232f54f2bSChristian Brauner 		if (enter_userns())
15332f54f2bSChristian Brauner 			_exit(1);
15432f54f2bSChristian Brauner 
15532f54f2bSChristian Brauner 		if (unshare(UNSHARE_EMPTY_MNTNS | CLONE_NEWUTS | CLONE_NEWIPC))
15632f54f2bSChristian Brauner 			_exit(2);
15732f54f2bSChristian Brauner 
15832f54f2bSChristian Brauner 		if (count_mounts() != 1)
15932f54f2bSChristian Brauner 			_exit(3);
16032f54f2bSChristian Brauner 
16132f54f2bSChristian Brauner 		_exit(0);
16232f54f2bSChristian Brauner 	}
16332f54f2bSChristian Brauner 
16432f54f2bSChristian Brauner 	ASSERT_EQ(wait_for_pid(pid), 0);
16532f54f2bSChristian Brauner }
16632f54f2bSChristian Brauner 
16732f54f2bSChristian Brauner /* EPERM without proper capabilities */
16832f54f2bSChristian Brauner TEST_F(empty_mntns, eperm_without_caps)
16932f54f2bSChristian Brauner {
17032f54f2bSChristian Brauner 	pid_t pid;
17132f54f2bSChristian Brauner 
17232f54f2bSChristian Brauner 	pid = fork();
17332f54f2bSChristian Brauner 	ASSERT_GE(pid, 0);
17432f54f2bSChristian Brauner 
17532f54f2bSChristian Brauner 	if (pid == 0) {
17632f54f2bSChristian Brauner 		/* Skip if already root */
17732f54f2bSChristian Brauner 		if (getuid() == 0)
17832f54f2bSChristian Brauner 			_exit(0);
17932f54f2bSChristian Brauner 
18032f54f2bSChristian Brauner 		if (unshare(UNSHARE_EMPTY_MNTNS) == 0)
18132f54f2bSChristian Brauner 			_exit(1);
18232f54f2bSChristian Brauner 
18332f54f2bSChristian Brauner 		if (errno != EPERM)
18432f54f2bSChristian Brauner 			_exit(2);
18532f54f2bSChristian Brauner 
18632f54f2bSChristian Brauner 		_exit(0);
18732f54f2bSChristian Brauner 	}
18832f54f2bSChristian Brauner 
18932f54f2bSChristian Brauner 	ASSERT_EQ(wait_for_pid(pid), 0);
19032f54f2bSChristian Brauner }
19132f54f2bSChristian Brauner 
19232f54f2bSChristian Brauner /* Many source mounts still result in exactly 1 mount */
19332f54f2bSChristian Brauner TEST_F(empty_mntns, many_source_mounts)
19432f54f2bSChristian Brauner {
19532f54f2bSChristian Brauner 	pid_t pid;
19632f54f2bSChristian Brauner 
19732f54f2bSChristian Brauner 	pid = fork();
19832f54f2bSChristian Brauner 	ASSERT_GE(pid, 0);
19932f54f2bSChristian Brauner 
20032f54f2bSChristian Brauner 	if (pid == 0) {
20132f54f2bSChristian Brauner 		char tmpdir[] = "/tmp/empty_mntns_test.XXXXXX";
20232f54f2bSChristian Brauner 		int i;
20332f54f2bSChristian Brauner 
20432f54f2bSChristian Brauner 		if (enter_userns())
20532f54f2bSChristian Brauner 			_exit(1);
20632f54f2bSChristian Brauner 
20732f54f2bSChristian Brauner 		if (unshare(CLONE_NEWNS))
20832f54f2bSChristian Brauner 			_exit(2);
20932f54f2bSChristian Brauner 
21032f54f2bSChristian Brauner 		if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL))
21132f54f2bSChristian Brauner 			_exit(3);
21232f54f2bSChristian Brauner 
21332f54f2bSChristian Brauner 		if (!mkdtemp(tmpdir))
21432f54f2bSChristian Brauner 			_exit(4);
21532f54f2bSChristian Brauner 
21632f54f2bSChristian Brauner 		if (mount("tmpfs", tmpdir, "tmpfs", 0, "size=1M"))
21732f54f2bSChristian Brauner 			_exit(5);
21832f54f2bSChristian Brauner 
21932f54f2bSChristian Brauner 		for (i = 0; i < 5; i++) {
22032f54f2bSChristian Brauner 			char subdir[256];
22132f54f2bSChristian Brauner 
22232f54f2bSChristian Brauner 			snprintf(subdir, sizeof(subdir), "%s/sub%d", tmpdir, i);
22332f54f2bSChristian Brauner 			if (mkdir(subdir, 0755) && errno != EEXIST)
22432f54f2bSChristian Brauner 				_exit(6);
22532f54f2bSChristian Brauner 			if (mount(subdir, subdir, NULL, MS_BIND, NULL))
22632f54f2bSChristian Brauner 				_exit(7);
22732f54f2bSChristian Brauner 		}
22832f54f2bSChristian Brauner 
22932f54f2bSChristian Brauner 		if (count_mounts() < 5)
23032f54f2bSChristian Brauner 			_exit(8);
23132f54f2bSChristian Brauner 
23232f54f2bSChristian Brauner 		if (unshare(UNSHARE_EMPTY_MNTNS))
23332f54f2bSChristian Brauner 			_exit(9);
23432f54f2bSChristian Brauner 
23532f54f2bSChristian Brauner 		if (count_mounts() != 1)
23632f54f2bSChristian Brauner 			_exit(10);
23732f54f2bSChristian Brauner 
23832f54f2bSChristian Brauner 		_exit(0);
23932f54f2bSChristian Brauner 	}
24032f54f2bSChristian Brauner 
24132f54f2bSChristian Brauner 	ASSERT_EQ(wait_for_pid(pid), 0);
24232f54f2bSChristian Brauner }
24332f54f2bSChristian Brauner 
24432f54f2bSChristian Brauner /* CWD on a different mount gets reset to root */
24532f54f2bSChristian Brauner TEST_F(empty_mntns, cwd_reset)
24632f54f2bSChristian Brauner {
24732f54f2bSChristian Brauner 	pid_t pid;
24832f54f2bSChristian Brauner 
24932f54f2bSChristian Brauner 	pid = fork();
25032f54f2bSChristian Brauner 	ASSERT_GE(pid, 0);
25132f54f2bSChristian Brauner 
25232f54f2bSChristian Brauner 	if (pid == 0) {
25332f54f2bSChristian Brauner 		char tmpdir[] = "/tmp/empty_mntns_cwd.XXXXXX";
25432f54f2bSChristian Brauner 		uint64_t root_id, cwd_id;
25532f54f2bSChristian Brauner 		struct statmount *sm;
25632f54f2bSChristian Brauner 
25732f54f2bSChristian Brauner 		if (enter_userns())
25832f54f2bSChristian Brauner 			_exit(1);
25932f54f2bSChristian Brauner 
26032f54f2bSChristian Brauner 		if (unshare(CLONE_NEWNS))
26132f54f2bSChristian Brauner 			_exit(2);
26232f54f2bSChristian Brauner 
26332f54f2bSChristian Brauner 		if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL))
26432f54f2bSChristian Brauner 			_exit(3);
26532f54f2bSChristian Brauner 
26632f54f2bSChristian Brauner 		if (!mkdtemp(tmpdir))
26732f54f2bSChristian Brauner 			_exit(4);
26832f54f2bSChristian Brauner 
26932f54f2bSChristian Brauner 		if (mount("tmpfs", tmpdir, "tmpfs", 0, "size=1M"))
27032f54f2bSChristian Brauner 			_exit(5);
27132f54f2bSChristian Brauner 
27232f54f2bSChristian Brauner 		if (chdir(tmpdir))
27332f54f2bSChristian Brauner 			_exit(6);
27432f54f2bSChristian Brauner 
27532f54f2bSChristian Brauner 		if (unshare(UNSHARE_EMPTY_MNTNS))
27632f54f2bSChristian Brauner 			_exit(7);
27732f54f2bSChristian Brauner 
27832f54f2bSChristian Brauner 		root_id = get_unique_mnt_id("/");
27932f54f2bSChristian Brauner 		cwd_id = get_unique_mnt_id(".");
28032f54f2bSChristian Brauner 		if (root_id == 0 || cwd_id == 0)
28132f54f2bSChristian Brauner 			_exit(8);
28232f54f2bSChristian Brauner 
28332f54f2bSChristian Brauner 		if (root_id != cwd_id)
28432f54f2bSChristian Brauner 			_exit(9);
28532f54f2bSChristian Brauner 
286*1a398a23SChristian Brauner 		sm = statmount_alloc(root_id, 0, STATMOUNT_MNT_ROOT | STATMOUNT_MNT_POINT, 0);
28732f54f2bSChristian Brauner 		if (!sm)
28832f54f2bSChristian Brauner 			_exit(10);
28932f54f2bSChristian Brauner 
29032f54f2bSChristian Brauner 		if (strcmp(sm->str + sm->mnt_point, "/") != 0)
29132f54f2bSChristian Brauner 			_exit(11);
29232f54f2bSChristian Brauner 
29332f54f2bSChristian Brauner 		free(sm);
29432f54f2bSChristian Brauner 		_exit(0);
29532f54f2bSChristian Brauner 	}
29632f54f2bSChristian Brauner 
29732f54f2bSChristian Brauner 	ASSERT_EQ(wait_for_pid(pid), 0);
29832f54f2bSChristian Brauner }
29932f54f2bSChristian Brauner 
30032f54f2bSChristian Brauner /* Verify statmount properties of the root mount */
30132f54f2bSChristian Brauner TEST_F(empty_mntns, mount_properties)
30232f54f2bSChristian Brauner {
30332f54f2bSChristian Brauner 	pid_t pid;
30432f54f2bSChristian Brauner 
30532f54f2bSChristian Brauner 	pid = fork();
30632f54f2bSChristian Brauner 	ASSERT_GE(pid, 0);
30732f54f2bSChristian Brauner 
30832f54f2bSChristian Brauner 	if (pid == 0) {
30932f54f2bSChristian Brauner 		struct statmount *sm;
31032f54f2bSChristian Brauner 		uint64_t root_id;
31132f54f2bSChristian Brauner 
31232f54f2bSChristian Brauner 		if (enter_userns())
31332f54f2bSChristian Brauner 			_exit(1);
31432f54f2bSChristian Brauner 
31532f54f2bSChristian Brauner 		if (unshare(UNSHARE_EMPTY_MNTNS))
31632f54f2bSChristian Brauner 			_exit(2);
31732f54f2bSChristian Brauner 
31832f54f2bSChristian Brauner 		root_id = get_unique_mnt_id("/");
31932f54f2bSChristian Brauner 		if (!root_id)
32032f54f2bSChristian Brauner 			_exit(3);
32132f54f2bSChristian Brauner 
32232f54f2bSChristian Brauner 		sm = statmount_alloc(root_id, 0, STATMOUNT_MNT_BASIC | STATMOUNT_MNT_ROOT |
323*1a398a23SChristian Brauner 				     STATMOUNT_MNT_POINT | STATMOUNT_FS_TYPE, 0);
32432f54f2bSChristian Brauner 		if (!sm)
32532f54f2bSChristian Brauner 			_exit(4);
32632f54f2bSChristian Brauner 
32732f54f2bSChristian Brauner 		if (!(sm->mask & STATMOUNT_MNT_POINT))
32832f54f2bSChristian Brauner 			_exit(5);
32932f54f2bSChristian Brauner 
33032f54f2bSChristian Brauner 		if (strcmp(sm->str + sm->mnt_point, "/") != 0)
33132f54f2bSChristian Brauner 			_exit(6);
33232f54f2bSChristian Brauner 
33332f54f2bSChristian Brauner 		if (!(sm->mask & STATMOUNT_MNT_BASIC))
33432f54f2bSChristian Brauner 			_exit(7);
33532f54f2bSChristian Brauner 
33632f54f2bSChristian Brauner 		if (sm->mnt_id != root_id)
33732f54f2bSChristian Brauner 			_exit(8);
33832f54f2bSChristian Brauner 
33932f54f2bSChristian Brauner 		free(sm);
34032f54f2bSChristian Brauner 		_exit(0);
34132f54f2bSChristian Brauner 	}
34232f54f2bSChristian Brauner 
34332f54f2bSChristian Brauner 	ASSERT_EQ(wait_for_pid(pid), 0);
34432f54f2bSChristian Brauner }
34532f54f2bSChristian Brauner 
34632f54f2bSChristian Brauner /* Consecutive UNSHARE_EMPTY_MNTNS calls produce new namespaces */
34732f54f2bSChristian Brauner TEST_F(empty_mntns, repeated_unshare)
34832f54f2bSChristian Brauner {
34932f54f2bSChristian Brauner 	pid_t pid;
35032f54f2bSChristian Brauner 
35132f54f2bSChristian Brauner 	pid = fork();
35232f54f2bSChristian Brauner 	ASSERT_GE(pid, 0);
35332f54f2bSChristian Brauner 
35432f54f2bSChristian Brauner 	if (pid == 0) {
35532f54f2bSChristian Brauner 		uint64_t first_root_id, second_root_id;
35632f54f2bSChristian Brauner 
35732f54f2bSChristian Brauner 		if (enter_userns())
35832f54f2bSChristian Brauner 			_exit(1);
35932f54f2bSChristian Brauner 
36032f54f2bSChristian Brauner 		if (unshare(UNSHARE_EMPTY_MNTNS))
36132f54f2bSChristian Brauner 			_exit(2);
36232f54f2bSChristian Brauner 
36332f54f2bSChristian Brauner 		if (count_mounts() != 1)
36432f54f2bSChristian Brauner 			_exit(3);
36532f54f2bSChristian Brauner 
36632f54f2bSChristian Brauner 		first_root_id = get_unique_mnt_id("/");
36732f54f2bSChristian Brauner 
36832f54f2bSChristian Brauner 		if (unshare(UNSHARE_EMPTY_MNTNS))
36932f54f2bSChristian Brauner 			_exit(4);
37032f54f2bSChristian Brauner 
37132f54f2bSChristian Brauner 		if (count_mounts() != 1)
37232f54f2bSChristian Brauner 			_exit(5);
37332f54f2bSChristian Brauner 
37432f54f2bSChristian Brauner 		second_root_id = get_unique_mnt_id("/");
37532f54f2bSChristian Brauner 
37632f54f2bSChristian Brauner 		if (first_root_id == second_root_id)
37732f54f2bSChristian Brauner 			_exit(6);
37832f54f2bSChristian Brauner 
37932f54f2bSChristian Brauner 		_exit(0);
38032f54f2bSChristian Brauner 	}
38132f54f2bSChristian Brauner 
38232f54f2bSChristian Brauner 	ASSERT_EQ(wait_for_pid(pid), 0);
38332f54f2bSChristian Brauner }
38432f54f2bSChristian Brauner 
38532f54f2bSChristian Brauner /* Root mount's parent is itself */
38632f54f2bSChristian Brauner TEST_F(empty_mntns, root_is_own_parent)
38732f54f2bSChristian Brauner {
38832f54f2bSChristian Brauner 	pid_t pid;
38932f54f2bSChristian Brauner 
39032f54f2bSChristian Brauner 	pid = fork();
39132f54f2bSChristian Brauner 	ASSERT_GE(pid, 0);
39232f54f2bSChristian Brauner 
39332f54f2bSChristian Brauner 	if (pid == 0) {
39432f54f2bSChristian Brauner 		struct statmount sm;
39532f54f2bSChristian Brauner 		uint64_t root_id;
39632f54f2bSChristian Brauner 
39732f54f2bSChristian Brauner 		if (enter_userns())
39832f54f2bSChristian Brauner 			_exit(1);
39932f54f2bSChristian Brauner 
40032f54f2bSChristian Brauner 		if (unshare(UNSHARE_EMPTY_MNTNS))
40132f54f2bSChristian Brauner 			_exit(2);
40232f54f2bSChristian Brauner 
40332f54f2bSChristian Brauner 		root_id = get_unique_mnt_id("/");
40432f54f2bSChristian Brauner 		if (!root_id)
40532f54f2bSChristian Brauner 			_exit(3);
40632f54f2bSChristian Brauner 
40732f54f2bSChristian Brauner 		if (statmount(root_id, 0, 0, STATMOUNT_MNT_BASIC, &sm, sizeof(sm), 0) < 0)
40832f54f2bSChristian Brauner 			_exit(4);
40932f54f2bSChristian Brauner 
41032f54f2bSChristian Brauner 		if (!(sm.mask & STATMOUNT_MNT_BASIC))
41132f54f2bSChristian Brauner 			_exit(5);
41232f54f2bSChristian Brauner 
41332f54f2bSChristian Brauner 		if (sm.mnt_parent_id != sm.mnt_id)
41432f54f2bSChristian Brauner 			_exit(6);
41532f54f2bSChristian Brauner 
41632f54f2bSChristian Brauner 		_exit(0);
41732f54f2bSChristian Brauner 	}
41832f54f2bSChristian Brauner 
41932f54f2bSChristian Brauner 	ASSERT_EQ(wait_for_pid(pid), 0);
42032f54f2bSChristian Brauner }
42132f54f2bSChristian Brauner 
42232f54f2bSChristian Brauner /* Listmount returns only the root mount */
42332f54f2bSChristian Brauner TEST_F(empty_mntns, listmount_single_entry)
42432f54f2bSChristian Brauner {
42532f54f2bSChristian Brauner 	pid_t pid;
42632f54f2bSChristian Brauner 
42732f54f2bSChristian Brauner 	pid = fork();
42832f54f2bSChristian Brauner 	ASSERT_GE(pid, 0);
42932f54f2bSChristian Brauner 
43032f54f2bSChristian Brauner 	if (pid == 0) {
43132f54f2bSChristian Brauner 		uint64_t list[16];
43232f54f2bSChristian Brauner 		ssize_t nr_mounts;
43332f54f2bSChristian Brauner 		uint64_t root_id;
43432f54f2bSChristian Brauner 
43532f54f2bSChristian Brauner 		if (enter_userns())
43632f54f2bSChristian Brauner 			_exit(1);
43732f54f2bSChristian Brauner 
43832f54f2bSChristian Brauner 		if (unshare(UNSHARE_EMPTY_MNTNS))
43932f54f2bSChristian Brauner 			_exit(2);
44032f54f2bSChristian Brauner 
44132f54f2bSChristian Brauner 		nr_mounts = listmount(LSMT_ROOT, 0, 0, list, 16, 0);
44232f54f2bSChristian Brauner 		if (nr_mounts != 1)
44332f54f2bSChristian Brauner 			_exit(3);
44432f54f2bSChristian Brauner 
44532f54f2bSChristian Brauner 		root_id = get_unique_mnt_id("/");
44632f54f2bSChristian Brauner 		if (!root_id)
44732f54f2bSChristian Brauner 			_exit(4);
44832f54f2bSChristian Brauner 
44932f54f2bSChristian Brauner 		if (list[0] != root_id)
45032f54f2bSChristian Brauner 			_exit(5);
45132f54f2bSChristian Brauner 
45232f54f2bSChristian Brauner 		_exit(0);
45332f54f2bSChristian Brauner 	}
45432f54f2bSChristian Brauner 
45532f54f2bSChristian Brauner 	ASSERT_EQ(wait_for_pid(pid), 0);
45632f54f2bSChristian Brauner }
45732f54f2bSChristian Brauner 
45832f54f2bSChristian Brauner /*
45932f54f2bSChristian Brauner  * Mount tmpfs over nullfs root to build a writable filesystem from scratch.
46032f54f2bSChristian Brauner  * This exercises the intended usage pattern: create an empty mount namespace
46132f54f2bSChristian Brauner  * (which has a nullfs root), then mount a real filesystem over it.
46232f54f2bSChristian Brauner  *
46332f54f2bSChristian Brauner  * Because resolving "/" returns the process root directly (via nd_jump_root)
46432f54f2bSChristian Brauner  * without following overmounts, we use the new mount API (fsopen/fsmount)
46532f54f2bSChristian Brauner  * to obtain a mount fd, then fchdir + chroot to enter the new filesystem.
46632f54f2bSChristian Brauner  */
46732f54f2bSChristian Brauner TEST_F(empty_mntns, overmount_tmpfs)
46832f54f2bSChristian Brauner {
46932f54f2bSChristian Brauner 	pid_t pid;
47032f54f2bSChristian Brauner 
47132f54f2bSChristian Brauner 	pid = fork();
47232f54f2bSChristian Brauner 	ASSERT_GE(pid, 0);
47332f54f2bSChristian Brauner 
47432f54f2bSChristian Brauner 	if (pid == 0) {
47532f54f2bSChristian Brauner 		struct statmount *sm;
47632f54f2bSChristian Brauner 		uint64_t root_id, cwd_id;
47732f54f2bSChristian Brauner 		int fd, fsfd, mntfd;
47832f54f2bSChristian Brauner 
47932f54f2bSChristian Brauner 		if (enter_userns())
48032f54f2bSChristian Brauner 			_exit(1);
48132f54f2bSChristian Brauner 
48232f54f2bSChristian Brauner 		if (unshare(UNSHARE_EMPTY_MNTNS))
48332f54f2bSChristian Brauner 			_exit(2);
48432f54f2bSChristian Brauner 
48532f54f2bSChristian Brauner 		if (count_mounts() != 1)
48632f54f2bSChristian Brauner 			_exit(3);
48732f54f2bSChristian Brauner 
48832f54f2bSChristian Brauner 		root_id = get_unique_mnt_id("/");
48932f54f2bSChristian Brauner 		if (!root_id)
49032f54f2bSChristian Brauner 			_exit(4);
49132f54f2bSChristian Brauner 
49232f54f2bSChristian Brauner 		/* Verify root is nullfs */
493*1a398a23SChristian Brauner 		sm = statmount_alloc(root_id, 0, STATMOUNT_FS_TYPE, 0);
49432f54f2bSChristian Brauner 		if (!sm)
49532f54f2bSChristian Brauner 			_exit(5);
49632f54f2bSChristian Brauner 
49732f54f2bSChristian Brauner 		if (!(sm->mask & STATMOUNT_FS_TYPE))
49832f54f2bSChristian Brauner 			_exit(6);
49932f54f2bSChristian Brauner 
50032f54f2bSChristian Brauner 		if (strcmp(sm->str + sm->fs_type, "nullfs") != 0)
50132f54f2bSChristian Brauner 			_exit(7);
50232f54f2bSChristian Brauner 
50332f54f2bSChristian Brauner 		free(sm);
50432f54f2bSChristian Brauner 
50532f54f2bSChristian Brauner 		cwd_id = get_unique_mnt_id(".");
50632f54f2bSChristian Brauner 		if (!cwd_id || root_id != cwd_id)
50732f54f2bSChristian Brauner 			_exit(8);
50832f54f2bSChristian Brauner 
50932f54f2bSChristian Brauner 		/*
51032f54f2bSChristian Brauner 		 * nullfs root is immutable.  open(O_CREAT) returns ENOENT
51132f54f2bSChristian Brauner 		 * because empty_dir_lookup() returns -ENOENT before the
51232f54f2bSChristian Brauner 		 * IS_IMMUTABLE permission check in may_o_create() is reached.
51332f54f2bSChristian Brauner 		 */
51432f54f2bSChristian Brauner 		fd = open("/test", O_CREAT | O_RDWR, 0644);
51532f54f2bSChristian Brauner 		if (fd >= 0) {
51632f54f2bSChristian Brauner 			close(fd);
51732f54f2bSChristian Brauner 			_exit(9);
51832f54f2bSChristian Brauner 		}
51932f54f2bSChristian Brauner 		if (errno != ENOENT)
52032f54f2bSChristian Brauner 			_exit(10);
52132f54f2bSChristian Brauner 
52232f54f2bSChristian Brauner 		/*
52332f54f2bSChristian Brauner 		 * Use the new mount API to create tmpfs and get a mount fd.
52432f54f2bSChristian Brauner 		 * We need the fd because after attaching the tmpfs on top of
52532f54f2bSChristian Brauner 		 * "/", path resolution of "/" still returns the process root
52632f54f2bSChristian Brauner 		 * (nullfs) without following the overmount.  The mount fd
52732f54f2bSChristian Brauner 		 * lets us fchdir + chroot into the tmpfs.
52832f54f2bSChristian Brauner 		 */
52932f54f2bSChristian Brauner 		fsfd = sys_fsopen("tmpfs", 0);
53032f54f2bSChristian Brauner 		if (fsfd < 0)
53132f54f2bSChristian Brauner 			_exit(11);
53232f54f2bSChristian Brauner 
53332f54f2bSChristian Brauner 		if (sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "size", "1M", 0)) {
53432f54f2bSChristian Brauner 			close(fsfd);
53532f54f2bSChristian Brauner 			_exit(12);
53632f54f2bSChristian Brauner 		}
53732f54f2bSChristian Brauner 
53832f54f2bSChristian Brauner 		if (sys_fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0)) {
53932f54f2bSChristian Brauner 			close(fsfd);
54032f54f2bSChristian Brauner 			_exit(13);
54132f54f2bSChristian Brauner 		}
54232f54f2bSChristian Brauner 
54332f54f2bSChristian Brauner 		mntfd = sys_fsmount(fsfd, 0, 0);
54432f54f2bSChristian Brauner 		close(fsfd);
54532f54f2bSChristian Brauner 		if (mntfd < 0)
54632f54f2bSChristian Brauner 			_exit(14);
54732f54f2bSChristian Brauner 
54832f54f2bSChristian Brauner 		if (sys_move_mount(mntfd, "", AT_FDCWD, "/",
54932f54f2bSChristian Brauner 				   MOVE_MOUNT_F_EMPTY_PATH)) {
55032f54f2bSChristian Brauner 			close(mntfd);
55132f54f2bSChristian Brauner 			_exit(15);
55232f54f2bSChristian Brauner 		}
55332f54f2bSChristian Brauner 
55432f54f2bSChristian Brauner 		if (count_mounts() != 2) {
55532f54f2bSChristian Brauner 			close(mntfd);
55632f54f2bSChristian Brauner 			_exit(16);
55732f54f2bSChristian Brauner 		}
55832f54f2bSChristian Brauner 
55932f54f2bSChristian Brauner 		/* Enter the tmpfs via the mount fd */
56032f54f2bSChristian Brauner 		if (fchdir(mntfd)) {
56132f54f2bSChristian Brauner 			close(mntfd);
56232f54f2bSChristian Brauner 			_exit(17);
56332f54f2bSChristian Brauner 		}
56432f54f2bSChristian Brauner 
56532f54f2bSChristian Brauner 		if (chroot(".")) {
56632f54f2bSChristian Brauner 			close(mntfd);
56732f54f2bSChristian Brauner 			_exit(18);
56832f54f2bSChristian Brauner 		}
56932f54f2bSChristian Brauner 
57032f54f2bSChristian Brauner 		close(mntfd);
57132f54f2bSChristian Brauner 
57232f54f2bSChristian Brauner 		/* Verify "/" now resolves to tmpfs */
57332f54f2bSChristian Brauner 		root_id = get_unique_mnt_id("/");
57432f54f2bSChristian Brauner 		if (!root_id)
57532f54f2bSChristian Brauner 			_exit(19);
57632f54f2bSChristian Brauner 
577*1a398a23SChristian Brauner 		sm = statmount_alloc(root_id, 0, STATMOUNT_FS_TYPE, 0);
57832f54f2bSChristian Brauner 		if (!sm)
57932f54f2bSChristian Brauner 			_exit(20);
58032f54f2bSChristian Brauner 
58132f54f2bSChristian Brauner 		if (!(sm->mask & STATMOUNT_FS_TYPE))
58232f54f2bSChristian Brauner 			_exit(21);
58332f54f2bSChristian Brauner 
58432f54f2bSChristian Brauner 		if (strcmp(sm->str + sm->fs_type, "tmpfs") != 0)
58532f54f2bSChristian Brauner 			_exit(22);
58632f54f2bSChristian Brauner 
58732f54f2bSChristian Brauner 		free(sm);
58832f54f2bSChristian Brauner 
58932f54f2bSChristian Brauner 		/* Verify tmpfs is writable */
59032f54f2bSChristian Brauner 		fd = open("/testfile", O_CREAT | O_RDWR, 0644);
59132f54f2bSChristian Brauner 		if (fd < 0)
59232f54f2bSChristian Brauner 			_exit(23);
59332f54f2bSChristian Brauner 
59432f54f2bSChristian Brauner 		if (write(fd, "test", 4) != 4) {
59532f54f2bSChristian Brauner 			close(fd);
59632f54f2bSChristian Brauner 			_exit(24);
59732f54f2bSChristian Brauner 		}
59832f54f2bSChristian Brauner 
59932f54f2bSChristian Brauner 		close(fd);
60032f54f2bSChristian Brauner 
60132f54f2bSChristian Brauner 		if (access("/testfile", F_OK))
60232f54f2bSChristian Brauner 			_exit(25);
60332f54f2bSChristian Brauner 
60432f54f2bSChristian Brauner 		_exit(0);
60532f54f2bSChristian Brauner 	}
60632f54f2bSChristian Brauner 
60732f54f2bSChristian Brauner 	ASSERT_EQ(wait_for_pid(pid), 0);
60832f54f2bSChristian Brauner }
60932f54f2bSChristian Brauner 
61032f54f2bSChristian Brauner /*
61132f54f2bSChristian Brauner  * Tests below do not require UNSHARE_EMPTY_MNTNS support.
61232f54f2bSChristian Brauner  */
61332f54f2bSChristian Brauner 
61432f54f2bSChristian Brauner /* Invalid unshare flags return EINVAL */
61532f54f2bSChristian Brauner TEST(invalid_flags)
61632f54f2bSChristian Brauner {
61732f54f2bSChristian Brauner 	pid_t pid;
61832f54f2bSChristian Brauner 
61932f54f2bSChristian Brauner 	pid = fork();
62032f54f2bSChristian Brauner 	ASSERT_GE(pid, 0);
62132f54f2bSChristian Brauner 
62232f54f2bSChristian Brauner 	if (pid == 0) {
62332f54f2bSChristian Brauner 		if (enter_userns())
62432f54f2bSChristian Brauner 			_exit(1);
62532f54f2bSChristian Brauner 
62632f54f2bSChristian Brauner 		if (unshare(0x80000000) == 0)
62732f54f2bSChristian Brauner 			_exit(2);
62832f54f2bSChristian Brauner 
62932f54f2bSChristian Brauner 		if (errno != EINVAL)
63032f54f2bSChristian Brauner 			_exit(3);
63132f54f2bSChristian Brauner 
63232f54f2bSChristian Brauner 		_exit(0);
63332f54f2bSChristian Brauner 	}
63432f54f2bSChristian Brauner 
63532f54f2bSChristian Brauner 	ASSERT_EQ(wait_for_pid(pid), 0);
63632f54f2bSChristian Brauner }
63732f54f2bSChristian Brauner 
63832f54f2bSChristian Brauner /* Regular CLONE_NEWNS still copies the full mount tree */
63932f54f2bSChristian Brauner TEST(clone_newns_full_copy)
64032f54f2bSChristian Brauner {
64132f54f2bSChristian Brauner 	pid_t pid;
64232f54f2bSChristian Brauner 
64332f54f2bSChristian Brauner 	pid = fork();
64432f54f2bSChristian Brauner 	ASSERT_GE(pid, 0);
64532f54f2bSChristian Brauner 
64632f54f2bSChristian Brauner 	if (pid == 0) {
64732f54f2bSChristian Brauner 		ssize_t nr_mounts_before, nr_mounts_after;
64832f54f2bSChristian Brauner 		char tmpdir[] = "/tmp/empty_mntns_regr.XXXXXX";
64932f54f2bSChristian Brauner 		int i;
65032f54f2bSChristian Brauner 
65132f54f2bSChristian Brauner 		if (enter_userns())
65232f54f2bSChristian Brauner 			_exit(1);
65332f54f2bSChristian Brauner 
65432f54f2bSChristian Brauner 		if (unshare(CLONE_NEWNS))
65532f54f2bSChristian Brauner 			_exit(2);
65632f54f2bSChristian Brauner 
65732f54f2bSChristian Brauner 		if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL))
65832f54f2bSChristian Brauner 			_exit(3);
65932f54f2bSChristian Brauner 
66032f54f2bSChristian Brauner 		if (!mkdtemp(tmpdir))
66132f54f2bSChristian Brauner 			_exit(4);
66232f54f2bSChristian Brauner 
66332f54f2bSChristian Brauner 		if (mount("tmpfs", tmpdir, "tmpfs", 0, "size=1M"))
66432f54f2bSChristian Brauner 			_exit(5);
66532f54f2bSChristian Brauner 
66632f54f2bSChristian Brauner 		for (i = 0; i < 3; i++) {
66732f54f2bSChristian Brauner 			char subdir[256];
66832f54f2bSChristian Brauner 
66932f54f2bSChristian Brauner 			snprintf(subdir, sizeof(subdir), "%s/sub%d", tmpdir, i);
67032f54f2bSChristian Brauner 			if (mkdir(subdir, 0755) && errno != EEXIST)
67132f54f2bSChristian Brauner 				_exit(6);
67232f54f2bSChristian Brauner 			if (mount(subdir, subdir, NULL, MS_BIND, NULL))
67332f54f2bSChristian Brauner 				_exit(7);
67432f54f2bSChristian Brauner 		}
67532f54f2bSChristian Brauner 
67632f54f2bSChristian Brauner 		nr_mounts_before = count_mounts();
67732f54f2bSChristian Brauner 		if (nr_mounts_before < 3)
67832f54f2bSChristian Brauner 			_exit(8);
67932f54f2bSChristian Brauner 
68032f54f2bSChristian Brauner 		if (unshare(CLONE_NEWNS))
68132f54f2bSChristian Brauner 			_exit(9);
68232f54f2bSChristian Brauner 
68332f54f2bSChristian Brauner 		nr_mounts_after = count_mounts();
68432f54f2bSChristian Brauner 		if (nr_mounts_after < nr_mounts_before)
68532f54f2bSChristian Brauner 			_exit(10);
68632f54f2bSChristian Brauner 
68732f54f2bSChristian Brauner 		_exit(0);
68832f54f2bSChristian Brauner 	}
68932f54f2bSChristian Brauner 
69032f54f2bSChristian Brauner 	ASSERT_EQ(wait_for_pid(pid), 0);
69132f54f2bSChristian Brauner }
69232f54f2bSChristian Brauner 
69332f54f2bSChristian Brauner /* Other namespace unshares are unaffected */
69432f54f2bSChristian Brauner TEST(other_ns_unaffected)
69532f54f2bSChristian Brauner {
69632f54f2bSChristian Brauner 	pid_t pid;
69732f54f2bSChristian Brauner 
69832f54f2bSChristian Brauner 	pid = fork();
69932f54f2bSChristian Brauner 	ASSERT_GE(pid, 0);
70032f54f2bSChristian Brauner 
70132f54f2bSChristian Brauner 	if (pid == 0) {
70232f54f2bSChristian Brauner 		char hostname[256];
70332f54f2bSChristian Brauner 
70432f54f2bSChristian Brauner 		if (enter_userns())
70532f54f2bSChristian Brauner 			_exit(1);
70632f54f2bSChristian Brauner 
70732f54f2bSChristian Brauner 		if (unshare(CLONE_NEWUTS))
70832f54f2bSChristian Brauner 			_exit(2);
70932f54f2bSChristian Brauner 
71032f54f2bSChristian Brauner 		if (sethostname("test-empty-mntns", 16))
71132f54f2bSChristian Brauner 			_exit(3);
71232f54f2bSChristian Brauner 
71332f54f2bSChristian Brauner 		if (gethostname(hostname, sizeof(hostname)))
71432f54f2bSChristian Brauner 			_exit(4);
71532f54f2bSChristian Brauner 
71632f54f2bSChristian Brauner 		if (strcmp(hostname, "test-empty-mntns") != 0)
71732f54f2bSChristian Brauner 			_exit(5);
71832f54f2bSChristian Brauner 
71932f54f2bSChristian Brauner 		_exit(0);
72032f54f2bSChristian Brauner 	}
72132f54f2bSChristian Brauner 
72232f54f2bSChristian Brauner 	ASSERT_EQ(wait_for_pid(pid), 0);
72332f54f2bSChristian Brauner }
72432f54f2bSChristian Brauner 
72532f54f2bSChristian Brauner TEST_HARNESS_MAIN
726