| // 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; |
| } |