• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 //
3 // Copyright (c) 2019 Google, Inc.
4 
5 #define _GNU_SOURCE
6 
7 #include "config.h"
8 
9 #include <errno.h>
10 #include <lapi/syscalls.h>
11 #include <sched.h>
12 
13 #include <sys/mount.h>
14 #include <stdlib.h>
15 
16 #include "tst_test.h"
17 #include "lapi/mount.h"
18 
19 #ifdef HAVE_UNSHARE
20 
21 #ifdef HAVE_LIBCAP
22 #include <sys/capability.h>
23 #endif
24 
25 #define CHROOT_DIR	"chroot"
26 #define NEW_ROOT	"/new_root"
27 #define PUT_OLD		"/new_root/put_old"
28 #define PUT_OLD_FS	"/put_old_fs"
29 #define PUT_OLD_BAD	"/put_old_fs/put_old"
30 
31 enum {
32 	/*
33 	 * Test consists of a series of steps that allow pivot_root to succeed,
34 	 * which is run when param is NORMAL. All other values tweak one of the
35 	 * steps to induce a failure, and check the errno is as expected.
36 	 */
37 	NORMAL,
38 
39 	/*
40 	 * EBUSY
41 	 * new_root or put_old are on the current root file system
42 	 */
43 	NEW_ROOT_ON_CURRENT_ROOT,
44 
45 	/*
46 	 * EINVAL
47 	 * put_old is not underneath new_root
48 	 * Note: if put_old and new_root are on the same fs,
49 	 * pivot_root fails with EBUSY before testing reachability
50 	 */
51 	PUT_OLD_NOT_UNDERNEATH_NEW_ROOT,
52 
53 	/*
54 	 * ENOTDIR
55 	 * new_root or put_old is not a directory
56 	 */
57 	PUT_OLD_NOT_DIR,
58 
59 	/*
60 	 * EPERM
61 	 * The calling process does not have the CAP_SYS_ADMIN capability.
62 	 */
63 	NO_CAP_SYS_ADMIN,
64 };
65 
66 static const struct test_case {
67 	int test_case;
68 	int expected_error;
69 } test_cases[] = {
70 	{NORMAL, 0},
71 	{NEW_ROOT_ON_CURRENT_ROOT, EBUSY},
72 	{PUT_OLD_NOT_UNDERNEATH_NEW_ROOT, EINVAL},
73 	{PUT_OLD_NOT_DIR, ENOTDIR},
74 	{NO_CAP_SYS_ADMIN, EPERM},
75 };
76 
77 #ifdef HAVE_LIBCAP
drop_cap_sys_admin(void)78 static void drop_cap_sys_admin(void)
79 {
80 	cap_value_t cap_value[] = { CAP_SYS_ADMIN };
81 	cap_t cap = cap_get_proc();
82 	if (!cap)
83 		tst_brk(TBROK | TERRNO, "cap_get_proc failed");
84 
85 	if (cap_set_flag(cap, CAP_EFFECTIVE, 1, cap_value, CAP_CLEAR))
86 		tst_brk(TBROK | TERRNO, "cap_set_flag failed");
87 
88 	if (cap_set_proc(cap))
89 		tst_brk(TBROK | TERRNO, "cap_set_proc failed");
90 }
91 #endif
92 
run(unsigned int test_case)93 static void run(unsigned int test_case)
94 {
95 	/* Work in child process - needed to undo unshare and chroot */
96 	if (SAFE_FORK()) {
97 		tst_reap_children();
98 		return;
99 	}
100 
101 	/* pivot_root requires no shared mounts exist in process namespace */
102 	TEST(unshare(CLONE_NEWNS | CLONE_FS));
103 	if (TST_RET == -1)
104 		tst_brk(TFAIL | TERRNO, "unshare failed");
105 
106 	/*
107 	 * Create an initial root dir. pivot_root doesn't work if the initial root
108 	 * dir is a initramfs, so use chroot to create a safe environment
109 	 */
110 	SAFE_MOUNT("none", "/", NULL, MS_REC|MS_PRIVATE, NULL);
111 	SAFE_MOUNT("none", CHROOT_DIR, "tmpfs", 0, 0);
112 	SAFE_CHROOT(CHROOT_DIR);
113 
114 	SAFE_MKDIR(NEW_ROOT, 0777);
115 
116 	/*
117 	 * pivot_root only works if new_root is a mount point, so mount a tmpfs
118 	 * unless testing for that fail mode
119 	 */
120 	if (test_cases[test_case].test_case != NEW_ROOT_ON_CURRENT_ROOT)
121 		SAFE_MOUNT("none", NEW_ROOT, "tmpfs", 0, 0);
122 
123 	/*
124 	 * Create put_old under new_root, unless testing for that specific fail
125 	 * mode
126 	 */
127 	const char* actual_put_old = NULL;
128 	if (test_cases[test_case].test_case == PUT_OLD_NOT_UNDERNEATH_NEW_ROOT) {
129 		actual_put_old = PUT_OLD_BAD;
130 		SAFE_MKDIR(PUT_OLD_FS, 0777);
131 		SAFE_MOUNT("none", PUT_OLD_FS, "tmpfs", 0, 0);
132 		SAFE_MKDIR(PUT_OLD_BAD, 0777);
133 	} else {
134 		actual_put_old = PUT_OLD;
135 
136 		if (test_cases[test_case].test_case == PUT_OLD_NOT_DIR)
137 			SAFE_CREAT(PUT_OLD, 0777);
138 		else
139 			SAFE_MKDIR(PUT_OLD, 0777);
140 	}
141 
142 	if (test_cases[test_case].test_case == NO_CAP_SYS_ADMIN) {
143 #ifdef HAVE_LIBCAP
144 		drop_cap_sys_admin();
145 #else
146 		tst_res(TCONF,
147 			"System doesn't have POSIX capabilities support");
148 		return;
149 #endif
150 	}
151 
152 	TEST(syscall(__NR_pivot_root, NEW_ROOT, actual_put_old));
153 
154 	if (test_cases[test_case].test_case == NORMAL) {
155 		if (TST_RET)
156 			tst_res(TFAIL | TERRNO, "pivot_root failed");
157 		else
158 			tst_res(TPASS, "pivot_root succeeded");
159 
160 		return;
161 	}
162 
163 	if (TST_RET == 0) {
164 		tst_res(TFAIL, "pivot_root succeeded unexpectedly");
165 		return;
166 	}
167 
168 	if (errno != test_cases[test_case].expected_error) {
169 		tst_res(TFAIL | TERRNO,	"pivot_root failed with wrong errno");
170 		return;
171 	}
172 
173 	tst_res(TPASS | TERRNO, "pivot_root failed as expectedly");
174 }
175 
setup(void)176 static void setup(void)
177 {
178 	SAFE_MKDIR(CHROOT_DIR, 0777);
179 }
180 
181 static struct tst_test test = {
182 	.test = run,
183 	.tcnt = ARRAY_SIZE(test_cases),
184 	.needs_tmpdir = 1,
185 	.needs_root = 1,
186 	.forks_child = 1,
187 	.setup = setup,
188 };
189 
190 #else
191 	TST_TEST_TCONF("unshare is undefined.");
192 #endif
193