blob: 272db8efb4b5c32ef7c06e40859d9eaffd1c0b3f [file] [edit]
// 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;
}