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