xref: /linux/tools/testing/selftests/exec/binfmt_misc_loader.c (revision b9cba7ebfe539f3e4bbdd03a1e0efa3b30b3f592)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Test the 'L' (loader substitution) flag of binfmt_misc. A matched
4  * binary runs as the MAIN image - a fully native exec - with the
5  * registered interpreter substituted for its PT_INTERP. The payload
6  * (binfmt_loader_payload) asserts the native identity from inside.
7  *
8  * The substitute is a copy of the system loader found via our own
9  * PT_INTERP; magic matching pokes a marker into the ELF header's
10  * e_ident padding, which kernel and loader ignore.
11  *
12  * Needs root for the registration; no bpf toolchain involved.
13  */
14 #define _GNU_SOURCE
15 #include <elf.h>
16 #include <link.h>
17 #include <signal.h>
18 #include <stddef.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <sys/mman.h>
22 #include <sys/ptrace.h>
23 #include <sys/syscall.h>
24 #include <sys/wait.h>
25 
26 #include "binfmt_misc_common.h"
27 #include "kselftest_harness.h"
28 
29 #define ENTRY		"test_loader"
30 #define INTERP_PATH	"/tmp/binfmt_loader_interp"
31 #define MOVED_PATH	INTERP_PATH ".moved"
32 #define TARGET_PATH	"/tmp/binfmt_loader_target.ldrtest"
33 #define STATIC_PATH	"/tmp/binfmt_loader_static.ldrtest"
34 #define FOREIGN_PATH	"/tmp/binfmt_loader_foreign.ldrtest"
35 #define SCRIPT_PATH	"/tmp/binfmt_loader_script.ldrtest"
36 #define M_RULE		":" ENTRY ":M:9:" LOADER_MARKER "::" INTERP_PATH ":L"
37 #define E_RULE		":" ENTRY ":E::ldrtest::" INTERP_PATH ":L"
38 #define FL_RULE		":" ENTRY ":E::ldrtest::" INTERP_PATH ":FL"
39 
40 /* Execute the binary from an inaccessible O_CLOEXEC memfd. */
41 static int run_memfd(const char *path)
42 {
43 	int status;
44 	pid_t pid;
45 
46 	pid = fork();
47 	if (pid == 0) {
48 		char *argv[] = { PAYLOAD_ARGV0, PAYLOAD_ARG1, PAYLOAD_ARG2, NULL };
49 		char buf[4096];
50 		int in, mfd;
51 		ssize_t n;
52 
53 		mfd = memfd_create("loader-test", MFD_CLOEXEC);
54 		in = open(path, O_RDONLY);
55 		if (mfd < 0 || in < 0)
56 			_exit(125);
57 		while ((n = read(in, buf, sizeof(buf))) > 0)
58 			if (write(mfd, buf, n) != n)
59 				_exit(125);
60 		close(in);
61 		setenv("BINFMT_TEST_MEMFD", "1", 1);
62 		unsetenv("BINFMT_TEST_BINARY");
63 		syscall(SYS_execveat, mfd, "", argv, environ, AT_EMPTY_PATH);
64 		_exit(126);
65 	}
66 	if (pid < 0 || waitpid(pid, &status, 0) != pid || !WIFEXITED(status))
67 		return -1;
68 	return WEXITSTATUS(status);
69 }
70 
71 /*
72  * The differentiator against the transparent mode: at PTRACE_EVENT_EXEC
73  * the identity is already complete - exe, auxv and the stat code markers
74  * are mutually consistent with no window a debugger could observe.
75  */
76 static int ptrace_probe(const char *target)
77 {
78 	unsigned long auxv[2 * 64], base = 0, entry = 0, at_flags = 0;
79 	unsigned long start_code = 0, end_code = 0;
80 	int status, fd, execfd_seen = 0, failed = 0;
81 	char path[64], buf[PATH_MAX];
82 	ssize_t n;
83 	pid_t pid;
84 	int i;
85 
86 	pid = fork();
87 	if (pid == 0) {
88 		ptrace(PTRACE_TRACEME, 0, NULL, NULL);
89 		raise(SIGSTOP);
90 		execl(target, PAYLOAD_ARGV0, PAYLOAD_ARG1, PAYLOAD_ARG2, (char *)NULL);
91 		_exit(126);
92 	}
93 	if (pid < 0)
94 		return -1;
95 	if (waitpid(pid, &status, 0) != pid || !WIFSTOPPED(status))
96 		goto fail_kill;
97 	if (ptrace(PTRACE_SETOPTIONS, pid, NULL, (void *)PTRACE_O_TRACEEXEC))
98 		goto fail_kill;
99 	if (ptrace(PTRACE_CONT, pid, NULL, NULL))
100 		goto fail_kill;
101 	if (waitpid(pid, &status, 0) != pid || !WIFSTOPPED(status) ||
102 	    status >> 8 != (SIGTRAP | (PTRACE_EVENT_EXEC << 8))) {
103 		fprintf(stderr, "no exec stop (status %#x)\n", status);
104 		goto fail_kill;
105 	}
106 
107 	snprintf(path, sizeof(path), "/proc/%d/exe", pid);
108 	n = readlink(path, buf, sizeof(buf) - 1);
109 	if (n <= 0) {
110 		failed = 1;
111 	} else {
112 		buf[n] = '\0';
113 		if (strcmp(buf, target)) {
114 			fprintf(stderr, "exe at exec stop: %s\n", buf);
115 			failed = 1;
116 		}
117 	}
118 
119 	snprintf(path, sizeof(path), "/proc/%d/auxv", pid);
120 	fd = open(path, O_RDONLY);
121 	if (fd < 0) {
122 		n = -1;
123 	} else {
124 		n = read(fd, auxv, sizeof(auxv));
125 		close(fd);
126 	}
127 	if (n <= 0) {
128 		failed = 1;
129 		n = 0;
130 	}
131 	for (i = 0; i + 1 < (int)(n / sizeof(unsigned long)); i += 2) {
132 		switch (auxv[i]) {
133 		case AT_BASE:
134 			base = auxv[i + 1];
135 			break;
136 		case AT_ENTRY:
137 			entry = auxv[i + 1];
138 			break;
139 		case AT_FLAGS:
140 			at_flags = auxv[i + 1];
141 			break;
142 		case AT_EXECFD:
143 			execfd_seen = 1;
144 			break;
145 		}
146 	}
147 
148 	if (stat_codes(pid, &start_code, &end_code))
149 		failed = 1;
150 
151 	if (!base || execfd_seen || at_flags) {
152 		fprintf(stderr, "auxv at exec stop not native\n");
153 		failed = 1;
154 	}
155 	if (!start_code || entry < start_code || entry >= end_code) {
156 		fprintf(stderr, "auxv/stat inconsistent at exec stop\n");
157 		failed = 1;
158 	}
159 
160 	if (ptrace(PTRACE_CONT, pid, NULL, NULL))
161 		goto fail_kill;
162 	if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status) ||
163 	    WEXITSTATUS(status))
164 		failed = 1;
165 	return failed ? -1 : 0;
166 
167 fail_kill:
168 	kill(pid, SIGKILL);
169 	waitpid(pid, &status, 0);
170 	return -1;
171 }
172 
173 FIXTURE(loader) {
174 	bool have_static;
175 };
176 
177 FIXTURE_SETUP(loader)
178 {
179 	unsigned short foreign_machine = 0xdead;
180 	char src[PATH_MAX], loader[PATH_MAX];
181 
182 	if (getuid() != 0)
183 		SKIP(return, "test must be run as root");
184 	if (!binfmt_misc_available())
185 		SKIP(return, "no binfmt_misc");
186 	if (find_loader(loader, sizeof(loader)))
187 		SKIP(return, "cannot determine own PT_INTERP");
188 
189 	ASSERT_EQ(copy_file(loader, INTERP_PATH), 0);
190 
191 	ASSERT_EQ(artifact_path(src, sizeof(src), "binfmt_loader_payload"), 0);
192 	ASSERT_EQ(copy_file(src, TARGET_PATH), 0);
193 	ASSERT_EQ(patch_file(TARGET_PATH, EI_PAD, LOADER_MARKER,
194 			     strlen(LOADER_MARKER)), 0);
195 
196 	/* The same payload with a machine type this kernel cannot load. */
197 	ASSERT_EQ(copy_file(src, FOREIGN_PATH), 0);
198 	ASSERT_EQ(patch_file(FOREIGN_PATH, EI_PAD, LOADER_MARKER,
199 			     strlen(LOADER_MARKER)), 0);
200 	ASSERT_EQ(patch_file(FOREIGN_PATH, offsetof(ElfW(Ehdr), e_machine),
201 			     &foreign_machine, sizeof(foreign_machine)), 0);
202 
203 	self->have_static =
204 		artifact_path(src, sizeof(src), "binfmt_loader_payload_static") == 0 &&
205 		copy_file(src, STATIC_PATH) == 0;
206 
207 	setenv("BINFMT_TEST_BINARY", TARGET_PATH, 1);
208 	setenv("BINFMT_TEST_INTERP", INTERP_PATH, 1);
209 
210 	/* Everything below needs the flag; find out once. */
211 	if (write_reg(E_RULE)) {
212 		ASSERT_EQ(errno, EINVAL);
213 		SKIP(return, "kernel without the 'L' flag");
214 	}
215 	unregister(ENTRY);
216 }
217 
218 FIXTURE_TEARDOWN(loader)
219 {
220 	unregister(ENTRY);
221 	if (access(MOVED_PATH, F_OK) == 0)
222 		rename(MOVED_PATH, INTERP_PATH);
223 	unlink(TARGET_PATH);
224 	unlink(STATIC_PATH);
225 	unlink(FOREIGN_PATH);
226 	unlink(SCRIPT_PATH);
227 	unlink(INTERP_PATH);
228 }
229 
230 /* Grammar sanity check: the same entry without 'L' has to register. */
231 TEST_F(loader, plain_entry_registers)
232 {
233 	ASSERT_EQ(write_reg(":" ENTRY ":E::ldrtest::" INTERP_PATH ":"), 0);
234 }
235 
236 /* 'L' is a native exec: every classic-dispatch flag is rejected. */
237 TEST_F(loader, rejects_classic_flags)
238 {
239 	static const char * const combos[] = { "LT", "LP", "LC", "LO" };
240 	char rule[PATH_MAX];
241 	unsigned int i;
242 
243 	for (i = 0; i < ARRAY_SIZE(combos); i++) {
244 		int rc;
245 
246 		snprintf(rule, sizeof(rule),
247 			 ":" ENTRY ":E::ldrtest::" INTERP_PATH ":%s", combos[i]);
248 		rc = write_reg(rule);
249 		EXPECT_EQ(rc, -1)
250 			TH_LOG("'%s' was not rejected", combos[i]);
251 		if (rc == 0) {
252 			unregister(ENTRY);
253 			continue;
254 		}
255 		EXPECT_EQ(errno, EINVAL);
256 	}
257 }
258 
259 /*
260  * Without 'F' the interpreter is opened when the binary is executed, so a
261  * relative path would be resolved against the caller's working directory.
262  */
263 TEST_F(loader, rejects_relative_interpreter)
264 {
265 	static const char * const flags[] = { "L", "C" };
266 	char rule[PATH_MAX];
267 	unsigned int i;
268 
269 	for (i = 0; i < ARRAY_SIZE(flags); i++) {
270 		int rc;
271 
272 		snprintf(rule, sizeof(rule),
273 			 ":" ENTRY ":E::ldrtest::binfmt_loader_interp:%s",
274 			 flags[i]);
275 		rc = write_reg(rule);
276 		EXPECT_EQ(rc, -1)
277 			TH_LOG("'%s' accepted a relative interpreter", flags[i]);
278 		if (rc == 0) {
279 			unregister(ENTRY);
280 			continue;
281 		}
282 		EXPECT_EQ(errno, EINVAL);
283 	}
284 }
285 
286 TEST_F(loader, extension_matched)
287 {
288 	ASSERT_EQ(write_reg(E_RULE), 0);
289 	EXPECT_EQ(run_payload(TARGET_PATH), 0);
290 }
291 
292 TEST_F(loader, magic_matched)
293 {
294 	ASSERT_EQ(write_reg(M_RULE), 0);
295 	EXPECT_EQ(run_payload(TARGET_PATH), 0);
296 }
297 
298 /*
299  * The differentiator against the transparent mode: at PTRACE_EVENT_EXEC the
300  * identity is already complete, with no window a debugger could observe.
301  */
302 TEST_F(loader, exec_stop_consistency)
303 {
304 	ASSERT_EQ(write_reg(E_RULE), 0);
305 	EXPECT_EQ(ptrace_probe(TARGET_PATH), 0);
306 }
307 
308 /* A binary without PT_INTERP drops the override and runs natively. */
309 TEST_F(loader, static_binary_runs_natively)
310 {
311 	if (!self->have_static)
312 		SKIP(return, "no static payload built");
313 
314 	ASSERT_EQ(write_reg(E_RULE), 0);
315 	setenv("BINFMT_TEST_BINARY", STATIC_PATH, 1);
316 	setenv("BINFMT_TEST_STATIC", "1", 1);
317 	EXPECT_EQ(run_payload(STATIC_PATH), 0);
318 	unsetenv("BINFMT_TEST_STATIC");
319 	setenv("BINFMT_TEST_BINARY", TARGET_PATH, 1);
320 }
321 
322 /*
323  * A '#!' file that matched an 'L' entry is claimed by binfmt_script, which
324  * sits ahead of binfmt_elf. The substitute the entry staged has to be
325  * released when the interpreter replaces the file, not leaked.
326  */
327 TEST_F(loader, script_claims_the_file)
328 {
329 	static const char script[] = "#!/bin/sh\nexit 0\n";
330 	int fd;
331 
332 	unlink(SCRIPT_PATH);
333 	fd = open(SCRIPT_PATH, O_WRONLY | O_CREAT | O_EXCL, 0755);
334 	ASSERT_GE(fd, 0);
335 	ASSERT_EQ(write(fd, script, sizeof(script) - 1),
336 		  (ssize_t)sizeof(script) - 1);
337 	ASSERT_EQ(close(fd), 0);
338 
339 	ASSERT_EQ(write_reg(E_RULE), 0);
340 	EXPECT_EQ(run_payload(SCRIPT_PATH), 0);
341 
342 	/* A leaked substitute keeps its write denial on the loader. */
343 	fd = open(INTERP_PATH, O_WRONLY);
344 	EXPECT_GE(fd, 0)
345 		TH_LOG("loader still write denied (errno %d)", errno);
346 	if (fd >= 0)
347 		close(fd);
348 }
349 
350 /* Nothing needs the binary's path, so an inaccessible fd works. */
351 TEST_F(loader, inaccessible_memfd)
352 {
353 	ASSERT_EQ(write_reg(M_RULE), 0);
354 	EXPECT_EQ(run_memfd(TARGET_PATH), 0);
355 }
356 
357 /* The whole exec of a wrong-arch binary fails as if unhandled. */
358 TEST_F(loader, foreign_arch_enoexec)
359 {
360 	ASSERT_EQ(write_reg(M_RULE), 0);
361 	EXPECT_EQ(run_payload(FOREIGN_PATH), RUN_ENOEXEC);
362 }
363 
364 /* 'F' pre-opens the substitute, so it survives losing its path. */
365 TEST_F(loader, fixed_interpreter_survives_rename)
366 {
367 	ASSERT_EQ(write_reg(FL_RULE), 0);
368 	ASSERT_EQ(rename(INTERP_PATH, MOVED_PATH), 0);
369 	EXPECT_EQ(run_payload(TARGET_PATH), 0);
370 }
371 
372 TEST_HARNESS_MAIN
373