xref: /linux/tools/testing/selftests/exec/binfmt_misc_interplimit.c (revision b9cba7ebfe539f3e4bbdd03a1e0efa3b30b3f592)
1*f2b69ea2SChristian Brauner // SPDX-License-Identifier: GPL-2.0
2*f2b69ea2SChristian Brauner /*
3*f2b69ea2SChristian Brauner  * A pre-opened interpreter - what 'F' gives a static entry and what a 'B'
4*f2b69ea2SChristian Brauner  * entry binds - keeps a file open for as long as the entry lives, so it pins
5*f2b69ea2SChristian Brauner  * the mount it came from. It costs no file descriptor, and binfmt_misc is
6*f2b69ea2SChristian Brauner  * FS_USERNS_MOUNT, so an unprivileged user namespace can create them without
7*f2b69ea2SChristian Brauner  * bound. Check that UCOUNT_BINFMT_MISC_INTERPRETERS bounds it, that an entry
8*f2b69ea2SChristian Brauner  * that pre-opens nothing is not charged, that removing an entry gives the
9*f2b69ea2SChristian Brauner  * charge back, and that nesting a user namespace does not evade it.
10*f2b69ea2SChristian Brauner  *
11*f2b69ea2SChristian Brauner  * Runs unprivileged in a user namespace.
12*f2b69ea2SChristian Brauner  */
13*f2b69ea2SChristian Brauner #define _GNU_SOURCE
14*f2b69ea2SChristian Brauner #include <errno.h>
15*f2b69ea2SChristian Brauner #include <fcntl.h>
16*f2b69ea2SChristian Brauner #include <limits.h>
17*f2b69ea2SChristian Brauner #include <stdio.h>
18*f2b69ea2SChristian Brauner #include <string.h>
19*f2b69ea2SChristian Brauner #include <sys/mount.h>
20*f2b69ea2SChristian Brauner #include <sys/stat.h>
21*f2b69ea2SChristian Brauner #include <unistd.h>
22*f2b69ea2SChristian Brauner 
23*f2b69ea2SChristian Brauner #include "../filesystems/utils.h"
24*f2b69ea2SChristian Brauner #include "kselftest_harness.h"
25*f2b69ea2SChristian Brauner 
26*f2b69ea2SChristian Brauner #define MNT		"/tmp/binfmt_interplimit"
27*f2b69ea2SChristian Brauner #define NESTED_MNT	"/tmp/binfmt_interplimit_nested"
28*f2b69ea2SChristian Brauner #define LIMIT_SYSCTL	"/proc/sys/user/max_binfmt_misc_interpreters"
29*f2b69ea2SChristian Brauner 
30*f2b69ea2SChristian Brauner #define MAGIC		"\\xde\\xad"
31*f2b69ea2SChristian Brauner /* Not on the instance, and unlike /bin/true it always exists. */
32*f2b69ea2SChristian Brauner #define INTERP		"/proc/self/exe"
33*f2b69ea2SChristian Brauner 
34*f2b69ea2SChristian Brauner /* Small enough to fill by hand, big enough that a refund is visible. */
35*f2b69ea2SChristian Brauner #define LIMIT		4
36*f2b69ea2SChristian Brauner 
37*f2b69ea2SChristian Brauner /* What UCOUNT_ENTRY() lets a namespace raise its own limit to. */
38*f2b69ea2SChristian Brauner #define LIMIT_MAX	"2147483647"
39*f2b69ea2SChristian Brauner 
40*f2b69ea2SChristian Brauner static int ensure_dir(const char *path)
41*f2b69ea2SChristian Brauner {
42*f2b69ea2SChristian Brauner 	if (mkdir(path, 0755) && errno != EEXIST)
43*f2b69ea2SChristian Brauner 		return -1;
44*f2b69ea2SChristian Brauner 	return 0;
45*f2b69ea2SChristian Brauner }
46*f2b69ea2SChristian Brauner 
47*f2b69ea2SChristian Brauner /* Write @val to @path, preserving write(2)'s errno for the caller. */
48*f2b69ea2SChristian Brauner static int write_keep_errno(const char *path, const char *val)
49*f2b69ea2SChristian Brauner {
50*f2b69ea2SChristian Brauner 	int fd, saved;
51*f2b69ea2SChristian Brauner 	ssize_t n;
52*f2b69ea2SChristian Brauner 
53*f2b69ea2SChristian Brauner 	fd = open(path, O_WRONLY | O_CLOEXEC);
54*f2b69ea2SChristian Brauner 	if (fd < 0)
55*f2b69ea2SChristian Brauner 		return -1;
56*f2b69ea2SChristian Brauner 	n = write(fd, val, strlen(val));
57*f2b69ea2SChristian Brauner 	saved = errno;
58*f2b69ea2SChristian Brauner 	close(fd);
59*f2b69ea2SChristian Brauner 	errno = saved;
60*f2b69ea2SChristian Brauner 	return n < 0 ? -1 : 0;
61*f2b69ea2SChristian Brauner }
62*f2b69ea2SChristian Brauner 
63*f2b69ea2SChristian Brauner static int set_limit(const char *val)
64*f2b69ea2SChristian Brauner {
65*f2b69ea2SChristian Brauner 	return write_keep_errno(LIMIT_SYSCTL, val);
66*f2b69ea2SChristian Brauner }
67*f2b69ea2SChristian Brauner 
68*f2b69ea2SChristian Brauner static int register_at(const char *mnt, const char *rule)
69*f2b69ea2SChristian Brauner {
70*f2b69ea2SChristian Brauner 	char path[PATH_MAX];
71*f2b69ea2SChristian Brauner 
72*f2b69ea2SChristian Brauner 	snprintf(path, sizeof(path), "%s/register", mnt);
73*f2b69ea2SChristian Brauner 	return write_keep_errno(path, rule);
74*f2b69ea2SChristian Brauner }
75*f2b69ea2SChristian Brauner 
76*f2b69ea2SChristian Brauner /* An 'F' entry: one interpreter pre-opened at registration, one charge. */
77*f2b69ea2SChristian Brauner static int register_fixed(const char *mnt, const char *name)
78*f2b69ea2SChristian Brauner {
79*f2b69ea2SChristian Brauner 	char rule[PATH_MAX];
80*f2b69ea2SChristian Brauner 
81*f2b69ea2SChristian Brauner 	snprintf(rule, sizeof(rule), ":%s:M::" MAGIC "::" INTERP ":F", name);
82*f2b69ea2SChristian Brauner 	return register_at(mnt, rule);
83*f2b69ea2SChristian Brauner }
84*f2b69ea2SChristian Brauner 
85*f2b69ea2SChristian Brauner /* The same entry without 'F': the interpreter is opened per exec instead. */
86*f2b69ea2SChristian Brauner static int register_plain(const char *mnt, const char *name)
87*f2b69ea2SChristian Brauner {
88*f2b69ea2SChristian Brauner 	char rule[PATH_MAX];
89*f2b69ea2SChristian Brauner 
90*f2b69ea2SChristian Brauner 	snprintf(rule, sizeof(rule), ":%s:M::" MAGIC "::" INTERP ":", name);
91*f2b69ea2SChristian Brauner 	return register_at(mnt, rule);
92*f2b69ea2SChristian Brauner }
93*f2b69ea2SChristian Brauner 
94*f2b69ea2SChristian Brauner static int remove_entry(const char *mnt, const char *name)
95*f2b69ea2SChristian Brauner {
96*f2b69ea2SChristian Brauner 	char path[PATH_MAX];
97*f2b69ea2SChristian Brauner 
98*f2b69ea2SChristian Brauner 	snprintf(path, sizeof(path), "%s/%s", mnt, name);
99*f2b69ea2SChristian Brauner 	return write_keep_errno(path, "-1\n");
100*f2b69ea2SChristian Brauner }
101*f2b69ea2SChristian Brauner 
102*f2b69ea2SChristian Brauner static bool entry_exists(const char *mnt, const char *name)
103*f2b69ea2SChristian Brauner {
104*f2b69ea2SChristian Brauner 	char path[PATH_MAX];
105*f2b69ea2SChristian Brauner 
106*f2b69ea2SChristian Brauner 	snprintf(path, sizeof(path), "%s/%s", mnt, name);
107*f2b69ea2SChristian Brauner 	return access(path, F_OK) == 0;
108*f2b69ea2SChristian Brauner }
109*f2b69ea2SChristian Brauner 
110*f2b69ea2SChristian Brauner /* Register @n 'F' entries, each with a name of its own. */
111*f2b69ea2SChristian Brauner static int fill_budget(const char *mnt, unsigned int n)
112*f2b69ea2SChristian Brauner {
113*f2b69ea2SChristian Brauner 	char name[32];
114*f2b69ea2SChristian Brauner 	unsigned int i;
115*f2b69ea2SChristian Brauner 
116*f2b69ea2SChristian Brauner 	for (i = 0; i < n; i++) {
117*f2b69ea2SChristian Brauner 		snprintf(name, sizeof(name), "fixed%u", i);
118*f2b69ea2SChristian Brauner 		if (register_fixed(mnt, name))
119*f2b69ea2SChristian Brauner 			return -1;
120*f2b69ea2SChristian Brauner 	}
121*f2b69ea2SChristian Brauner 	return 0;
122*f2b69ea2SChristian Brauner }
123*f2b69ea2SChristian Brauner 
124*f2b69ea2SChristian Brauner FIXTURE(interp_limit) {
125*f2b69ea2SChristian Brauner };
126*f2b69ea2SChristian Brauner 
127*f2b69ea2SChristian Brauner FIXTURE_SETUP(interp_limit)
128*f2b69ea2SChristian Brauner {
129*f2b69ea2SChristian Brauner 	/* setup_userns() exits rather than returns if this is not there. */
130*f2b69ea2SChristian Brauner 	if (access("/proc/self/ns/user", F_OK))
131*f2b69ea2SChristian Brauner 		SKIP(return, "kernel without user namespaces");
132*f2b69ea2SChristian Brauner 	ASSERT_EQ(setup_userns(), 0);
133*f2b69ea2SChristian Brauner 
134*f2b69ea2SChristian Brauner 	/* CAP_SYS_RESOURCE in this namespace is what makes it writable. */
135*f2b69ea2SChristian Brauner 	if (set_limit(LIMIT_MAX)) {
136*f2b69ea2SChristian Brauner 		if (errno == ENOENT)
137*f2b69ea2SChristian Brauner 			SKIP(return, "kernel without " LIMIT_SYSCTL);
138*f2b69ea2SChristian Brauner 		SKIP(return, "cannot set the limit: %s", strerror(errno));
139*f2b69ea2SChristian Brauner 	}
140*f2b69ea2SChristian Brauner 
141*f2b69ea2SChristian Brauner 	ASSERT_EQ(ensure_dir(MNT), 0);
142*f2b69ea2SChristian Brauner 	if (mount("binfmt_misc", MNT, "binfmt_misc", 0, NULL)) {
143*f2b69ea2SChristian Brauner 		int saved = errno;
144*f2b69ea2SChristian Brauner 
145*f2b69ea2SChristian Brauner 		/* Teardown doesn't run when setup skips, so clean up here. */
146*f2b69ea2SChristian Brauner 		rmdir(MNT);
147*f2b69ea2SChristian Brauner 		SKIP(return, "no binfmt_misc: %s", strerror(saved));
148*f2b69ea2SChristian Brauner 	}
149*f2b69ea2SChristian Brauner }
150*f2b69ea2SChristian Brauner 
151*f2b69ea2SChristian Brauner FIXTURE_TEARDOWN(interp_limit)
152*f2b69ea2SChristian Brauner {
153*f2b69ea2SChristian Brauner 	/* The namespaces go with the process; just don't litter /tmp. */
154*f2b69ea2SChristian Brauner 	umount2(NESTED_MNT, MNT_DETACH);
155*f2b69ea2SChristian Brauner 	umount2(MNT, MNT_DETACH);
156*f2b69ea2SChristian Brauner 	rmdir(NESTED_MNT);
157*f2b69ea2SChristian Brauner 	rmdir(MNT);
158*f2b69ea2SChristian Brauner }
159*f2b69ea2SChristian Brauner 
160*f2b69ea2SChristian Brauner /* Every pre-opened interpreter is charged, and the budget is a hard stop. */
161*f2b69ea2SChristian Brauner TEST_F(interp_limit, fixed_interpreters_are_charged)
162*f2b69ea2SChristian Brauner {
163*f2b69ea2SChristian Brauner 	char buf[32];
164*f2b69ea2SChristian Brauner 
165*f2b69ea2SChristian Brauner 	snprintf(buf, sizeof(buf), "%u", LIMIT);
166*f2b69ea2SChristian Brauner 	ASSERT_EQ(set_limit(buf), 0);
167*f2b69ea2SChristian Brauner 
168*f2b69ea2SChristian Brauner 	ASSERT_EQ(fill_budget(MNT, LIMIT), 0);
169*f2b69ea2SChristian Brauner 
170*f2b69ea2SChristian Brauner 	EXPECT_NE(register_fixed(MNT, "over"), 0);
171*f2b69ea2SChristian Brauner 	EXPECT_EQ(errno, ENOSPC);
172*f2b69ea2SChristian Brauner 
173*f2b69ea2SChristian Brauner 	/* A refused registration leaves nothing behind. */
174*f2b69ea2SChristian Brauner 	EXPECT_FALSE(entry_exists(MNT, "over"));
175*f2b69ea2SChristian Brauner }
176*f2b69ea2SChristian Brauner 
177*f2b69ea2SChristian Brauner /* An entry that pre-opens nothing pins nothing, so it is not charged. */
178*f2b69ea2SChristian Brauner TEST_F(interp_limit, plain_entries_are_not_charged)
179*f2b69ea2SChristian Brauner {
180*f2b69ea2SChristian Brauner 	ASSERT_EQ(set_limit("0"), 0);
181*f2b69ea2SChristian Brauner 
182*f2b69ea2SChristian Brauner 	EXPECT_EQ(register_plain(MNT, "plain"), 0);
183*f2b69ea2SChristian Brauner 	EXPECT_TRUE(entry_exists(MNT, "plain"));
184*f2b69ea2SChristian Brauner 
185*f2b69ea2SChristian Brauner 	/* ... while the same entry with 'F' has nothing to spend. */
186*f2b69ea2SChristian Brauner 	EXPECT_NE(register_fixed(MNT, "fixed"), 0);
187*f2b69ea2SChristian Brauner 	EXPECT_EQ(errno, ENOSPC);
188*f2b69ea2SChristian Brauner }
189*f2b69ea2SChristian Brauner 
190*f2b69ea2SChristian Brauner /* Removing an entry closes its interpreters and gives the charge back. */
191*f2b69ea2SChristian Brauner TEST_F(interp_limit, removal_refunds_the_charge)
192*f2b69ea2SChristian Brauner {
193*f2b69ea2SChristian Brauner 	char buf[32];
194*f2b69ea2SChristian Brauner 
195*f2b69ea2SChristian Brauner 	snprintf(buf, sizeof(buf), "%u", LIMIT);
196*f2b69ea2SChristian Brauner 	ASSERT_EQ(set_limit(buf), 0);
197*f2b69ea2SChristian Brauner 
198*f2b69ea2SChristian Brauner 	ASSERT_EQ(fill_budget(MNT, LIMIT), 0);
199*f2b69ea2SChristian Brauner 	ASSERT_NE(register_fixed(MNT, "over"), 0);
200*f2b69ea2SChristian Brauner 
201*f2b69ea2SChristian Brauner 	ASSERT_EQ(remove_entry(MNT, "fixed0"), 0);
202*f2b69ea2SChristian Brauner 	EXPECT_EQ(register_fixed(MNT, "over"), 0);
203*f2b69ea2SChristian Brauner }
204*f2b69ea2SChristian Brauner 
205*f2b69ea2SChristian Brauner /*
206*f2b69ea2SChristian Brauner  * The charge walks the ancestors, so a namespace cannot buy itself budget by
207*f2b69ea2SChristian Brauner  * nesting: it may raise only its own limit, and the parent it was created
208*f2b69ea2SChristian Brauner  * from is charged for every binding made below it.
209*f2b69ea2SChristian Brauner  */
210*f2b69ea2SChristian Brauner TEST_F(interp_limit, nesting_does_not_evade_it)
211*f2b69ea2SChristian Brauner {
212*f2b69ea2SChristian Brauner 	char buf[32];
213*f2b69ea2SChristian Brauner 
214*f2b69ea2SChristian Brauner 	snprintf(buf, sizeof(buf), "%u", LIMIT);
215*f2b69ea2SChristian Brauner 	ASSERT_EQ(set_limit(buf), 0);
216*f2b69ea2SChristian Brauner 	ASSERT_EQ(fill_budget(MNT, LIMIT), 0);
217*f2b69ea2SChristian Brauner 
218*f2b69ea2SChristian Brauner 	ASSERT_EQ(setup_userns(), 0);
219*f2b69ea2SChristian Brauner 	ASSERT_EQ(set_limit(LIMIT_MAX), 0);
220*f2b69ea2SChristian Brauner 
221*f2b69ea2SChristian Brauner 	ASSERT_EQ(ensure_dir(NESTED_MNT), 0);
222*f2b69ea2SChristian Brauner 	ASSERT_EQ(mount("binfmt_misc", NESTED_MNT, "binfmt_misc", 0, NULL), 0);
223*f2b69ea2SChristian Brauner 
224*f2b69ea2SChristian Brauner 	/* A fresh instance with an unlimited budget of its own, and yet: */
225*f2b69ea2SChristian Brauner 	EXPECT_NE(register_fixed(NESTED_MNT, "nested"), 0);
226*f2b69ea2SChristian Brauner 	EXPECT_EQ(errno, ENOSPC);
227*f2b69ea2SChristian Brauner 
228*f2b69ea2SChristian Brauner 	/* The nested instance works for anything that pins no file. */
229*f2b69ea2SChristian Brauner 	EXPECT_EQ(register_plain(NESTED_MNT, "nested_plain"), 0);
230*f2b69ea2SChristian Brauner }
231*f2b69ea2SChristian Brauner 
232*f2b69ea2SChristian Brauner TEST_HARNESS_MAIN
233