xref: /linux/tools/testing/selftests/exec/binfmt_loader_payload.c (revision b9cba7ebfe539f3e4bbdd03a1e0efa3b30b3f592)
1*87c50a58SChristian Brauner // SPDX-License-Identifier: GPL-2.0
2*87c50a58SChristian Brauner /*
3*87c50a58SChristian Brauner  * Payload for the binfmt_misc 'L' (loader substitution) selftest. It is
4*87c50a58SChristian Brauner  * executed as the MAIN image - a fully native exec - with the registered
5*87c50a58SChristian Brauner  * interpreter substituted for its PT_INTERP, and asserts the native
6*87c50a58SChristian Brauner  * identity from the inside. Exits 0 when every surface checks out.
7*87c50a58SChristian Brauner  *
8*87c50a58SChristian Brauner  * Modes, selected by the orchestrator via the environment:
9*87c50a58SChristian Brauner  *  - default:                full assertions, path-based ones included
10*87c50a58SChristian Brauner  *  - BINFMT_TEST_MEMFD=1:    executed from an inaccessible memfd, skip
11*87c50a58SChristian Brauner  *                            the path-based assertions
12*87c50a58SChristian Brauner  *  - BINFMT_TEST_STATIC=1:   static build; the override was dropped, so
13*87c50a58SChristian Brauner  *                            expect no interpreter at all
14*87c50a58SChristian Brauner  */
15*87c50a58SChristian Brauner #define _GNU_SOURCE
16*87c50a58SChristian Brauner #include <elf.h>
17*87c50a58SChristian Brauner #include <errno.h>
18*87c50a58SChristian Brauner #include <fcntl.h>
19*87c50a58SChristian Brauner #include <limits.h>
20*87c50a58SChristian Brauner #include <stdio.h>
21*87c50a58SChristian Brauner #include <stdlib.h>
22*87c50a58SChristian Brauner #include <string.h>
23*87c50a58SChristian Brauner #include <sys/auxv.h>
24*87c50a58SChristian Brauner #include <unistd.h>
25*87c50a58SChristian Brauner 
26*87c50a58SChristian Brauner #include "binfmt_misc_common.h"
27*87c50a58SChristian Brauner 
28*87c50a58SChristian Brauner /* Start of our own mapped image, courtesy of the linker. */
29*87c50a58SChristian Brauner extern const char __ehdr_start[];
30*87c50a58SChristian Brauner 
31*87c50a58SChristian Brauner /* An image is never this large; used to bracket "within our image". */
32*87c50a58SChristian Brauner #define IMAGE_SPAN (16UL << 20)
33*87c50a58SChristian Brauner 
34*87c50a58SChristian Brauner static int failed;
35*87c50a58SChristian Brauner 
36*87c50a58SChristian Brauner static void check(int cond, const char *what)
37*87c50a58SChristian Brauner {
38*87c50a58SChristian Brauner 	if (cond)
39*87c50a58SChristian Brauner 		return;
40*87c50a58SChristian Brauner 	fprintf(stderr, "[payload] FAILED: %s (errno %d)\n", what, errno);
41*87c50a58SChristian Brauner 	failed = 1;
42*87c50a58SChristian Brauner }
43*87c50a58SChristian Brauner 
44*87c50a58SChristian Brauner /* Return whether /proc/self/maps names a path starting with @prefix. */
45*87c50a58SChristian Brauner static int maps_has_prefix(const char *prefix)
46*87c50a58SChristian Brauner {
47*87c50a58SChristian Brauner 	char *line = NULL;
48*87c50a58SChristian Brauner 	size_t len = 0;
49*87c50a58SChristian Brauner 	int found = 0;
50*87c50a58SChristian Brauner 	FILE *f;
51*87c50a58SChristian Brauner 
52*87c50a58SChristian Brauner 	f = fopen("/proc/self/maps", "r");
53*87c50a58SChristian Brauner 	if (!f)
54*87c50a58SChristian Brauner 		return -1;
55*87c50a58SChristian Brauner 	while (getline(&line, &len, f) > 0) {
56*87c50a58SChristian Brauner 		char *path = strchr(line, '/');
57*87c50a58SChristian Brauner 
58*87c50a58SChristian Brauner 		if (path && !strncmp(path, prefix, strlen(prefix))) {
59*87c50a58SChristian Brauner 			found = 1;
60*87c50a58SChristian Brauner 			break;
61*87c50a58SChristian Brauner 		}
62*87c50a58SChristian Brauner 	}
63*87c50a58SChristian Brauner 	free(line);
64*87c50a58SChristian Brauner 	fclose(f);
65*87c50a58SChristian Brauner 	return found;
66*87c50a58SChristian Brauner }
67*87c50a58SChristian Brauner 
68*87c50a58SChristian Brauner int main(int argc, char *argv[])
69*87c50a58SChristian Brauner {
70*87c50a58SChristian Brauner 	const char *binary = getenv("BINFMT_TEST_BINARY");
71*87c50a58SChristian Brauner 	const char *interp = getenv("BINFMT_TEST_INTERP");
72*87c50a58SChristian Brauner 	int memfd_mode = getenv("BINFMT_TEST_MEMFD") != NULL;
73*87c50a58SChristian Brauner 	int static_mode = getenv("BINFMT_TEST_STATIC") != NULL;
74*87c50a58SChristian Brauner 	unsigned long self = (unsigned long)__ehdr_start;
75*87c50a58SChristian Brauner 	unsigned long base = getauxval(AT_BASE);
76*87c50a58SChristian Brauner 	unsigned long phdr = getauxval(AT_PHDR);
77*87c50a58SChristian Brauner 	unsigned long entry = getauxval(AT_ENTRY);
78*87c50a58SChristian Brauner 	unsigned long start_code, end_code;
79*87c50a58SChristian Brauner 
80*87c50a58SChristian Brauner 	/* The argument vector is exactly what the caller built. */
81*87c50a58SChristian Brauner 	check(argc == 3 && !strcmp(argv[0], PAYLOAD_ARGV0) &&
82*87c50a58SChristian Brauner 	      !strcmp(argv[1], PAYLOAD_ARG1) && !strcmp(argv[2], PAYLOAD_ARG2),
83*87c50a58SChristian Brauner 	      "argv was rewritten");
84*87c50a58SChristian Brauner 
85*87c50a58SChristian Brauner 	/* Native from birth: no execfd, no dispatch marker. */
86*87c50a58SChristian Brauner 	check(getauxval(AT_EXECFD) == 0, "AT_EXECFD present");
87*87c50a58SChristian Brauner 	check(getauxval(AT_FLAGS) == 0, "AT_FLAGS not native");
88*87c50a58SChristian Brauner 
89*87c50a58SChristian Brauner 	if (static_mode) {
90*87c50a58SChristian Brauner 		/* The override was dropped: no interpreter was loaded. */
91*87c50a58SChristian Brauner 		check(base == 0, "AT_BASE set for a static payload");
92*87c50a58SChristian Brauner 	} else {
93*87c50a58SChristian Brauner 		/* A loader is mapped in the interpreter slot, not our image. */
94*87c50a58SChristian Brauner 		check(base != 0, "AT_BASE missing");
95*87c50a58SChristian Brauner 		check(base < self || base >= self + IMAGE_SPAN,
96*87c50a58SChristian Brauner 		      "AT_BASE inside our own image");
97*87c50a58SChristian Brauner 	}
98*87c50a58SChristian Brauner 
99*87c50a58SChristian Brauner 	/* We occupy the main-image slot. */
100*87c50a58SChristian Brauner 	check(phdr >= self && phdr < self + IMAGE_SPAN,
101*87c50a58SChristian Brauner 	      "AT_PHDR outside our image");
102*87c50a58SChristian Brauner 	check(entry >= self && entry < self + IMAGE_SPAN,
103*87c50a58SChristian Brauner 	      "AT_ENTRY outside our image");
104*87c50a58SChristian Brauner 
105*87c50a58SChristian Brauner 	/* The code statistics markers describe our image, natively placed. */
106*87c50a58SChristian Brauner 	if (stat_codes(getpid(), &start_code, &end_code) == 0) {
107*87c50a58SChristian Brauner 		check(start_code >= self && start_code < end_code &&
108*87c50a58SChristian Brauner 		      end_code < self + IMAGE_SPAN,
109*87c50a58SChristian Brauner 		      "stat start_code/end_code not our image");
110*87c50a58SChristian Brauner 		check(entry >= start_code && entry < end_code,
111*87c50a58SChristian Brauner 		      "AT_ENTRY outside [start_code, end_code)");
112*87c50a58SChristian Brauner 	} else {
113*87c50a58SChristian Brauner 		check(0, "cannot parse /proc/self/stat");
114*87c50a58SChristian Brauner 	}
115*87c50a58SChristian Brauner 
116*87c50a58SChristian Brauner 	if (!memfd_mode && binary) {
117*87c50a58SChristian Brauner 		const char *execfn = (const char *)getauxval(AT_EXECFN);
118*87c50a58SChristian Brauner 		const char *base_name = strrchr(binary, '/');
119*87c50a58SChristian Brauner 
120*87c50a58SChristian Brauner 		base_name = base_name ? base_name + 1 : binary;
121*87c50a58SChristian Brauner 
122*87c50a58SChristian Brauner 		/* exe link, AT_EXECFN and comm all follow the binary. */
123*87c50a58SChristian Brauner 		check(exe_is(binary), "/proc/self/exe");
124*87c50a58SChristian Brauner 		check(execfn && !strcmp(execfn, binary), "AT_EXECFN");
125*87c50a58SChristian Brauner 		check(comm_is(base_name), "comm");
126*87c50a58SChristian Brauner 
127*87c50a58SChristian Brauner 		/* The running binary is write-denied, natively. */
128*87c50a58SChristian Brauner 		check(write_denied(binary), "no ETXTBSY on the binary");
129*87c50a58SChristian Brauner 	}
130*87c50a58SChristian Brauner 
131*87c50a58SChristian Brauner 	if (interp) {
132*87c50a58SChristian Brauner 		int found = maps_has_prefix(interp);
133*87c50a58SChristian Brauner 
134*87c50a58SChristian Brauner 		if (static_mode)
135*87c50a58SChristian Brauner 			/* Nothing was substituted, nothing may be mapped. */
136*87c50a58SChristian Brauner 			check(found == 0, "loader mapped for a static payload");
137*87c50a58SChristian Brauner 		else
138*87c50a58SChristian Brauner 			/* The substituted loader shows under its real path. */
139*87c50a58SChristian Brauner 			check(found == 1, "loader path not in /proc/self/maps");
140*87c50a58SChristian Brauner 	}
141*87c50a58SChristian Brauner 
142*87c50a58SChristian Brauner 	if (failed)
143*87c50a58SChristian Brauner 		return 1;
144*87c50a58SChristian Brauner 	printf("[payload] native identity checks out\n");
145*87c50a58SChristian Brauner 	return 0;
146*87c50a58SChristian Brauner }
147