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