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