summaryrefslogtreecommitdiff
path: root/tools/testing/selftests/exec/binfmt_transparent_interp.c
blob: d4c4a538c9aa9ab838883f2b046e8b3b725f5ff0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// 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;
}