xref: /linux/tools/testing/selftests/filesystems/overlayfs/idmapped_mounts.c (revision aaed66fadba2d2de8fe0daa0aa3eac827d2076b9)
1*1ec284f3SChristian Brauner // SPDX-License-Identifier: GPL-2.0
2*1ec284f3SChristian Brauner #define _GNU_SOURCE
3*1ec284f3SChristian Brauner 
4*1ec284f3SChristian Brauner #include <fcntl.h>
5*1ec284f3SChristian Brauner #include <limits.h>
6*1ec284f3SChristian Brauner #include <sched.h>
7*1ec284f3SChristian Brauner #include <stdio.h>
8*1ec284f3SChristian Brauner #include <unistd.h>
9*1ec284f3SChristian Brauner #include <sys/stat.h>
10*1ec284f3SChristian Brauner #include <sys/syscall.h>
11*1ec284f3SChristian Brauner 
12*1ec284f3SChristian Brauner #include <linux/mount.h>
13*1ec284f3SChristian Brauner #include <linux/types.h>
14*1ec284f3SChristian Brauner 
15*1ec284f3SChristian Brauner #include "kselftest_harness.h"
16*1ec284f3SChristian Brauner #include "../wrappers.h"
17*1ec284f3SChristian Brauner #include "../utils.h"
18*1ec284f3SChristian Brauner 
19*1ec284f3SChristian Brauner /*
20*1ec284f3SChristian Brauner  * An idmapping that maps the mount-visible id range [0, ID_RANGE) onto the
21*1ec284f3SChristian Brauner  * host/overlay-final id range [ID_HOST, ID_HOST + ID_RANGE).  Through such an
22*1ec284f3SChristian Brauner  * idmapped overlay mount, an overlay-final id of ID_HOST + n is reported as n,
23*1ec284f3SChristian Brauner  * and an id of n requested through the mount is stored as ID_HOST + n.
24*1ec284f3SChristian Brauner  */
25*1ec284f3SChristian Brauner #define ID_NS	 0
26*1ec284f3SChristian Brauner #define ID_HOST	 10000
27*1ec284f3SChristian Brauner #define ID_RANGE 10000
28*1ec284f3SChristian Brauner 
29*1ec284f3SChristian Brauner /*
30*1ec284f3SChristian Brauner  * For the composition test the lower layer's on-disk ids live in a
31*1ec284f3SChristian Brauner  * separate range and are mapped by an idmapped lower layer onto the
32*1ec284f3SChristian Brauner  * overlay-final range [ID_HOST, ID_HOST + ID_RANGE).
33*1ec284f3SChristian Brauner  */
34*1ec284f3SChristian Brauner #define LAYER_HOST 20000
35*1ec284f3SChristian Brauner 
36*1ec284f3SChristian Brauner #ifndef MOUNT_ATTR_IDMAP
37*1ec284f3SChristian Brauner #define MOUNT_ATTR_IDMAP 0x00100000
38*1ec284f3SChristian Brauner #endif
39*1ec284f3SChristian Brauner 
40*1ec284f3SChristian Brauner #ifndef __NR_mount_setattr
41*1ec284f3SChristian Brauner #define __NR_mount_setattr 442
42*1ec284f3SChristian Brauner #endif
43*1ec284f3SChristian Brauner 
44*1ec284f3SChristian Brauner static inline int sys_mount_setattr(int dfd, const char *path,
45*1ec284f3SChristian Brauner 				    unsigned int flags,
46*1ec284f3SChristian Brauner 				    struct mount_attr *attr, size_t size)
47*1ec284f3SChristian Brauner {
48*1ec284f3SChristian Brauner 	return syscall(__NR_mount_setattr, dfd, path, flags, attr, size);
49*1ec284f3SChristian Brauner }
50*1ec284f3SChristian Brauner 
51*1ec284f3SChristian Brauner static bool ovl_supported(void)
52*1ec284f3SChristian Brauner {
53*1ec284f3SChristian Brauner 	int fd = sys_fsopen("overlay", 0);
54*1ec284f3SChristian Brauner 
55*1ec284f3SChristian Brauner 	if (fd < 0)
56*1ec284f3SChristian Brauner 		return false;
57*1ec284f3SChristian Brauner 	close(fd);
58*1ec284f3SChristian Brauner 	return true;
59*1ec284f3SChristian Brauner }
60*1ec284f3SChristian Brauner 
61*1ec284f3SChristian Brauner /* base/{l,u,w} owned by ID_HOST so they map to ID_NS through the idmap. */
62*1ec284f3SChristian Brauner static int setup_layers(const char *base)
63*1ec284f3SChristian Brauner {
64*1ec284f3SChristian Brauner 	static const char *sub[] = { "", "/l", "/u", "/w" };
65*1ec284f3SChristian Brauner 	char path[PATH_MAX];
66*1ec284f3SChristian Brauner 
67*1ec284f3SChristian Brauner 	for (size_t i = 0; i < ARRAY_SIZE(sub); i++) {
68*1ec284f3SChristian Brauner 		snprintf(path, sizeof(path), "%s%s", base, sub[i]);
69*1ec284f3SChristian Brauner 		if (mkdir(path, 0755) && errno != EEXIST)
70*1ec284f3SChristian Brauner 			return -1;
71*1ec284f3SChristian Brauner 		if (i && chown(path, ID_HOST, ID_HOST))
72*1ec284f3SChristian Brauner 			return -1;
73*1ec284f3SChristian Brauner 	}
74*1ec284f3SChristian Brauner 	return 0;
75*1ec284f3SChristian Brauner }
76*1ec284f3SChristian Brauner 
77*1ec284f3SChristian Brauner static int ovl_mount(const char *base, bool nfs_export)
78*1ec284f3SChristian Brauner {
79*1ec284f3SChristian Brauner 	char lower[PATH_MAX], upper[PATH_MAX], work[PATH_MAX];
80*1ec284f3SChristian Brauner 	int fsfd, ovl;
81*1ec284f3SChristian Brauner 
82*1ec284f3SChristian Brauner 	snprintf(lower, sizeof(lower), "%s/l", base);
83*1ec284f3SChristian Brauner 	snprintf(upper, sizeof(upper), "%s/u", base);
84*1ec284f3SChristian Brauner 	snprintf(work, sizeof(work), "%s/w", base);
85*1ec284f3SChristian Brauner 
86*1ec284f3SChristian Brauner 	fsfd = sys_fsopen("overlay", 0);
87*1ec284f3SChristian Brauner 	if (fsfd < 0)
88*1ec284f3SChristian Brauner 		return -1;
89*1ec284f3SChristian Brauner 
90*1ec284f3SChristian Brauner 	if (sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "test", 0) ||
91*1ec284f3SChristian Brauner 	    sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "lowerdir", lower, 0) ||
92*1ec284f3SChristian Brauner 	    sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "upperdir", upper, 0) ||
93*1ec284f3SChristian Brauner 	    sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "workdir", work, 0))
94*1ec284f3SChristian Brauner 		goto err;
95*1ec284f3SChristian Brauner 	if (nfs_export &&
96*1ec284f3SChristian Brauner 	    (sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "index", "on", 0) ||
97*1ec284f3SChristian Brauner 	     sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "nfs_export", "on", 0)))
98*1ec284f3SChristian Brauner 		goto err;
99*1ec284f3SChristian Brauner 	if (sys_fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0))
100*1ec284f3SChristian Brauner 		goto err;
101*1ec284f3SChristian Brauner 
102*1ec284f3SChristian Brauner 	ovl = sys_fsmount(fsfd, 0, 0);
103*1ec284f3SChristian Brauner 	close(fsfd);
104*1ec284f3SChristian Brauner 	return ovl;
105*1ec284f3SChristian Brauner err:
106*1ec284f3SChristian Brauner 	close(fsfd);
107*1ec284f3SChristian Brauner 	return -1;
108*1ec284f3SChristian Brauner }
109*1ec284f3SChristian Brauner 
110*1ec284f3SChristian Brauner /* Idmap the (still detached, not yet visible) overlay mount @mfd. */
111*1ec284f3SChristian Brauner static int ovl_idmap(int mfd)
112*1ec284f3SChristian Brauner {
113*1ec284f3SChristian Brauner 	struct mount_attr attr = {
114*1ec284f3SChristian Brauner 		.attr_set = MOUNT_ATTR_IDMAP,
115*1ec284f3SChristian Brauner 	};
116*1ec284f3SChristian Brauner 	int ret, userns_fd;
117*1ec284f3SChristian Brauner 
118*1ec284f3SChristian Brauner 	/*
119*1ec284f3SChristian Brauner 	 * get_userns_fd(fs_id, mount_id, range): a file whose filesystem id
120*1ec284f3SChristian Brauner 	 * is fs_id + n is shown through the idmapped mount as mount_id + n.
121*1ec284f3SChristian Brauner 	 * Here the overlay-final (fs side) range is [ID_HOST, ..) and the
122*1ec284f3SChristian Brauner 	 * caller-visible (mount side) range is [ID_NS, ..).
123*1ec284f3SChristian Brauner 	 */
124*1ec284f3SChristian Brauner 	userns_fd = get_userns_fd(ID_HOST, ID_NS, ID_RANGE);
125*1ec284f3SChristian Brauner 	if (userns_fd < 0)
126*1ec284f3SChristian Brauner 		return -1;
127*1ec284f3SChristian Brauner 
128*1ec284f3SChristian Brauner 	attr.userns_fd = userns_fd;
129*1ec284f3SChristian Brauner 	ret = sys_mount_setattr(mfd, "", AT_EMPTY_PATH, &attr, sizeof(attr));
130*1ec284f3SChristian Brauner 	close(userns_fd);
131*1ec284f3SChristian Brauner 	return ret;
132*1ec284f3SChristian Brauner }
133*1ec284f3SChristian Brauner 
134*1ec284f3SChristian Brauner /* Clone @path into a detached, idmapped mount usable as an overlay layer. */
135*1ec284f3SChristian Brauner static int idmapped_layer_fd(const char *path, int nsid, int hostid, int range)
136*1ec284f3SChristian Brauner {
137*1ec284f3SChristian Brauner 	struct mount_attr attr = {
138*1ec284f3SChristian Brauner 		.attr_set = MOUNT_ATTR_IDMAP,
139*1ec284f3SChristian Brauner 	};
140*1ec284f3SChristian Brauner 	int fd_tree, userns_fd;
141*1ec284f3SChristian Brauner 
142*1ec284f3SChristian Brauner 	fd_tree = sys_open_tree(AT_FDCWD, path,
143*1ec284f3SChristian Brauner 			       OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
144*1ec284f3SChristian Brauner 	if (fd_tree < 0)
145*1ec284f3SChristian Brauner 		return -1;
146*1ec284f3SChristian Brauner 	userns_fd = get_userns_fd(nsid, hostid, range);
147*1ec284f3SChristian Brauner 	if (userns_fd < 0) {
148*1ec284f3SChristian Brauner 		close(fd_tree);
149*1ec284f3SChristian Brauner 		return -1;
150*1ec284f3SChristian Brauner 	}
151*1ec284f3SChristian Brauner 	attr.userns_fd = userns_fd;
152*1ec284f3SChristian Brauner 	if (sys_mount_setattr(fd_tree, "", AT_EMPTY_PATH, &attr,
153*1ec284f3SChristian Brauner 			      sizeof(attr))) {
154*1ec284f3SChristian Brauner 		close(userns_fd);
155*1ec284f3SChristian Brauner 		close(fd_tree);
156*1ec284f3SChristian Brauner 		return -1;
157*1ec284f3SChristian Brauner 	}
158*1ec284f3SChristian Brauner 	close(userns_fd);
159*1ec284f3SChristian Brauner 	return fd_tree;
160*1ec284f3SChristian Brauner }
161*1ec284f3SChristian Brauner 
162*1ec284f3SChristian Brauner /* Overlay with a layer passed by fd (idmapped) plus a plain upper/work. */
163*1ec284f3SChristian Brauner static int ovl_mount_lower_fd(const char *upper, const char *work, int fd_lower)
164*1ec284f3SChristian Brauner {
165*1ec284f3SChristian Brauner 	int fsfd, ovl;
166*1ec284f3SChristian Brauner 
167*1ec284f3SChristian Brauner 	fsfd = sys_fsopen("overlay", 0);
168*1ec284f3SChristian Brauner 	if (fsfd < 0)
169*1ec284f3SChristian Brauner 		return -1;
170*1ec284f3SChristian Brauner 
171*1ec284f3SChristian Brauner 	if (sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "test", 0) ||
172*1ec284f3SChristian Brauner 	    sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "upperdir", upper, 0) ||
173*1ec284f3SChristian Brauner 	    sys_fsconfig(fsfd, FSCONFIG_SET_STRING, "workdir", work, 0) ||
174*1ec284f3SChristian Brauner 	    sys_fsconfig(fsfd, FSCONFIG_SET_FD, "lowerdir+", NULL, fd_lower) ||
175*1ec284f3SChristian Brauner 	    sys_fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0))
176*1ec284f3SChristian Brauner 		goto err;
177*1ec284f3SChristian Brauner 
178*1ec284f3SChristian Brauner 	ovl = sys_fsmount(fsfd, 0, 0);
179*1ec284f3SChristian Brauner 	close(fsfd);
180*1ec284f3SChristian Brauner 	return ovl;
181*1ec284f3SChristian Brauner err:
182*1ec284f3SChristian Brauner 	close(fsfd);
183*1ec284f3SChristian Brauner 	return -1;
184*1ec284f3SChristian Brauner }
185*1ec284f3SChristian Brauner 
186*1ec284f3SChristian Brauner /*
187*1ec284f3SChristian Brauner  * Mount an overlay inside user namespace @u1 (so the overlay sb's s_user_ns is
188*1ec284f3SChristian Brauner  * not the initial namespace) and idmap that overlay mount with @u2.  Runs in a
189*1ec284f3SChristian Brauner  * child that joins @u1; returns 0 on success.
190*1ec284f3SChristian Brauner  */
191*1ec284f3SChristian Brauner static int userns_overlay_child(int u1)
192*1ec284f3SChristian Brauner {
193*1ec284f3SChristian Brauner 	struct mount_attr attr = {
194*1ec284f3SChristian Brauner 		.attr_set = MOUNT_ATTR_IDMAP,
195*1ec284f3SChristian Brauner 	};
196*1ec284f3SChristian Brauner 	struct stat st;
197*1ec284f3SChristian Brauner 	int ovl, u2;
198*1ec284f3SChristian Brauner 
199*1ec284f3SChristian Brauner 	/* Become root in the overlay sb's user namespace u1. */
200*1ec284f3SChristian Brauner 	if (!switch_userns(u1, 0, 0, false))
201*1ec284f3SChristian Brauner 		return fprintf(stderr, "userns: switch_userns: %m\n"), -1;
202*1ec284f3SChristian Brauner 	if (unshare(CLONE_NEWNS) ||
203*1ec284f3SChristian Brauner 	    sys_mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL))
204*1ec284f3SChristian Brauner 		return fprintf(stderr, "userns: unshare/slave: %m\n"), -1;
205*1ec284f3SChristian Brauner 	if (sys_mount("tmpfs", "/tmp", "tmpfs", 0, NULL))
206*1ec284f3SChristian Brauner 		return fprintf(stderr, "userns: mount tmpfs: %m\n"), -1;
207*1ec284f3SChristian Brauner 	if (setup_layers("/tmp/ovl"))
208*1ec284f3SChristian Brauner 		return fprintf(stderr, "userns: setup_layers: %m\n"), -1;
209*1ec284f3SChristian Brauner 	if (mknod("/tmp/ovl/l/file", S_IFREG | 0644, 0) ||
210*1ec284f3SChristian Brauner 	    chown("/tmp/ovl/l/file", ID_HOST + 5, ID_HOST + 5))
211*1ec284f3SChristian Brauner 		return fprintf(stderr, "userns: lower file: %m\n"), -1;
212*1ec284f3SChristian Brauner 
213*1ec284f3SChristian Brauner 	ovl = ovl_mount("/tmp/ovl", false);
214*1ec284f3SChristian Brauner 	if (ovl < 0)
215*1ec284f3SChristian Brauner 		return fprintf(stderr, "userns: ovl_mount: %m\n"), -1;
216*1ec284f3SChristian Brauner 
217*1ec284f3SChristian Brauner 	/*
218*1ec284f3SChristian Brauner 	 * mount_setattr() requires CAP_SYS_ADMIN over the idmap user
219*1ec284f3SChristian Brauner 	 * namespace, so it must be a child of u1.  Create it now, from
220*1ec284f3SChristian Brauner 	 * inside u1.
221*1ec284f3SChristian Brauner 	 */
222*1ec284f3SChristian Brauner 	u2 = get_userns_fd(ID_HOST, ID_NS, ID_RANGE);
223*1ec284f3SChristian Brauner 	if (u2 < 0)
224*1ec284f3SChristian Brauner 		return fprintf(stderr, "userns: get_userns_fd: %m\n"), -1;
225*1ec284f3SChristian Brauner 	attr.userns_fd = u2;
226*1ec284f3SChristian Brauner 	if (sys_mount_setattr(ovl, "", AT_EMPTY_PATH, &attr, sizeof(attr)))
227*1ec284f3SChristian Brauner 		return fprintf(stderr, "userns: mount_setattr: %m\n"), -1;
228*1ec284f3SChristian Brauner 	close(u2);
229*1ec284f3SChristian Brauner 
230*1ec284f3SChristian Brauner 	if (fstatat(ovl, "file", &st, 0))
231*1ec284f3SChristian Brauner 		return fprintf(stderr, "userns: fstatat: %m\n"), -1;
232*1ec284f3SChristian Brauner 	if (st.st_uid != ID_NS + 5 || st.st_gid != ID_NS + 5) {
233*1ec284f3SChristian Brauner 		fprintf(stderr, "userns: got %u:%u expected %u:%u\n",
234*1ec284f3SChristian Brauner 			st.st_uid, st.st_gid, ID_NS + 5, ID_NS + 5);
235*1ec284f3SChristian Brauner 		return -1;
236*1ec284f3SChristian Brauner 	}
237*1ec284f3SChristian Brauner 	return 0;
238*1ec284f3SChristian Brauner }
239*1ec284f3SChristian Brauner 
240*1ec284f3SChristian Brauner FIXTURE(idmapped_overlay) {
241*1ec284f3SChristian Brauner 	char base[64];
242*1ec284f3SChristian Brauner };
243*1ec284f3SChristian Brauner 
244*1ec284f3SChristian Brauner FIXTURE_SETUP(idmapped_overlay)
245*1ec284f3SChristian Brauner {
246*1ec284f3SChristian Brauner 	/* Private mount namespace so test mounts need no cleanup. */
247*1ec284f3SChristian Brauner 	ASSERT_EQ(unshare(CLONE_NEWNS), 0);
248*1ec284f3SChristian Brauner 	ASSERT_EQ(sys_mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL), 0);
249*1ec284f3SChristian Brauner 
250*1ec284f3SChristian Brauner 	/* tmpfs for the layers so we can chown them to arbitrary ids. */
251*1ec284f3SChristian Brauner 	ASSERT_EQ(sys_mount("tmpfs", "/tmp", "tmpfs", 0, NULL), 0);
252*1ec284f3SChristian Brauner 
253*1ec284f3SChristian Brauner 	snprintf(self->base, sizeof(self->base), "/tmp/ovl");
254*1ec284f3SChristian Brauner 	ASSERT_EQ(setup_layers(self->base), 0);
255*1ec284f3SChristian Brauner }
256*1ec284f3SChristian Brauner 
257*1ec284f3SChristian Brauner FIXTURE_TEARDOWN(idmapped_overlay)
258*1ec284f3SChristian Brauner {
259*1ec284f3SChristian Brauner }
260*1ec284f3SChristian Brauner 
261*1ec284f3SChristian Brauner /* A file owned by ID_HOST + 5 is reported as ID_NS + 5 through the idmap. */
262*1ec284f3SChristian Brauner TEST_F(idmapped_overlay, getattr)
263*1ec284f3SChristian Brauner {
264*1ec284f3SChristian Brauner 	char path[PATH_MAX];
265*1ec284f3SChristian Brauner 	struct stat st;
266*1ec284f3SChristian Brauner 	int ovl;
267*1ec284f3SChristian Brauner 
268*1ec284f3SChristian Brauner 	if (!ovl_supported())
269*1ec284f3SChristian Brauner 		SKIP(return, "overlayfs not supported");
270*1ec284f3SChristian Brauner 
271*1ec284f3SChristian Brauner 	snprintf(path, sizeof(path), "%s/l/file", self->base);
272*1ec284f3SChristian Brauner 	ASSERT_EQ(mknod(path, S_IFREG | 0644, 0), 0);
273*1ec284f3SChristian Brauner 	ASSERT_EQ(chown(path, ID_HOST + 5, ID_HOST + 5), 0);
274*1ec284f3SChristian Brauner 
275*1ec284f3SChristian Brauner 	ovl = ovl_mount(self->base, false);
276*1ec284f3SChristian Brauner 	ASSERT_GE(ovl, 0);
277*1ec284f3SChristian Brauner 	ASSERT_EQ(ovl_idmap(ovl), 0);
278*1ec284f3SChristian Brauner 
279*1ec284f3SChristian Brauner 	ASSERT_EQ(fstatat(ovl, "file", &st, 0), 0);
280*1ec284f3SChristian Brauner 	EXPECT_EQ(st.st_uid, ID_NS + 5);
281*1ec284f3SChristian Brauner 	EXPECT_EQ(st.st_gid, ID_NS + 5);
282*1ec284f3SChristian Brauner 
283*1ec284f3SChristian Brauner 	EXPECT_EQ(close(ovl), 0);
284*1ec284f3SChristian Brauner }
285*1ec284f3SChristian Brauner 
286*1ec284f3SChristian Brauner /*
287*1ec284f3SChristian Brauner  * Every creation path initializes the new owner through the mount idmap:
288*1ec284f3SChristian Brauner  * created as caller id ID_NS, stored on the upper layer as overlay-final
289*1ec284f3SChristian Brauner  * ID_HOST.  Covers ovl_create() (regular file), ovl_mkdir(), ovl_mknod()
290*1ec284f3SChristian Brauner  * and ovl_symlink() (which share ovl_create_object()), plus the separate
291*1ec284f3SChristian Brauner  * ovl_tmpfile() path.
292*1ec284f3SChristian Brauner  */
293*1ec284f3SChristian Brauner TEST_F(idmapped_overlay, create)
294*1ec284f3SChristian Brauner {
295*1ec284f3SChristian Brauner 	static const char *names[] = { "reg", "dir", "fifo", "lnk" };
296*1ec284f3SChristian Brauner 	char path[PATH_MAX];
297*1ec284f3SChristian Brauner 	struct stat st;
298*1ec284f3SChristian Brauner 	int ovl, fd;
299*1ec284f3SChristian Brauner 
300*1ec284f3SChristian Brauner 	if (!ovl_supported())
301*1ec284f3SChristian Brauner 		SKIP(return, "overlayfs not supported");
302*1ec284f3SChristian Brauner 
303*1ec284f3SChristian Brauner 	ovl = ovl_mount(self->base, false);
304*1ec284f3SChristian Brauner 	ASSERT_GE(ovl, 0);
305*1ec284f3SChristian Brauner 	ASSERT_EQ(ovl_idmap(ovl), 0);
306*1ec284f3SChristian Brauner 
307*1ec284f3SChristian Brauner 	/* One object per creation operation, all as caller id ID_NS. */
308*1ec284f3SChristian Brauner 	fd = openat(ovl, "reg", O_CREAT | O_WRONLY | O_EXCL, 0644);
309*1ec284f3SChristian Brauner 	ASSERT_GE(fd, 0);
310*1ec284f3SChristian Brauner 	EXPECT_EQ(close(fd), 0);
311*1ec284f3SChristian Brauner 	ASSERT_EQ(mkdirat(ovl, "dir", 0755), 0);
312*1ec284f3SChristian Brauner 	ASSERT_EQ(mknodat(ovl, "fifo", S_IFIFO | 0644, 0), 0);
313*1ec284f3SChristian Brauner 	ASSERT_EQ(symlinkat("target", ovl, "lnk"), 0);
314*1ec284f3SChristian Brauner 
315*1ec284f3SChristian Brauner 	for (size_t i = 0; i < ARRAY_SIZE(names); i++) {
316*1ec284f3SChristian Brauner 		/* Reported as ID_NS through the idmapped mount ... */
317*1ec284f3SChristian Brauner 		ASSERT_EQ(fstatat(ovl, names[i], &st, AT_SYMLINK_NOFOLLOW), 0);
318*1ec284f3SChristian Brauner 		EXPECT_EQ(st.st_uid, ID_NS);
319*1ec284f3SChristian Brauner 		EXPECT_EQ(st.st_gid, ID_NS);
320*1ec284f3SChristian Brauner 		/* ... and stored as ID_HOST on the upper layer. */
321*1ec284f3SChristian Brauner 		snprintf(path, sizeof(path), "%s/u/%s", self->base, names[i]);
322*1ec284f3SChristian Brauner 		ASSERT_EQ(lstat(path, &st), 0);
323*1ec284f3SChristian Brauner 		EXPECT_EQ(st.st_uid, ID_HOST);
324*1ec284f3SChristian Brauner 		EXPECT_EQ(st.st_gid, ID_HOST);
325*1ec284f3SChristian Brauner 	}
326*1ec284f3SChristian Brauner 
327*1ec284f3SChristian Brauner 	/* O_TMPFILE goes through the separate ovl_tmpfile() path. */
328*1ec284f3SChristian Brauner 	fd = openat(ovl, ".", O_TMPFILE | O_WRONLY, 0644);
329*1ec284f3SChristian Brauner 	ASSERT_GE(fd, 0);
330*1ec284f3SChristian Brauner 	/* Inside the mount: caller id ID_NS. */
331*1ec284f3SChristian Brauner 	ASSERT_EQ(fstat(fd, &st), 0);
332*1ec284f3SChristian Brauner 	EXPECT_EQ(st.st_uid, ID_NS);
333*1ec284f3SChristian Brauner 	EXPECT_EQ(st.st_gid, ID_NS);
334*1ec284f3SChristian Brauner 	/* Link it in so the upper backing file can be inspected too. */
335*1ec284f3SChristian Brauner 	ASSERT_EQ(linkat(fd, "", ovl, "tmp", AT_EMPTY_PATH), 0);
336*1ec284f3SChristian Brauner 	EXPECT_EQ(close(fd), 0);
337*1ec284f3SChristian Brauner 	snprintf(path, sizeof(path), "%s/u/tmp", self->base);
338*1ec284f3SChristian Brauner 	ASSERT_EQ(lstat(path, &st), 0);
339*1ec284f3SChristian Brauner 	EXPECT_EQ(st.st_uid, ID_HOST);
340*1ec284f3SChristian Brauner 	EXPECT_EQ(st.st_gid, ID_HOST);
341*1ec284f3SChristian Brauner 
342*1ec284f3SChristian Brauner 	EXPECT_EQ(close(ovl), 0);
343*1ec284f3SChristian Brauner }
344*1ec284f3SChristian Brauner 
345*1ec284f3SChristian Brauner /* chown through the idmapped mount round-trips: ID_NS + 5 <-> ID_HOST + 5. */
346*1ec284f3SChristian Brauner TEST_F(idmapped_overlay, chown)
347*1ec284f3SChristian Brauner {
348*1ec284f3SChristian Brauner 	char path[PATH_MAX];
349*1ec284f3SChristian Brauner 	struct stat st;
350*1ec284f3SChristian Brauner 	int ovl, fd;
351*1ec284f3SChristian Brauner 
352*1ec284f3SChristian Brauner 	if (!ovl_supported())
353*1ec284f3SChristian Brauner 		SKIP(return, "overlayfs not supported");
354*1ec284f3SChristian Brauner 
355*1ec284f3SChristian Brauner 	ovl = ovl_mount(self->base, false);
356*1ec284f3SChristian Brauner 	ASSERT_GE(ovl, 0);
357*1ec284f3SChristian Brauner 	ASSERT_EQ(ovl_idmap(ovl), 0);
358*1ec284f3SChristian Brauner 
359*1ec284f3SChristian Brauner 	fd = openat(ovl, "f", O_CREAT | O_WRONLY | O_EXCL, 0644);
360*1ec284f3SChristian Brauner 	ASSERT_GE(fd, 0);
361*1ec284f3SChristian Brauner 	EXPECT_EQ(close(fd), 0);
362*1ec284f3SChristian Brauner 
363*1ec284f3SChristian Brauner 	ASSERT_EQ(fchownat(ovl, "f", ID_NS + 5, ID_NS + 5, 0), 0);
364*1ec284f3SChristian Brauner 
365*1ec284f3SChristian Brauner 	ASSERT_EQ(fstatat(ovl, "f", &st, 0), 0);
366*1ec284f3SChristian Brauner 	EXPECT_EQ(st.st_uid, ID_NS + 5);
367*1ec284f3SChristian Brauner 	EXPECT_EQ(st.st_gid, ID_NS + 5);
368*1ec284f3SChristian Brauner 
369*1ec284f3SChristian Brauner 	snprintf(path, sizeof(path), "%s/u/f", self->base);
370*1ec284f3SChristian Brauner 	ASSERT_EQ(stat(path, &st), 0);
371*1ec284f3SChristian Brauner 	EXPECT_EQ(st.st_uid, ID_HOST + 5);
372*1ec284f3SChristian Brauner 	EXPECT_EQ(st.st_gid, ID_HOST + 5);
373*1ec284f3SChristian Brauner 
374*1ec284f3SChristian Brauner 	EXPECT_EQ(close(ovl), 0);
375*1ec284f3SChristian Brauner }
376*1ec284f3SChristian Brauner 
377*1ec284f3SChristian Brauner /*
378*1ec284f3SChristian Brauner  * Composition: an idmapped lower layer underneath an idmapped overlay mount.
379*1ec284f3SChristian Brauner  * An on-disk id is mapped by the layer idmap into the overlay-final range and
380*1ec284f3SChristian Brauner  * then by the mount idmap into the caller's range:
381*1ec284f3SChristian Brauner  *
382*1ec284f3SChristian Brauner  *   on-disk LAYER_HOST+7  --layer-->  ID_HOST+7  --mount-->  ID_NS+7
383*1ec284f3SChristian Brauner  */
384*1ec284f3SChristian Brauner TEST_F(idmapped_overlay, composition)
385*1ec284f3SChristian Brauner {
386*1ec284f3SChristian Brauner 	char lower[PATH_MAX], upper[PATH_MAX], work[PATH_MAX], path[PATH_MAX];
387*1ec284f3SChristian Brauner 	struct stat st;
388*1ec284f3SChristian Brauner 	int ovl, fd_lower;
389*1ec284f3SChristian Brauner 
390*1ec284f3SChristian Brauner 	if (!ovl_supported())
391*1ec284f3SChristian Brauner 		SKIP(return, "overlayfs not supported");
392*1ec284f3SChristian Brauner 
393*1ec284f3SChristian Brauner 	snprintf(lower, sizeof(lower), "%s/l", self->base);
394*1ec284f3SChristian Brauner 	snprintf(upper, sizeof(upper), "%s/u", self->base);
395*1ec284f3SChristian Brauner 	snprintf(work, sizeof(work), "%s/w", self->base);
396*1ec284f3SChristian Brauner 
397*1ec284f3SChristian Brauner 	/* Put the lower layer's ids in the on-disk [LAYER_HOST, ..) range. */
398*1ec284f3SChristian Brauner 	ASSERT_EQ(chown(lower, LAYER_HOST, LAYER_HOST), 0);
399*1ec284f3SChristian Brauner 	snprintf(path, sizeof(path), "%s/l/file", self->base);
400*1ec284f3SChristian Brauner 	ASSERT_EQ(mknod(path, S_IFREG | 0644, 0), 0);
401*1ec284f3SChristian Brauner 	ASSERT_EQ(chown(path, LAYER_HOST + 7, LAYER_HOST + 7), 0);
402*1ec284f3SChristian Brauner 
403*1ec284f3SChristian Brauner 	/* Idmapped lower: on-disk LAYER_HOST <-> overlay-final ID_HOST. */
404*1ec284f3SChristian Brauner 	fd_lower = idmapped_layer_fd(lower, LAYER_HOST, ID_HOST, ID_RANGE);
405*1ec284f3SChristian Brauner 	ASSERT_GE(fd_lower, 0);
406*1ec284f3SChristian Brauner 
407*1ec284f3SChristian Brauner 	ovl = ovl_mount_lower_fd(upper, work, fd_lower);
408*1ec284f3SChristian Brauner 	ASSERT_GE(ovl, 0);
409*1ec284f3SChristian Brauner 	EXPECT_EQ(close(fd_lower), 0);
410*1ec284f3SChristian Brauner 
411*1ec284f3SChristian Brauner 	/* Idmap the overlay mount: overlay-final ID_HOST <-> caller ID_NS. */
412*1ec284f3SChristian Brauner 	ASSERT_EQ(ovl_idmap(ovl), 0);
413*1ec284f3SChristian Brauner 
414*1ec284f3SChristian Brauner 	ASSERT_EQ(fstatat(ovl, "file", &st, 0), 0);
415*1ec284f3SChristian Brauner 	EXPECT_EQ(st.st_uid, ID_NS + 7);
416*1ec284f3SChristian Brauner 	EXPECT_EQ(st.st_gid, ID_NS + 7);
417*1ec284f3SChristian Brauner 
418*1ec284f3SChristian Brauner 	EXPECT_EQ(close(ovl), 0);
419*1ec284f3SChristian Brauner }
420*1ec284f3SChristian Brauner 
421*1ec284f3SChristian Brauner /* An idmapped overlay mount whose sb lives inside a user namespace. */
422*1ec284f3SChristian Brauner TEST_F(idmapped_overlay, userns)
423*1ec284f3SChristian Brauner {
424*1ec284f3SChristian Brauner 	int u1;
425*1ec284f3SChristian Brauner 	pid_t pid;
426*1ec284f3SChristian Brauner 
427*1ec284f3SChristian Brauner 	if (!ovl_supported())
428*1ec284f3SChristian Brauner 		SKIP(return, "overlayfs not supported");
429*1ec284f3SChristian Brauner 
430*1ec284f3SChristian Brauner 	/* u1 backs the overlay sb: identity-mapped, but not the init ns. */
431*1ec284f3SChristian Brauner 	u1 = get_userns_fd(0, 0, 65536);
432*1ec284f3SChristian Brauner 	if (u1 < 0)
433*1ec284f3SChristian Brauner 		SKIP(return, "user namespaces not available");
434*1ec284f3SChristian Brauner 
435*1ec284f3SChristian Brauner 	pid = fork();
436*1ec284f3SChristian Brauner 	ASSERT_GE(pid, 0);
437*1ec284f3SChristian Brauner 	if (pid == 0) {
438*1ec284f3SChristian Brauner 		int ret = userns_overlay_child(u1);
439*1ec284f3SChristian Brauner 
440*1ec284f3SChristian Brauner 		_exit(ret ? EXIT_FAILURE : EXIT_SUCCESS);
441*1ec284f3SChristian Brauner 	}
442*1ec284f3SChristian Brauner 	EXPECT_EQ(wait_for_pid(pid), 0);
443*1ec284f3SChristian Brauner 
444*1ec284f3SChristian Brauner 	EXPECT_EQ(close(u1), 0);
445*1ec284f3SChristian Brauner }
446*1ec284f3SChristian Brauner 
447*1ec284f3SChristian Brauner /*
448*1ec284f3SChristian Brauner  * An nfs_export overlay can be idmapped, and decodable file handles round-trip
449*1ec284f3SChristian Brauner  * through the idmapped mount with correctly mapped ownership.  Overlay file
450*1ec284f3SChristian Brauner  * handles encode object identity, not ownership, so the mount idmap does not
451*1ec284f3SChristian Brauner  * affect them; it only maps the owner reported once a handle is reopened.
452*1ec284f3SChristian Brauner  */
453*1ec284f3SChristian Brauner TEST_F(idmapped_overlay, nfs_export_handles)
454*1ec284f3SChristian Brauner {
455*1ec284f3SChristian Brauner 	char path[PATH_MAX], mnt[128];
456*1ec284f3SChristian Brauner 	union {
457*1ec284f3SChristian Brauner 		struct file_handle fh;
458*1ec284f3SChristian Brauner 		char buf[sizeof(struct file_handle) + MAX_HANDLE_SZ];
459*1ec284f3SChristian Brauner 	} fhu;
460*1ec284f3SChristian Brauner 	struct file_handle *fh = &fhu.fh;
461*1ec284f3SChristian Brauner 	struct stat st;
462*1ec284f3SChristian Brauner 	int ovl, mfd, fd, mount_id;
463*1ec284f3SChristian Brauner 
464*1ec284f3SChristian Brauner 	if (!ovl_supported())
465*1ec284f3SChristian Brauner 		SKIP(return, "overlayfs not supported");
466*1ec284f3SChristian Brauner 
467*1ec284f3SChristian Brauner 	snprintf(path, sizeof(path), "%s/l/file", self->base);
468*1ec284f3SChristian Brauner 	ASSERT_EQ(mknod(path, S_IFREG | 0644, 0), 0);
469*1ec284f3SChristian Brauner 	ASSERT_EQ(chown(path, ID_HOST + 7, ID_HOST + 7), 0);
470*1ec284f3SChristian Brauner 
471*1ec284f3SChristian Brauner 	/* nfs_export=on gives decodable overlay file handles. */
472*1ec284f3SChristian Brauner 	ovl = ovl_mount(self->base, true);
473*1ec284f3SChristian Brauner 	if (ovl < 0)
474*1ec284f3SChristian Brauner 		SKIP(return, "overlayfs nfs_export not supported");
475*1ec284f3SChristian Brauner 	ASSERT_EQ(ovl_idmap(ovl), 0);
476*1ec284f3SChristian Brauner 
477*1ec284f3SChristian Brauner 	/* Attach the idmapped mount so handles can be resolved against it. */
478*1ec284f3SChristian Brauner 	snprintf(mnt, sizeof(mnt), "%s/mnt", self->base);
479*1ec284f3SChristian Brauner 	ASSERT_EQ(mkdir(mnt, 0755), 0);
480*1ec284f3SChristian Brauner 	ASSERT_EQ(sys_move_mount(ovl, "", AT_FDCWD, mnt,
481*1ec284f3SChristian Brauner 				 MOVE_MOUNT_F_EMPTY_PATH), 0);
482*1ec284f3SChristian Brauner 
483*1ec284f3SChristian Brauner 	snprintf(path, sizeof(path), "%s/file", mnt);
484*1ec284f3SChristian Brauner 	fh->handle_bytes = MAX_HANDLE_SZ;
485*1ec284f3SChristian Brauner 	ASSERT_EQ(name_to_handle_at(AT_FDCWD, path, fh, &mount_id, 0), 0);
486*1ec284f3SChristian Brauner 
487*1ec284f3SChristian Brauner 	mfd = open(mnt, O_RDONLY | O_DIRECTORY);
488*1ec284f3SChristian Brauner 	ASSERT_GE(mfd, 0);
489*1ec284f3SChristian Brauner 	fd = open_by_handle_at(mfd, fh, O_RDONLY);
490*1ec284f3SChristian Brauner 	EXPECT_EQ(close(mfd), 0);
491*1ec284f3SChristian Brauner 	ASSERT_GE(fd, 0);
492*1ec284f3SChristian Brauner 
493*1ec284f3SChristian Brauner 	ASSERT_EQ(fstat(fd, &st), 0);
494*1ec284f3SChristian Brauner 	EXPECT_EQ(st.st_uid, ID_NS + 7);
495*1ec284f3SChristian Brauner 	EXPECT_EQ(st.st_gid, ID_NS + 7);
496*1ec284f3SChristian Brauner 
497*1ec284f3SChristian Brauner 	EXPECT_EQ(close(fd), 0);
498*1ec284f3SChristian Brauner 	EXPECT_EQ(close(ovl), 0);
499*1ec284f3SChristian Brauner }
500*1ec284f3SChristian Brauner 
501*1ec284f3SChristian Brauner TEST_HARNESS_MAIN
502