xref: /linux/tools/testing/selftests/exec/binfmt_transparent_interp.c (revision b9cba7ebfe539f3e4bbdd03a1e0efa3b30b3f592)
1*7baee96fSChristian Brauner // SPDX-License-Identifier: GPL-2.0
2*7baee96fSChristian Brauner /*
3*7baee96fSChristian Brauner  * Asserting interpreter for the transparent binfmt_misc mode. It runs in
4*7baee96fSChristian Brauner  * place of the dispatched binary and verifies the identity the kernel
5*7baee96fSChristian Brauner  * constructed: the aux vector contract, the exe link, argv, cmdline, comm
6*7baee96fSChristian Brauner  * and the write denial on the binary. BINFMT_TEST_BINARY names the binary;
7*7baee96fSChristian Brauner  * the harness execs it with the arguments "argone argtwo". Prints
8*7baee96fSChristian Brauner  * TRANSPARENT_OK and exits 0 when every check holds.
9*7baee96fSChristian Brauner  */
10*7baee96fSChristian Brauner #define _GNU_SOURCE
11*7baee96fSChristian Brauner #include <errno.h>
12*7baee96fSChristian Brauner #include <fcntl.h>
13*7baee96fSChristian Brauner #include <limits.h>
14*7baee96fSChristian Brauner #include <stdio.h>
15*7baee96fSChristian Brauner #include <stdlib.h>
16*7baee96fSChristian Brauner #include <string.h>
17*7baee96fSChristian Brauner #include <sys/auxv.h>
18*7baee96fSChristian Brauner #include <sys/stat.h>
19*7baee96fSChristian Brauner #include <unistd.h>
20*7baee96fSChristian Brauner 
21*7baee96fSChristian Brauner #include "binfmt_misc_common.h"
22*7baee96fSChristian Brauner #include "kselftest.h"
23*7baee96fSChristian Brauner 
24*7baee96fSChristian Brauner #ifndef AT_FLAGS_TRANSPARENT_INTERP
25*7baee96fSChristian Brauner #define AT_FLAGS_TRANSPARENT_INTERP (1 << 1)
26*7baee96fSChristian Brauner #endif
27*7baee96fSChristian Brauner 
28*7baee96fSChristian Brauner static int fail;
29*7baee96fSChristian Brauner 
30*7baee96fSChristian Brauner static void ok(int cond, const char *what)
31*7baee96fSChristian Brauner {
32*7baee96fSChristian Brauner 	if (!cond) {
33*7baee96fSChristian Brauner 		fprintf(stderr, "TRANSPARENT_FAIL: %s (errno %d)\n", what, errno);
34*7baee96fSChristian Brauner 		fail = 1;
35*7baee96fSChristian Brauner 	}
36*7baee96fSChristian Brauner }
37*7baee96fSChristian Brauner 
38*7baee96fSChristian Brauner int main(int argc, char **argv)
39*7baee96fSChristian Brauner {
40*7baee96fSChristian Brauner 	const char *binary = getenv("BINFMT_TEST_BINARY");
41*7baee96fSChristian Brauner 	const char *argv0 = getenv("BINFMT_TEST_ARGV0");
42*7baee96fSChristian Brauner 	char expect[PATH_MAX + 32], buf[PATH_MAX];
43*7baee96fSChristian Brauner 	unsigned long execfd;
44*7baee96fSChristian Brauner 	struct stat stb, stfd;
45*7baee96fSChristian Brauner 	const char *want[3];
46*7baee96fSChristian Brauner 	const char *base;
47*7baee96fSChristian Brauner 	size_t expect_len, i;
48*7baee96fSChristian Brauner 	int fd, have_stb, have_stfd;
49*7baee96fSChristian Brauner 	ssize_t n;
50*7baee96fSChristian Brauner 
51*7baee96fSChristian Brauner 	if (!binary) {
52*7baee96fSChristian Brauner 		fprintf(stderr, "TRANSPARENT_FAIL: BINFMT_TEST_BINARY unset\n");
53*7baee96fSChristian Brauner 		return 1;
54*7baee96fSChristian Brauner 	}
55*7baee96fSChristian Brauner 	/* Distinct from the binary path, so a classic argv splice is caught. */
56*7baee96fSChristian Brauner 	want[0] = argv0 ? argv0 : binary;
57*7baee96fSChristian Brauner 	want[1] = PAYLOAD_ARG1;
58*7baee96fSChristian Brauner 	want[2] = PAYLOAD_ARG2;
59*7baee96fSChristian Brauner 
60*7baee96fSChristian Brauner 	/* The aux vector announces the transparent contract. */
61*7baee96fSChristian Brauner 	ok(getauxval(AT_FLAGS) & AT_FLAGS_TRANSPARENT_INTERP,
62*7baee96fSChristian Brauner 	   "AT_FLAGS lacks AT_FLAGS_TRANSPARENT_INTERP");
63*7baee96fSChristian Brauner 
64*7baee96fSChristian Brauner 	/* AT_EXECFD refers to the very file that was executed. */
65*7baee96fSChristian Brauner 	execfd = getauxval(AT_EXECFD);
66*7baee96fSChristian Brauner 	ok(execfd > 2, "no AT_EXECFD");
67*7baee96fSChristian Brauner 	have_stb = !stat(binary, &stb);
68*7baee96fSChristian Brauner 	ok(have_stb, "cannot stat the binary");
69*7baee96fSChristian Brauner 	have_stfd = !fstat((int)execfd, &stfd);
70*7baee96fSChristian Brauner 	ok(have_stfd, "cannot fstat AT_EXECFD");
71*7baee96fSChristian Brauner 	ok(have_stb && have_stfd && stb.st_dev == stfd.st_dev &&
72*7baee96fSChristian Brauner 	   stb.st_ino == stfd.st_ino, "AT_EXECFD is not the binary");
73*7baee96fSChristian Brauner 
74*7baee96fSChristian Brauner 	/* The exe link names the binary, not this interpreter. */
75*7baee96fSChristian Brauner 	ok(exe_is(binary), "/proc/self/exe is not the binary");
76*7baee96fSChristian Brauner 
77*7baee96fSChristian Brauner 	/* argv arrived unspliced. */
78*7baee96fSChristian Brauner 	ok(argc == (int)ARRAY_SIZE(want), "argv was rewritten");
79*7baee96fSChristian Brauner 	for (i = 0; i < ARRAY_SIZE(want) && i < (size_t)argc; i++)
80*7baee96fSChristian Brauner 		ok(!strcmp(argv[i], want[i]), "argv was rewritten");
81*7baee96fSChristian Brauner 
82*7baee96fSChristian Brauner 	/* And so did the kernel's copy of it: the same strings, NUL separated. */
83*7baee96fSChristian Brauner 	for (i = 0, expect_len = 0; i < ARRAY_SIZE(want); i++) {
84*7baee96fSChristian Brauner 		size_t len = strlen(want[i]) + 1;
85*7baee96fSChristian Brauner 
86*7baee96fSChristian Brauner 		if (expect_len + len > sizeof(expect)) {
87*7baee96fSChristian Brauner 			ok(0, "argv does not fit the expectation buffer");
88*7baee96fSChristian Brauner 			break;
89*7baee96fSChristian Brauner 		}
90*7baee96fSChristian Brauner 		memcpy(expect + expect_len, want[i], len);
91*7baee96fSChristian Brauner 		expect_len += len;
92*7baee96fSChristian Brauner 	}
93*7baee96fSChristian Brauner 	fd = open("/proc/self/cmdline", O_RDONLY);
94*7baee96fSChristian Brauner 	n = fd >= 0 ? read(fd, buf, sizeof(buf)) : -1;
95*7baee96fSChristian Brauner 	if (fd >= 0)
96*7baee96fSChristian Brauner 		close(fd);
97*7baee96fSChristian Brauner 	ok(n == (ssize_t)expect_len && !memcmp(buf, expect, expect_len),
98*7baee96fSChristian Brauner 	   "/proc/self/cmdline was rewritten");
99*7baee96fSChristian Brauner 
100*7baee96fSChristian Brauner 	/* comm is the binary's basename. */
101*7baee96fSChristian Brauner 	base = strrchr(binary, '/');
102*7baee96fSChristian Brauner 	base = base ? base + 1 : binary;
103*7baee96fSChristian Brauner 	ok(comm_is(base), "comm is not the binary's basename");
104*7baee96fSChristian Brauner 
105*7baee96fSChristian Brauner 	/* The binary is write-denied while it runs, like a direct exec. */
106*7baee96fSChristian Brauner 	ok(write_denied(binary), "binary is writable while running");
107*7baee96fSChristian Brauner 	ok(write_denied("/proc/self/exe"), "exe link is writable while running");
108*7baee96fSChristian Brauner 
109*7baee96fSChristian Brauner 	if (!fail)
110*7baee96fSChristian Brauner 		printf("TRANSPARENT_OK\n");
111*7baee96fSChristian Brauner 	return fail;
112*7baee96fSChristian Brauner }
113