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