xref: /linux/tools/testing/selftests/exec/binfmt_misc_bpf.c (revision 145e675de6ca0b3865000876414b8a5f93246c6b)
1277d787fSFarid Zakaria // SPDX-License-Identifier: GPL-2.0
2277d787fSFarid Zakaria /*
3277d787fSFarid Zakaria  * Selftest for binfmt_misc bpf-backed ('B') handlers.
4277d787fSFarid Zakaria  *
5277d787fSFarid Zakaria  * A handler is a struct binfmt_misc_ops struct_ops map with a sleepable match
6277d787fSFarid Zakaria  * and a sleepable load program. Attaching it publishes it by name in the
7277d787fSFarid Zakaria  * caller's user namespace; a 'B' entry referencing it by name in the
8277d787fSFarid Zakaria  * interpreter field activates it:
9277d787fSFarid Zakaria  *
10277d787fSFarid Zakaria  *     echo ':name:B::::<handler>:' > /proc/sys/fs/binfmt_misc/register
11277d787fSFarid Zakaria  *
127baee96fSChristian Brauner  * Three self-contained cases are exercised:
13277d787fSFarid Zakaria  *
14277d787fSFarid Zakaria  *   1. bpf_interp: the match program matches a synthetic aarch64 ELF header
15277d787fSFarid Zakaria  *      from the prefetched bprm->buf and the load program routes it to a
16277d787fSFarid Zakaria  *      fixed interpreter of its choosing.
17277d787fSFarid Zakaria  *   2. nix_origin: the match program reads the binary's program headers to
18277d787fSFarid Zakaria  *      commit only to a "$ORIGIN/..."-relative PT_INTERP and the load program
19277d787fSFarid Zakaria  *      resolves it to an interpreter co-located with the binary (the
20277d787fSFarid Zakaria  *      relocatable-loader case the kernel ELF loader cannot express).
217baee96fSChristian Brauner  *   3. transparent: the load program sets BPF_BINPRM_TRANSPARENT; the
227baee96fSChristian Brauner  *      asserting interpreter (binfmt_transparent_interp) verifies the
237baee96fSChristian Brauner  *      identity the kernel constructed (exe link, argv, cmdline, comm,
247baee96fSChristian Brauner  *      AT_EXECFD, write denial) from inside the process.
2587c50a58SChristian Brauner  *   4. loader: the load program sets BPF_BINPRM_LOADER; the payload
2687c50a58SChristian Brauner  *      (binfmt_loader_payload) runs as the main image with the selected
2787c50a58SChristian Brauner  *      interpreter substituted for its PT_INTERP and asserts the native
2887c50a58SChristian Brauner  *      identity from inside.
29277d787fSFarid Zakaria  *
307baee96fSChristian Brauner  * The first two route to a test interpreter that prints BPF_INTERP_RAN,
317baee96fSChristian Brauner  * proving the program's chosen interpreter actually ran.
32277d787fSFarid Zakaria  */
33277d787fSFarid Zakaria #define _GNU_SOURCE
3426860105SChristian Brauner #include <elf.h>
3526860105SChristian Brauner #include <limits.h>
36277d787fSFarid Zakaria #include <stdio.h>
37277d787fSFarid Zakaria #include <stdlib.h>
38277d787fSFarid Zakaria #include <string.h>
39277d787fSFarid Zakaria #include <unistd.h>
40277d787fSFarid Zakaria #include <fcntl.h>
41277d787fSFarid Zakaria 
42277d787fSFarid Zakaria #include <bpf/btf.h>
43277d787fSFarid Zakaria #include <bpf/libbpf.h>
44277d787fSFarid Zakaria 
4526860105SChristian Brauner #include "binfmt_misc_common.h"
4626860105SChristian Brauner #include "kselftest_harness.h"
4726860105SChristian Brauner 
48277d787fSFarid Zakaria #define INTERP_PATH	"/tmp/binfmt_bpf_interp"
49277d787fSFarid Zakaria #define AARCH64_PATH	"/tmp/binfmt_bpf_aarch64"
5026860105SChristian Brauner #define RELOC_TEMPLATE	"/tmp/binfmt_relocXXXXXX"
517baee96fSChristian Brauner #define TRANS_INTERP	"/tmp/binfmt_transparent_interp"
527baee96fSChristian Brauner #define TRANS_PATH	"/tmp/binfmt_bpf_riscv"
53277d787fSFarid Zakaria #define EXPECT		"BPF_INTERP_RAN"
547baee96fSChristian Brauner #define TRANS_EXPECT	"TRANSPARENT_OK"
5587c50a58SChristian Brauner #define LOADER_INTERP	"/tmp/binfmt_loader_interp"
5687c50a58SChristian Brauner #define LOADER_PATH	"/tmp/binfmt_bpf_loader.ldrtest"
57277d787fSFarid Zakaria 
5826860105SChristian Brauner /* A minimal 64-bit little-endian ELF header, padded to the read size. */
5926860105SChristian Brauner static int create_fake_elf(const char *path, unsigned short machine)
60277d787fSFarid Zakaria {
61277d787fSFarid Zakaria 	unsigned char hdr[256] = {0};
62277d787fSFarid Zakaria 	int fd;
63277d787fSFarid Zakaria 
64277d787fSFarid Zakaria 	hdr[0] = 0x7f; hdr[1] = 'E'; hdr[2] = 'L'; hdr[3] = 'F';
6526860105SChristian Brauner 	hdr[4] = ELFCLASS64;
6626860105SChristian Brauner 	hdr[5] = ELFDATA2LSB;
6726860105SChristian Brauner 	hdr[6] = EV_CURRENT;
6826860105SChristian Brauner 	hdr[16] = ET_EXEC;
6926860105SChristian Brauner 	hdr[18] = machine & 0xff;	/* e_machine, little-endian */
7026860105SChristian Brauner 	hdr[19] = machine >> 8;
7126860105SChristian Brauner 	hdr[20] = EV_CURRENT;
72277d787fSFarid Zakaria 
7326860105SChristian Brauner 	unlink(path);
7426860105SChristian Brauner 	fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0755);
75277d787fSFarid Zakaria 	if (fd < 0)
76277d787fSFarid Zakaria 		return -1;
77277d787fSFarid Zakaria 	if (write(fd, hdr, sizeof(hdr)) != (ssize_t)sizeof(hdr)) {
78277d787fSFarid Zakaria 		close(fd);
79277d787fSFarid Zakaria 		return -1;
80277d787fSFarid Zakaria 	}
81277d787fSFarid Zakaria 	close(fd);
82277d787fSFarid Zakaria 	return 0;
83277d787fSFarid Zakaria }
84277d787fSFarid Zakaria 
85277d787fSFarid Zakaria static int register_entry(const char *name, const char *handler)
86277d787fSFarid Zakaria {
8726860105SChristian Brauner 	char rule[PATH_MAX];
88277d787fSFarid Zakaria 
89277d787fSFarid Zakaria 	snprintf(rule, sizeof(rule), ":%s:B::::%s:", name, handler);
9026860105SChristian Brauner 	return write_reg(rule);
91277d787fSFarid Zakaria }
92277d787fSFarid Zakaria 
93277d787fSFarid Zakaria static int check_output(const char *cmd, const char *expected)
94277d787fSFarid Zakaria {
95277d787fSFarid Zakaria 	char buf[128];
96277d787fSFarid Zakaria 	FILE *fp;
97277d787fSFarid Zakaria 
98277d787fSFarid Zakaria 	fp = popen(cmd, "r");
99277d787fSFarid Zakaria 	if (!fp)
100277d787fSFarid Zakaria 		return -1;
101277d787fSFarid Zakaria 	if (!fgets(buf, sizeof(buf), fp)) {
102277d787fSFarid Zakaria 		pclose(fp);
103277d787fSFarid Zakaria 		return -1;
104277d787fSFarid Zakaria 	}
105277d787fSFarid Zakaria 	pclose(fp);
106277d787fSFarid Zakaria 	return strncmp(buf, expected, strlen(expected)) ? -1 : 0;
107277d787fSFarid Zakaria }
108277d787fSFarid Zakaria 
109*145e675dSChristian Brauner /* Does the kernel BTF know struct binfmt_misc_ops (CONFIG_BINFMT_MISC_BPF)? */
110*145e675dSChristian Brauner static bool have_binfmt_misc_ops(void)
111*145e675dSChristian Brauner {
112*145e675dSChristian Brauner 	struct btf *btf = btf__load_vmlinux_btf();
113*145e675dSChristian Brauner 	bool have;
114*145e675dSChristian Brauner 
115*145e675dSChristian Brauner 	have = btf && btf__find_by_name_kind(btf, "binfmt_misc_ops",
116*145e675dSChristian Brauner 					     BTF_KIND_STRUCT) >= 0;
117*145e675dSChristian Brauner 	btf__free(btf);
118*145e675dSChristian Brauner 	return have;
119*145e675dSChristian Brauner }
120*145e675dSChristian Brauner 
121*145e675dSChristian Brauner /* The reason bpf handler cases cannot run here, NULL if they can. */
122*145e675dSChristian Brauner static const char *bpf_handler_unsupported(void)
123*145e675dSChristian Brauner {
124*145e675dSChristian Brauner 	if (getuid() != 0)
125*145e675dSChristian Brauner 		return "test must be run as root";
126*145e675dSChristian Brauner 	if (!have_binfmt_misc_ops())
127*145e675dSChristian Brauner 		return "no struct binfmt_misc_ops in the kernel BTF (CONFIG_BINFMT_MISC_BPF)";
128*145e675dSChristian Brauner 	if (!binfmt_misc_available())
129*145e675dSChristian Brauner 		return "no binfmt_misc";
130*145e675dSChristian Brauner 	return NULL;
131*145e675dSChristian Brauner }
132*145e675dSChristian Brauner 
13387c50a58SChristian Brauner /* An attached handler with its 'B' entry activated. */
13487c50a58SChristian Brauner struct bpf_case {
13587c50a58SChristian Brauner 	struct bpf_object *obj;
13687c50a58SChristian Brauner 	struct bpf_link *link;
13787c50a58SChristian Brauner 	const char *entry;
13887c50a58SChristian Brauner };
13987c50a58SChristian Brauner 
140277d787fSFarid Zakaria /*
141277d787fSFarid Zakaria  * Load @objfile, attach its struct_ops map @handler (which publishes the
14287c50a58SChristian Brauner  * handler) and activate a 'B' entry named @entry that references it.
143277d787fSFarid Zakaria  */
14487c50a58SChristian Brauner static int bpf_case_start(struct bpf_case *c, const char *objfile,
14587c50a58SChristian Brauner 			  const char *handler, const char *entry)
146277d787fSFarid Zakaria {
147277d787fSFarid Zakaria 	struct bpf_map *map;
148277d787fSFarid Zakaria 
14987c50a58SChristian Brauner 	c->obj = NULL;
15087c50a58SChristian Brauner 	c->link = NULL;
15187c50a58SChristian Brauner 	c->entry = entry;
15287c50a58SChristian Brauner 
15387c50a58SChristian Brauner 	c->obj = bpf_object__open_file(objfile, NULL);
15487c50a58SChristian Brauner 	if (!c->obj || libbpf_get_error(c->obj)) {
155277d787fSFarid Zakaria 		fprintf(stderr, "open %s failed\n", objfile);
15687c50a58SChristian Brauner 		c->obj = NULL;
157277d787fSFarid Zakaria 		return -1;
158277d787fSFarid Zakaria 	}
15987c50a58SChristian Brauner 	if (bpf_object__load(c->obj)) {
160277d787fSFarid Zakaria 		fprintf(stderr, "load %s failed (check dmesg for the verifier log)\n",
161277d787fSFarid Zakaria 			objfile);
16287c50a58SChristian Brauner 		goto fail;
163277d787fSFarid Zakaria 	}
16487c50a58SChristian Brauner 	map = bpf_object__find_map_by_name(c->obj, handler);
165277d787fSFarid Zakaria 	if (!map) {
166277d787fSFarid Zakaria 		fprintf(stderr, "no struct_ops map '%s' in %s\n", handler, objfile);
16787c50a58SChristian Brauner 		goto fail;
168277d787fSFarid Zakaria 	}
16987c50a58SChristian Brauner 	c->link = bpf_map__attach_struct_ops(map);
17087c50a58SChristian Brauner 	if (!c->link || libbpf_get_error(c->link)) {
171277d787fSFarid Zakaria 		fprintf(stderr, "attach struct_ops '%s' failed\n", handler);
17287c50a58SChristian Brauner 		c->link = NULL;
17387c50a58SChristian Brauner 		goto fail;
174277d787fSFarid Zakaria 	}
175277d787fSFarid Zakaria 	if (register_entry(entry, handler)) {
176277d787fSFarid Zakaria 		fprintf(stderr, "register 'B' entry '%s' failed\n", entry);
17787c50a58SChristian Brauner 		goto fail;
178277d787fSFarid Zakaria 	}
17987c50a58SChristian Brauner 	return 0;
18087c50a58SChristian Brauner 
18187c50a58SChristian Brauner fail:
18287c50a58SChristian Brauner 	bpf_link__destroy(c->link);
18387c50a58SChristian Brauner 	bpf_object__close(c->obj);
18487c50a58SChristian Brauner 	c->obj = NULL;
18587c50a58SChristian Brauner 	c->link = NULL;
18687c50a58SChristian Brauner 	return -1;
18787c50a58SChristian Brauner }
18887c50a58SChristian Brauner 
18987c50a58SChristian Brauner static void bpf_case_stop(struct bpf_case *c)
19087c50a58SChristian Brauner {
19187c50a58SChristian Brauner 	unregister(c->entry);
19287c50a58SChristian Brauner 	bpf_link__destroy(c->link);
19387c50a58SChristian Brauner 	bpf_object__close(c->obj);
19487c50a58SChristian Brauner }
19587c50a58SChristian Brauner 
19687c50a58SChristian Brauner /* Activate @handler, run @target and check it produced @expect. */
19787c50a58SChristian Brauner static int run_case(const char *objfile, const char *handler,
19887c50a58SChristian Brauner 		    const char *entry, const char *target, const char *expect)
19987c50a58SChristian Brauner {
20087c50a58SChristian Brauner 	struct bpf_case c;
20187c50a58SChristian Brauner 	int ret;
20287c50a58SChristian Brauner 
20387c50a58SChristian Brauner 	if (bpf_case_start(&c, objfile, handler, entry))
20487c50a58SChristian Brauner 		return -1;
205277d787fSFarid Zakaria 	ret = check_output(target, expect);
20687c50a58SChristian Brauner 	bpf_case_stop(&c);
207277d787fSFarid Zakaria 	return ret;
208277d787fSFarid Zakaria }
209277d787fSFarid Zakaria 
21026860105SChristian Brauner FIXTURE(bpf_handler) {
21126860105SChristian Brauner 	char obj[PATH_MAX];	/* struct_ops object of the case under test */
21226860105SChristian Brauner };
21326860105SChristian Brauner 
21426860105SChristian Brauner FIXTURE_SETUP(bpf_handler)
215277d787fSFarid Zakaria {
21626860105SChristian Brauner 	char src[PATH_MAX];
217*145e675dSChristian Brauner 	const char *why = bpf_handler_unsupported();
218277d787fSFarid Zakaria 
219*145e675dSChristian Brauner 	if (why)
220*145e675dSChristian Brauner 		SKIP(return, "%s", why);
221277d787fSFarid Zakaria 
222277d787fSFarid Zakaria 	/* Shared test interpreter. */
22326860105SChristian Brauner 	ASSERT_EQ(artifact_path(src, sizeof(src), "binfmt_bpf_interp"), 0);
22426860105SChristian Brauner 	ASSERT_EQ(copy_file(src, INTERP_PATH), 0);
225277d787fSFarid Zakaria }
226277d787fSFarid Zakaria 
22726860105SChristian Brauner FIXTURE_TEARDOWN(bpf_handler)
22826860105SChristian Brauner {
229277d787fSFarid Zakaria 	unlink(INTERP_PATH);
230277d787fSFarid Zakaria }
23126860105SChristian Brauner 
23226860105SChristian Brauner /* The match program matches a synthetic header, the load program routes it. */
23326860105SChristian Brauner TEST_F(bpf_handler, fixed_interpreter)
23426860105SChristian Brauner {
23526860105SChristian Brauner 	ASSERT_EQ(create_fake_elf(AARCH64_PATH, EM_AARCH64), 0);
23626860105SChristian Brauner 	ASSERT_EQ(artifact_path(self->obj, sizeof(self->obj),
23726860105SChristian Brauner 				"bpf_interp.bpf.o"), 0);
23826860105SChristian Brauner 	EXPECT_EQ(run_case(self->obj, "bpf_interp", "test_bpf_interp",
23926860105SChristian Brauner 			   AARCH64_PATH, EXPECT), 0);
24026860105SChristian Brauner 	unlink(AARCH64_PATH);
24126860105SChristian Brauner }
24226860105SChristian Brauner 
24326860105SChristian Brauner /* A "$ORIGIN/..." PT_INTERP resolved to an interpreter next to the binary. */
24426860105SChristian Brauner TEST_F(bpf_handler, origin_relative_interpreter)
24526860105SChristian Brauner {
24626860105SChristian Brauner 	char src[PATH_MAX], app[PATH_MAX], interp[PATH_MAX];
24726860105SChristian Brauner 	char dir[] = RELOC_TEMPLATE;
24826860105SChristian Brauner 
24926860105SChristian Brauner 	ASSERT_NE(mkdtemp(dir), NULL);
25026860105SChristian Brauner 	snprintf(app, sizeof(app), "%s/app", dir);
25126860105SChristian Brauner 	snprintf(interp, sizeof(interp), "%s/binfmt_bpf_interp", dir);
25226860105SChristian Brauner 	ASSERT_EQ(artifact_path(src, sizeof(src), "binfmt_bpf_app"), 0);
25326860105SChristian Brauner 	ASSERT_EQ(copy_file(src, app), 0);
25426860105SChristian Brauner 	ASSERT_EQ(copy_file(INTERP_PATH, interp), 0);
25526860105SChristian Brauner 
25626860105SChristian Brauner 	ASSERT_EQ(artifact_path(self->obj, sizeof(self->obj),
25726860105SChristian Brauner 				"nix_origin.bpf.o"), 0);
25826860105SChristian Brauner 	EXPECT_EQ(run_case(self->obj, "nix_origin", "test_bpf_origin",
25926860105SChristian Brauner 			   app, EXPECT), 0);
26026860105SChristian Brauner 
26126860105SChristian Brauner 	unlink(app);
26226860105SChristian Brauner 	unlink(interp);
26326860105SChristian Brauner 	rmdir(dir);
26426860105SChristian Brauner }
26526860105SChristian Brauner 
2667baee96fSChristian Brauner /* A transparent dispatch: the process presents as the binary, not the interp. */
2677baee96fSChristian Brauner TEST_F(bpf_handler, transparent_dispatch)
2687baee96fSChristian Brauner {
2697baee96fSChristian Brauner 	char src[PATH_MAX], cmd[PATH_MAX + 16];
2707baee96fSChristian Brauner 
2717baee96fSChristian Brauner 	/* Probe for transparent-mode support via its static counterpart. */
272686585ecSChristian Brauner 	if (!binfmt_flag_supported('T'))
2737baee96fSChristian Brauner 		SKIP(return, "kernel without transparent mode");
2747baee96fSChristian Brauner 
2757baee96fSChristian Brauner 	ASSERT_EQ(artifact_path(src, sizeof(src), "binfmt_transparent_interp"), 0);
2767baee96fSChristian Brauner 	ASSERT_EQ(copy_file(src, TRANS_INTERP), 0);
2777baee96fSChristian Brauner 	ASSERT_EQ(create_fake_elf(TRANS_PATH, EM_RISCV), 0);
2787baee96fSChristian Brauner 
2797baee96fSChristian Brauner 	setenv("BINFMT_TEST_BINARY", TRANS_PATH, 1);
2807baee96fSChristian Brauner 	snprintf(cmd, sizeof(cmd), "%s argone argtwo", TRANS_PATH);
2817baee96fSChristian Brauner 	ASSERT_EQ(artifact_path(self->obj, sizeof(self->obj),
2827baee96fSChristian Brauner 				"transparent.bpf.o"), 0);
2837baee96fSChristian Brauner 	EXPECT_EQ(run_case(self->obj, "transparent", "test_bpf_transparent",
2847baee96fSChristian Brauner 			   cmd, TRANS_EXPECT), 0);
2857baee96fSChristian Brauner 
2867baee96fSChristian Brauner 	unlink(TRANS_PATH);
2877baee96fSChristian Brauner 	unlink(TRANS_INTERP);
2887baee96fSChristian Brauner }
2897baee96fSChristian Brauner 
29087c50a58SChristian Brauner /* A per-exec loader substitution: the payload runs as a native exec. */
29187c50a58SChristian Brauner TEST_F(bpf_handler, loader_substitution)
29287c50a58SChristian Brauner {
29387c50a58SChristian Brauner 	char src[PATH_MAX], loader[PATH_MAX];
29487c50a58SChristian Brauner 	struct bpf_case c;
29587c50a58SChristian Brauner 	int status;
29687c50a58SChristian Brauner 
29787c50a58SChristian Brauner 	if (find_loader(loader, sizeof(loader)))
29887c50a58SChristian Brauner 		SKIP(return, "cannot determine own PT_INTERP");
29987c50a58SChristian Brauner 
30087c50a58SChristian Brauner 	ASSERT_EQ(copy_file(loader, LOADER_INTERP), 0);
30187c50a58SChristian Brauner 	ASSERT_EQ(artifact_path(src, sizeof(src), "binfmt_loader_payload"), 0);
30287c50a58SChristian Brauner 	ASSERT_EQ(copy_file(src, LOADER_PATH), 0);
30387c50a58SChristian Brauner 	ASSERT_EQ(patch_file(LOADER_PATH, EI_PAD, LOADER_MARKER,
30487c50a58SChristian Brauner 			     strlen(LOADER_MARKER)), 0);
30587c50a58SChristian Brauner 	ASSERT_EQ(artifact_path(self->obj, sizeof(self->obj),
30687c50a58SChristian Brauner 				"loader.bpf.o"), 0);
30787c50a58SChristian Brauner 
30887c50a58SChristian Brauner 	setenv("BINFMT_TEST_BINARY", LOADER_PATH, 1);
30987c50a58SChristian Brauner 	setenv("BINFMT_TEST_INTERP", LOADER_INTERP, 1);
31087c50a58SChristian Brauner 
31187c50a58SChristian Brauner 	ASSERT_EQ(bpf_case_start(&c, self->obj, "loader", "test_bpf_loader"), 0);
31287c50a58SChristian Brauner 	status = run_payload(LOADER_PATH);
31387c50a58SChristian Brauner 	bpf_case_stop(&c);
31487c50a58SChristian Brauner 	EXPECT_EQ(status, 0);
31587c50a58SChristian Brauner 
31687c50a58SChristian Brauner 	unsetenv("BINFMT_TEST_INTERP");
31787c50a58SChristian Brauner 	unlink(LOADER_PATH);
31887c50a58SChristian Brauner 	unlink(LOADER_INTERP);
31987c50a58SChristian Brauner }
32087c50a58SChristian Brauner 
32126860105SChristian Brauner TEST_HARNESS_MAIN
322