projects/linux·blob master · refs

146 lines · 4.4 KiB

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. */
29extern 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
34static int failed;
35
36static 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. */
45static 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
68int 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}