112 lines · 3.4 KB · C
RawDownload
// 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;}