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