| 1 | |
| 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 |
|
| 29 | extern const char __ehdr_start[];
|
| 30 |
|
| 31 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 91 | check(base == 0, "AT_BASE set for a static payload");
|
| 92 | } else {
|
| 93 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 136 | check(found == 0, "loader mapped for a static payload");
|
| 137 | else
|
| 138 |
|
| 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 | }
|