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