mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-31 02:21:39 -04:00
Verify the identity a transparent dispatch constructs, from both activation paths. - binfmt_misc_transparent: registers a magic entry with the static 'T' flag and execs a matched binary with arguments. - binfmt_misc_bpf: a handler whose load program sets BPF_BINPRM_TRANSPARENT. Both dispatch to a shared asserting interpreter that runs in place of the binary and checks the contract from the inside: - AT_FLAGS carries AT_FLAGS_TRANSPARENT_INTERP - AT_EXECFD refers to the very inode of the binary - /proc/self/exe resolves to the binary - argv and /proc/self/cmdline are exactly what the caller passed with nothing spliced in - comm is the binary's basename - the binary is write-denied while it runs The static test also validates the registration. 'T' combined with 'P' must be rejected. A kernel that does not know 'T' turns the test into a skip. The asserting interpreter and the static test build without the bpf toolchain so the core transparent semantics stay covered on systems where the bpf cases are skipped. The flag support probe, the canonical payload argv with the run_payload() helper that execs it, and the identity assertions (exe link, comm, write denial) live in binfmt_misc_common.h; the loader substitution test reuses all of them. Link: https://patch.msgid.link/20260721-work-bpf-binfmt_misc-ptinterp-v2-13-e57866e4ae0f@kernel.org Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
113 lines
3.4 KiB
C
113 lines
3.4 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Asserting interpreter for the transparent binfmt_misc mode. It runs in
|
|
* place of the dispatched binary and verifies the identity the kernel
|
|
* constructed: the aux vector contract, the exe link, argv, cmdline, comm
|
|
* and the write denial on the binary. BINFMT_TEST_BINARY names the binary;
|
|
* the harness execs it with the arguments "argone argtwo". Prints
|
|
* TRANSPARENT_OK and exits 0 when every check holds.
|
|
*/
|
|
#define _GNU_SOURCE
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/auxv.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#include "binfmt_misc_common.h"
|
|
#include "kselftest.h"
|
|
|
|
#ifndef AT_FLAGS_TRANSPARENT_INTERP
|
|
#define AT_FLAGS_TRANSPARENT_INTERP (1 << 1)
|
|
#endif
|
|
|
|
static int fail;
|
|
|
|
static void ok(int cond, const char *what)
|
|
{
|
|
if (!cond) {
|
|
fprintf(stderr, "TRANSPARENT_FAIL: %s (errno %d)\n", what, errno);
|
|
fail = 1;
|
|
}
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
const char *binary = getenv("BINFMT_TEST_BINARY");
|
|
const char *argv0 = getenv("BINFMT_TEST_ARGV0");
|
|
char expect[PATH_MAX + 32], buf[PATH_MAX];
|
|
unsigned long execfd;
|
|
struct stat stb, stfd;
|
|
const char *want[3];
|
|
const char *base;
|
|
size_t expect_len, i;
|
|
int fd, have_stb, have_stfd;
|
|
ssize_t n;
|
|
|
|
if (!binary) {
|
|
fprintf(stderr, "TRANSPARENT_FAIL: BINFMT_TEST_BINARY unset\n");
|
|
return 1;
|
|
}
|
|
/* Distinct from the binary path, so a classic argv splice is caught. */
|
|
want[0] = argv0 ? argv0 : binary;
|
|
want[1] = PAYLOAD_ARG1;
|
|
want[2] = PAYLOAD_ARG2;
|
|
|
|
/* The aux vector announces the transparent contract. */
|
|
ok(getauxval(AT_FLAGS) & AT_FLAGS_TRANSPARENT_INTERP,
|
|
"AT_FLAGS lacks AT_FLAGS_TRANSPARENT_INTERP");
|
|
|
|
/* AT_EXECFD refers to the very file that was executed. */
|
|
execfd = getauxval(AT_EXECFD);
|
|
ok(execfd > 2, "no AT_EXECFD");
|
|
have_stb = !stat(binary, &stb);
|
|
ok(have_stb, "cannot stat the binary");
|
|
have_stfd = !fstat((int)execfd, &stfd);
|
|
ok(have_stfd, "cannot fstat AT_EXECFD");
|
|
ok(have_stb && have_stfd && stb.st_dev == stfd.st_dev &&
|
|
stb.st_ino == stfd.st_ino, "AT_EXECFD is not the binary");
|
|
|
|
/* The exe link names the binary, not this interpreter. */
|
|
ok(exe_is(binary), "/proc/self/exe is not the binary");
|
|
|
|
/* argv arrived unspliced. */
|
|
ok(argc == (int)ARRAY_SIZE(want), "argv was rewritten");
|
|
for (i = 0; i < ARRAY_SIZE(want) && i < (size_t)argc; i++)
|
|
ok(!strcmp(argv[i], want[i]), "argv was rewritten");
|
|
|
|
/* And so did the kernel's copy of it: the same strings, NUL separated. */
|
|
for (i = 0, expect_len = 0; i < ARRAY_SIZE(want); i++) {
|
|
size_t len = strlen(want[i]) + 1;
|
|
|
|
if (expect_len + len > sizeof(expect)) {
|
|
ok(0, "argv does not fit the expectation buffer");
|
|
break;
|
|
}
|
|
memcpy(expect + expect_len, want[i], len);
|
|
expect_len += len;
|
|
}
|
|
fd = open("/proc/self/cmdline", O_RDONLY);
|
|
n = fd >= 0 ? read(fd, buf, sizeof(buf)) : -1;
|
|
if (fd >= 0)
|
|
close(fd);
|
|
ok(n == (ssize_t)expect_len && !memcmp(buf, expect, expect_len),
|
|
"/proc/self/cmdline was rewritten");
|
|
|
|
/* comm is the binary's basename. */
|
|
base = strrchr(binary, '/');
|
|
base = base ? base + 1 : binary;
|
|
ok(comm_is(base), "comm is not the binary's basename");
|
|
|
|
/* The binary is write-denied while it runs, like a direct exec. */
|
|
ok(write_denied(binary), "binary is writable while running");
|
|
ok(write_denied("/proc/self/exe"), "exe link is writable while running");
|
|
|
|
if (!fail)
|
|
printf("TRANSPARENT_OK\n");
|
|
return fail;
|
|
}
|