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