xref: /linux/tools/testing/selftests/exec/binfmt_misc_bpf.c (revision b9cba7ebfe539f3e4bbdd03a1e0efa3b30b3f592)
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  *
127404b147SChristian Brauner  * Five 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.
297404b147SChristian Brauner  *   5. interp_bind: an entry registered disabled with 'D' is given its
307404b147SChristian Brauner  *      interpreters one write at a time, and the load program picks one by
317404b147SChristian Brauner  *      name per exec. Replacing what the path holds afterwards changes
327404b147SChristian Brauner  *      nothing, which is the point of binding a file rather than resolving
337404b147SChristian Brauner  *      a name at exec time. Enabling the entry seals it.
34277d787fSFarid Zakaria  *
357baee96fSChristian Brauner  * The first two route to a test interpreter that prints BPF_INTERP_RAN,
367baee96fSChristian Brauner  * proving the program's chosen interpreter actually ran.
37277d787fSFarid Zakaria  */
38277d787fSFarid Zakaria #define _GNU_SOURCE
3926860105SChristian Brauner #include <elf.h>
4026860105SChristian Brauner #include <limits.h>
41*f2b69ea2SChristian Brauner #include <sched.h>
42277d787fSFarid Zakaria #include <stdio.h>
43277d787fSFarid Zakaria #include <stdlib.h>
44277d787fSFarid Zakaria #include <string.h>
45277d787fSFarid Zakaria #include <unistd.h>
46277d787fSFarid Zakaria #include <fcntl.h>
47277d787fSFarid Zakaria 
48277d787fSFarid Zakaria #include <bpf/btf.h>
49277d787fSFarid Zakaria #include <bpf/libbpf.h>
50277d787fSFarid Zakaria 
5126860105SChristian Brauner #include "binfmt_misc_common.h"
5226860105SChristian Brauner #include "kselftest_harness.h"
5326860105SChristian Brauner 
54277d787fSFarid Zakaria #define INTERP_PATH	"/tmp/binfmt_bpf_interp"
55277d787fSFarid Zakaria #define AARCH64_PATH	"/tmp/binfmt_bpf_aarch64"
5626860105SChristian Brauner #define RELOC_TEMPLATE	"/tmp/binfmt_relocXXXXXX"
577baee96fSChristian Brauner #define TRANS_INTERP	"/tmp/binfmt_transparent_interp"
587baee96fSChristian Brauner #define TRANS_PATH	"/tmp/binfmt_bpf_riscv"
59277d787fSFarid Zakaria #define EXPECT		"BPF_INTERP_RAN"
607baee96fSChristian Brauner #define TRANS_EXPECT	"TRANSPARENT_OK"
6187c50a58SChristian Brauner #define LOADER_INTERP	"/tmp/binfmt_loader_interp"
6287c50a58SChristian Brauner #define LOADER_PATH	"/tmp/binfmt_bpf_loader.ldrtest"
637404b147SChristian Brauner #define BIND_FIRST	"/tmp/binfmt_bind_first"
647404b147SChristian Brauner #define BIND_SECOND	"/tmp/binfmt_bind_second"
657404b147SChristian Brauner #define BIND_ARM_PATH	"/tmp/binfmt_bind_arm"
667404b147SChristian Brauner #define BIND_RISCV_PATH	"/tmp/binfmt_bind_riscv"
677404b147SChristian Brauner #define BIND_EXPECT	"BIND_RAN "
687404b147SChristian Brauner #define BIND_MAX	100
69*f2b69ea2SChristian Brauner #define INTERP_LIMIT	"/proc/sys/user/max_binfmt_misc_interpreters"
70*f2b69ea2SChristian Brauner /* Exit status of the binding child when it cannot set up a budget of its own. */
71*f2b69ea2SChristian Brauner #define BIND_NO_BUDGET	200
72277d787fSFarid Zakaria 
7326860105SChristian Brauner /* A minimal 64-bit little-endian ELF header, padded to the read size. */
7426860105SChristian Brauner static int create_fake_elf(const char *path, unsigned short machine)
75277d787fSFarid Zakaria {
76277d787fSFarid Zakaria 	unsigned char hdr[256] = {0};
77277d787fSFarid Zakaria 	int fd;
78277d787fSFarid Zakaria 
79277d787fSFarid Zakaria 	hdr[0] = 0x7f; hdr[1] = 'E'; hdr[2] = 'L'; hdr[3] = 'F';
8026860105SChristian Brauner 	hdr[4] = ELFCLASS64;
8126860105SChristian Brauner 	hdr[5] = ELFDATA2LSB;
8226860105SChristian Brauner 	hdr[6] = EV_CURRENT;
8326860105SChristian Brauner 	hdr[16] = ET_EXEC;
8426860105SChristian Brauner 	hdr[18] = machine & 0xff;	/* e_machine, little-endian */
8526860105SChristian Brauner 	hdr[19] = machine >> 8;
8626860105SChristian Brauner 	hdr[20] = EV_CURRENT;
87277d787fSFarid Zakaria 
8826860105SChristian Brauner 	unlink(path);
8926860105SChristian Brauner 	fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0755);
90277d787fSFarid Zakaria 	if (fd < 0)
91277d787fSFarid Zakaria 		return -1;
92277d787fSFarid Zakaria 	if (write(fd, hdr, sizeof(hdr)) != (ssize_t)sizeof(hdr)) {
93277d787fSFarid Zakaria 		close(fd);
94277d787fSFarid Zakaria 		return -1;
95277d787fSFarid Zakaria 	}
96277d787fSFarid Zakaria 	close(fd);
97277d787fSFarid Zakaria 	return 0;
98277d787fSFarid Zakaria }
99277d787fSFarid Zakaria 
1007404b147SChristian Brauner /*
1017404b147SChristian Brauner  * Register a 'B' entry for @handler. With @flags "D" the entry is created
1027404b147SChristian Brauner  * disabled, which is what leaves it open to being given interpreters.
1037404b147SChristian Brauner  */
1047404b147SChristian Brauner static int register_entry(const char *name, const char *handler,
1057404b147SChristian Brauner 			  const char *flags)
106277d787fSFarid Zakaria {
10726860105SChristian Brauner 	char rule[PATH_MAX];
108277d787fSFarid Zakaria 
1097404b147SChristian Brauner 	snprintf(rule, sizeof(rule), ":%s:B::::%s:%s", name, handler,
1107404b147SChristian Brauner 		 flags ? flags : "");
11126860105SChristian Brauner 	return write_reg(rule);
112277d787fSFarid Zakaria }
113277d787fSFarid Zakaria 
114277d787fSFarid Zakaria static int check_output(const char *cmd, const char *expected)
115277d787fSFarid Zakaria {
116277d787fSFarid Zakaria 	char buf[128];
117277d787fSFarid Zakaria 	FILE *fp;
118277d787fSFarid Zakaria 
119277d787fSFarid Zakaria 	fp = popen(cmd, "r");
120277d787fSFarid Zakaria 	if (!fp)
121277d787fSFarid Zakaria 		return -1;
122277d787fSFarid Zakaria 	if (!fgets(buf, sizeof(buf), fp)) {
123277d787fSFarid Zakaria 		pclose(fp);
124277d787fSFarid Zakaria 		return -1;
125277d787fSFarid Zakaria 	}
126277d787fSFarid Zakaria 	pclose(fp);
127277d787fSFarid Zakaria 	return strncmp(buf, expected, strlen(expected)) ? -1 : 0;
128277d787fSFarid Zakaria }
129277d787fSFarid Zakaria 
130145e675dSChristian Brauner /* Does the kernel BTF know struct binfmt_misc_ops (CONFIG_BINFMT_MISC_BPF)? */
131145e675dSChristian Brauner static bool have_binfmt_misc_ops(void)
132145e675dSChristian Brauner {
133145e675dSChristian Brauner 	struct btf *btf = btf__load_vmlinux_btf();
134145e675dSChristian Brauner 	bool have;
135145e675dSChristian Brauner 
136145e675dSChristian Brauner 	have = btf && btf__find_by_name_kind(btf, "binfmt_misc_ops",
137145e675dSChristian Brauner 					     BTF_KIND_STRUCT) >= 0;
138145e675dSChristian Brauner 	btf__free(btf);
139145e675dSChristian Brauner 	return have;
140145e675dSChristian Brauner }
141145e675dSChristian Brauner 
142145e675dSChristian Brauner /* The reason bpf handler cases cannot run here, NULL if they can. */
143145e675dSChristian Brauner static const char *bpf_handler_unsupported(void)
144145e675dSChristian Brauner {
145145e675dSChristian Brauner 	if (getuid() != 0)
146145e675dSChristian Brauner 		return "test must be run as root";
147145e675dSChristian Brauner 	if (!have_binfmt_misc_ops())
148145e675dSChristian Brauner 		return "no struct binfmt_misc_ops in the kernel BTF (CONFIG_BINFMT_MISC_BPF)";
149145e675dSChristian Brauner 	if (!binfmt_misc_available())
150145e675dSChristian Brauner 		return "no binfmt_misc";
151145e675dSChristian Brauner 	return NULL;
152145e675dSChristian Brauner }
153145e675dSChristian Brauner 
15487c50a58SChristian Brauner /* An attached handler with its 'B' entry activated. */
15587c50a58SChristian Brauner struct bpf_case {
15687c50a58SChristian Brauner 	struct bpf_object *obj;
15787c50a58SChristian Brauner 	struct bpf_link *link;
15887c50a58SChristian Brauner 	const char *entry;
15987c50a58SChristian Brauner };
16087c50a58SChristian Brauner 
161277d787fSFarid Zakaria /*
162277d787fSFarid Zakaria  * Load @objfile, attach its struct_ops map @handler (which publishes the
1637404b147SChristian Brauner  * handler) and register a 'B' entry named @entry that references it, with
1647404b147SChristian Brauner  * @flags as the entry's register-string flags.
165277d787fSFarid Zakaria  */
1667404b147SChristian Brauner static int bpf_case_start_flags(struct bpf_case *c, const char *objfile,
1677404b147SChristian Brauner 				const char *handler, const char *entry,
1687404b147SChristian Brauner 				const char *flags)
169277d787fSFarid Zakaria {
170277d787fSFarid Zakaria 	struct bpf_map *map;
171277d787fSFarid Zakaria 
17287c50a58SChristian Brauner 	c->obj = NULL;
17387c50a58SChristian Brauner 	c->link = NULL;
17487c50a58SChristian Brauner 	c->entry = entry;
17587c50a58SChristian Brauner 
17687c50a58SChristian Brauner 	c->obj = bpf_object__open_file(objfile, NULL);
17787c50a58SChristian Brauner 	if (!c->obj || libbpf_get_error(c->obj)) {
178277d787fSFarid Zakaria 		fprintf(stderr, "open %s failed\n", objfile);
17987c50a58SChristian Brauner 		c->obj = NULL;
180277d787fSFarid Zakaria 		return -1;
181277d787fSFarid Zakaria 	}
18287c50a58SChristian Brauner 	if (bpf_object__load(c->obj)) {
183277d787fSFarid Zakaria 		fprintf(stderr, "load %s failed (check dmesg for the verifier log)\n",
184277d787fSFarid Zakaria 			objfile);
18587c50a58SChristian Brauner 		goto fail;
186277d787fSFarid Zakaria 	}
18787c50a58SChristian Brauner 	map = bpf_object__find_map_by_name(c->obj, handler);
188277d787fSFarid Zakaria 	if (!map) {
189277d787fSFarid Zakaria 		fprintf(stderr, "no struct_ops map '%s' in %s\n", handler, objfile);
19087c50a58SChristian Brauner 		goto fail;
191277d787fSFarid Zakaria 	}
19287c50a58SChristian Brauner 	c->link = bpf_map__attach_struct_ops(map);
19387c50a58SChristian Brauner 	if (!c->link || libbpf_get_error(c->link)) {
194277d787fSFarid Zakaria 		fprintf(stderr, "attach struct_ops '%s' failed\n", handler);
19587c50a58SChristian Brauner 		c->link = NULL;
19687c50a58SChristian Brauner 		goto fail;
197277d787fSFarid Zakaria 	}
1987404b147SChristian Brauner 	if (register_entry(entry, handler, flags)) {
199277d787fSFarid Zakaria 		fprintf(stderr, "register 'B' entry '%s' failed\n", entry);
20087c50a58SChristian Brauner 		goto fail;
201277d787fSFarid Zakaria 	}
20287c50a58SChristian Brauner 	return 0;
20387c50a58SChristian Brauner 
20487c50a58SChristian Brauner fail:
20587c50a58SChristian Brauner 	bpf_link__destroy(c->link);
20687c50a58SChristian Brauner 	bpf_object__close(c->obj);
20787c50a58SChristian Brauner 	c->obj = NULL;
20887c50a58SChristian Brauner 	c->link = NULL;
20987c50a58SChristian Brauner 	return -1;
21087c50a58SChristian Brauner }
21187c50a58SChristian Brauner 
2127404b147SChristian Brauner static int bpf_case_start(struct bpf_case *c, const char *objfile,
2137404b147SChristian Brauner 			  const char *handler, const char *entry)
2147404b147SChristian Brauner {
2157404b147SChristian Brauner 	return bpf_case_start_flags(c, objfile, handler, entry, NULL);
2167404b147SChristian Brauner }
2177404b147SChristian Brauner 
21887c50a58SChristian Brauner static void bpf_case_stop(struct bpf_case *c)
21987c50a58SChristian Brauner {
22087c50a58SChristian Brauner 	unregister(c->entry);
22187c50a58SChristian Brauner 	bpf_link__destroy(c->link);
22287c50a58SChristian Brauner 	bpf_object__close(c->obj);
22387c50a58SChristian Brauner }
22487c50a58SChristian Brauner 
22587c50a58SChristian Brauner /* Activate @handler, run @target and check it produced @expect. */
22687c50a58SChristian Brauner static int run_case(const char *objfile, const char *handler,
22787c50a58SChristian Brauner 		    const char *entry, const char *target, const char *expect)
22887c50a58SChristian Brauner {
22987c50a58SChristian Brauner 	struct bpf_case c;
23087c50a58SChristian Brauner 	int ret;
23187c50a58SChristian Brauner 
23287c50a58SChristian Brauner 	if (bpf_case_start(&c, objfile, handler, entry))
23387c50a58SChristian Brauner 		return -1;
234277d787fSFarid Zakaria 	ret = check_output(target, expect);
23587c50a58SChristian Brauner 	bpf_case_stop(&c);
236277d787fSFarid Zakaria 	return ret;
237277d787fSFarid Zakaria }
238277d787fSFarid Zakaria 
23926860105SChristian Brauner FIXTURE(bpf_handler) {
24026860105SChristian Brauner 	char obj[PATH_MAX];	/* struct_ops object of the case under test */
24126860105SChristian Brauner };
24226860105SChristian Brauner 
24326860105SChristian Brauner FIXTURE_SETUP(bpf_handler)
244277d787fSFarid Zakaria {
24526860105SChristian Brauner 	char src[PATH_MAX];
246145e675dSChristian Brauner 	const char *why = bpf_handler_unsupported();
247277d787fSFarid Zakaria 
248145e675dSChristian Brauner 	if (why)
249145e675dSChristian Brauner 		SKIP(return, "%s", why);
250277d787fSFarid Zakaria 
251277d787fSFarid Zakaria 	/* Shared test interpreter. */
25226860105SChristian Brauner 	ASSERT_EQ(artifact_path(src, sizeof(src), "binfmt_bpf_interp"), 0);
25326860105SChristian Brauner 	ASSERT_EQ(copy_file(src, INTERP_PATH), 0);
254277d787fSFarid Zakaria }
255277d787fSFarid Zakaria 
25626860105SChristian Brauner FIXTURE_TEARDOWN(bpf_handler)
25726860105SChristian Brauner {
258277d787fSFarid Zakaria 	unlink(INTERP_PATH);
259277d787fSFarid Zakaria }
26026860105SChristian Brauner 
26126860105SChristian Brauner /* The match program matches a synthetic header, the load program routes it. */
26226860105SChristian Brauner TEST_F(bpf_handler, fixed_interpreter)
26326860105SChristian Brauner {
26426860105SChristian Brauner 	ASSERT_EQ(create_fake_elf(AARCH64_PATH, EM_AARCH64), 0);
26526860105SChristian Brauner 	ASSERT_EQ(artifact_path(self->obj, sizeof(self->obj),
26626860105SChristian Brauner 				"bpf_interp.bpf.o"), 0);
26726860105SChristian Brauner 	EXPECT_EQ(run_case(self->obj, "bpf_interp", "test_bpf_interp",
26826860105SChristian Brauner 			   AARCH64_PATH, EXPECT), 0);
26926860105SChristian Brauner 	unlink(AARCH64_PATH);
27026860105SChristian Brauner }
27126860105SChristian Brauner 
27226860105SChristian Brauner /* A "$ORIGIN/..." PT_INTERP resolved to an interpreter next to the binary. */
27326860105SChristian Brauner TEST_F(bpf_handler, origin_relative_interpreter)
27426860105SChristian Brauner {
27526860105SChristian Brauner 	char src[PATH_MAX], app[PATH_MAX], interp[PATH_MAX];
27626860105SChristian Brauner 	char dir[] = RELOC_TEMPLATE;
27726860105SChristian Brauner 
27826860105SChristian Brauner 	ASSERT_NE(mkdtemp(dir), NULL);
27926860105SChristian Brauner 	snprintf(app, sizeof(app), "%s/app", dir);
28026860105SChristian Brauner 	snprintf(interp, sizeof(interp), "%s/binfmt_bpf_interp", dir);
28126860105SChristian Brauner 	ASSERT_EQ(artifact_path(src, sizeof(src), "binfmt_bpf_app"), 0);
28226860105SChristian Brauner 	ASSERT_EQ(copy_file(src, app), 0);
28326860105SChristian Brauner 	ASSERT_EQ(copy_file(INTERP_PATH, interp), 0);
28426860105SChristian Brauner 
28526860105SChristian Brauner 	ASSERT_EQ(artifact_path(self->obj, sizeof(self->obj),
28626860105SChristian Brauner 				"nix_origin.bpf.o"), 0);
28726860105SChristian Brauner 	EXPECT_EQ(run_case(self->obj, "nix_origin", "test_bpf_origin",
28826860105SChristian Brauner 			   app, EXPECT), 0);
28926860105SChristian Brauner 
29026860105SChristian Brauner 	unlink(app);
29126860105SChristian Brauner 	unlink(interp);
29226860105SChristian Brauner 	rmdir(dir);
29326860105SChristian Brauner }
29426860105SChristian Brauner 
2957baee96fSChristian Brauner /* A transparent dispatch: the process presents as the binary, not the interp. */
2967baee96fSChristian Brauner TEST_F(bpf_handler, transparent_dispatch)
2977baee96fSChristian Brauner {
2987baee96fSChristian Brauner 	char src[PATH_MAX], cmd[PATH_MAX + 16];
2997baee96fSChristian Brauner 
3007baee96fSChristian Brauner 	/* Probe for transparent-mode support via its static counterpart. */
301686585ecSChristian Brauner 	if (!binfmt_flag_supported('T'))
3027baee96fSChristian Brauner 		SKIP(return, "kernel without transparent mode");
3037baee96fSChristian Brauner 
3047baee96fSChristian Brauner 	ASSERT_EQ(artifact_path(src, sizeof(src), "binfmt_transparent_interp"), 0);
3057baee96fSChristian Brauner 	ASSERT_EQ(copy_file(src, TRANS_INTERP), 0);
3067baee96fSChristian Brauner 	ASSERT_EQ(create_fake_elf(TRANS_PATH, EM_RISCV), 0);
3077baee96fSChristian Brauner 
3087baee96fSChristian Brauner 	setenv("BINFMT_TEST_BINARY", TRANS_PATH, 1);
3097baee96fSChristian Brauner 	snprintf(cmd, sizeof(cmd), "%s argone argtwo", TRANS_PATH);
3107baee96fSChristian Brauner 	ASSERT_EQ(artifact_path(self->obj, sizeof(self->obj),
3117baee96fSChristian Brauner 				"transparent.bpf.o"), 0);
3127baee96fSChristian Brauner 	EXPECT_EQ(run_case(self->obj, "transparent", "test_bpf_transparent",
3137baee96fSChristian Brauner 			   cmd, TRANS_EXPECT), 0);
3147baee96fSChristian Brauner 
3157baee96fSChristian Brauner 	unlink(TRANS_PATH);
3167baee96fSChristian Brauner 	unlink(TRANS_INTERP);
3177baee96fSChristian Brauner }
3187baee96fSChristian Brauner 
31987c50a58SChristian Brauner /* A per-exec loader substitution: the payload runs as a native exec. */
32087c50a58SChristian Brauner TEST_F(bpf_handler, loader_substitution)
32187c50a58SChristian Brauner {
32287c50a58SChristian Brauner 	char src[PATH_MAX], loader[PATH_MAX];
32387c50a58SChristian Brauner 	struct bpf_case c;
32487c50a58SChristian Brauner 	int status;
32587c50a58SChristian Brauner 
32687c50a58SChristian Brauner 	if (find_loader(loader, sizeof(loader)))
32787c50a58SChristian Brauner 		SKIP(return, "cannot determine own PT_INTERP");
32887c50a58SChristian Brauner 
32987c50a58SChristian Brauner 	ASSERT_EQ(copy_file(loader, LOADER_INTERP), 0);
33087c50a58SChristian Brauner 	ASSERT_EQ(artifact_path(src, sizeof(src), "binfmt_loader_payload"), 0);
33187c50a58SChristian Brauner 	ASSERT_EQ(copy_file(src, LOADER_PATH), 0);
33287c50a58SChristian Brauner 	ASSERT_EQ(patch_file(LOADER_PATH, EI_PAD, LOADER_MARKER,
33387c50a58SChristian Brauner 			     strlen(LOADER_MARKER)), 0);
33487c50a58SChristian Brauner 	ASSERT_EQ(artifact_path(self->obj, sizeof(self->obj),
33587c50a58SChristian Brauner 				"loader.bpf.o"), 0);
33687c50a58SChristian Brauner 
33787c50a58SChristian Brauner 	setenv("BINFMT_TEST_BINARY", LOADER_PATH, 1);
33887c50a58SChristian Brauner 	setenv("BINFMT_TEST_INTERP", LOADER_INTERP, 1);
33987c50a58SChristian Brauner 
34087c50a58SChristian Brauner 	ASSERT_EQ(bpf_case_start(&c, self->obj, "loader", "test_bpf_loader"), 0);
34187c50a58SChristian Brauner 	status = run_payload(LOADER_PATH);
34287c50a58SChristian Brauner 	bpf_case_stop(&c);
34387c50a58SChristian Brauner 	EXPECT_EQ(status, 0);
34487c50a58SChristian Brauner 
34587c50a58SChristian Brauner 	unsetenv("BINFMT_TEST_INTERP");
34687c50a58SChristian Brauner 	unlink(LOADER_PATH);
34787c50a58SChristian Brauner 	unlink(LOADER_INTERP);
34887c50a58SChristian Brauner }
34987c50a58SChristian Brauner 
3507404b147SChristian Brauner /* The errno an exec of @path fails with, 0 if it succeeded. */
3517404b147SChristian Brauner static int exec_errno(const char *path)
3527404b147SChristian Brauner {
3537404b147SChristian Brauner 	int status;
3547404b147SChristian Brauner 	pid_t pid;
3557404b147SChristian Brauner 
3567404b147SChristian Brauner 	pid = fork();
3577404b147SChristian Brauner 	if (pid == 0) {
3587404b147SChristian Brauner 		execl(path, path, (char *)NULL);
3597404b147SChristian Brauner 		_exit(errno);
3607404b147SChristian Brauner 	}
3617404b147SChristian Brauner 	if (pid < 0 || waitpid(pid, &status, 0) != pid || !WIFEXITED(status))
3627404b147SChristian Brauner 		return -1;
3637404b147SChristian Brauner 	return WEXITSTATUS(status);
3647404b147SChristian Brauner }
3657404b147SChristian Brauner 
3667404b147SChristian Brauner /* Install a copy of the bound-interpreter test binary at @path. */
3677404b147SChristian Brauner static int install_interp(const char *path)
3687404b147SChristian Brauner {
3697404b147SChristian Brauner 	char src[PATH_MAX];
3707404b147SChristian Brauner 
3717404b147SChristian Brauner 	if (artifact_path(src, sizeof(src), "binfmt_bind_interp"))
3727404b147SChristian Brauner 		return -1;
3737404b147SChristian Brauner 	return copy_file(src, path);
3747404b147SChristian Brauner }
3757404b147SChristian Brauner 
3767404b147SChristian Brauner /* Bind @path to @entry under @name, the '+' command of a disabled entry. */
3777404b147SChristian Brauner static int entry_bind(const char *entry, const char *name, const char *path)
3787404b147SChristian Brauner {
3797404b147SChristian Brauner 	char cmd[PATH_MAX];
3807404b147SChristian Brauner 
3817404b147SChristian Brauner 	snprintf(cmd, sizeof(cmd), "+%s %s\n", name, path);
3827404b147SChristian Brauner 	return entry_command(entry, cmd);
3837404b147SChristian Brauner }
3847404b147SChristian Brauner 
385*f2b69ea2SChristian Brauner /* Set the interpreter budget of this namespace. */
386*f2b69ea2SChristian Brauner static int write_interp_limit(const char *val)
387*f2b69ea2SChristian Brauner {
388*f2b69ea2SChristian Brauner 	ssize_t n;
389*f2b69ea2SChristian Brauner 	int fd;
390*f2b69ea2SChristian Brauner 
391*f2b69ea2SChristian Brauner 	fd = open(INTERP_LIMIT, O_WRONLY | O_CLOEXEC);
392*f2b69ea2SChristian Brauner 	if (fd < 0)
393*f2b69ea2SChristian Brauner 		return -1;
394*f2b69ea2SChristian Brauner 	n = write(fd, val, strlen(val));
395*f2b69ea2SChristian Brauner 	close(fd);
396*f2b69ea2SChristian Brauner 	return n < 0 ? -1 : 0;
397*f2b69ea2SChristian Brauner }
398*f2b69ea2SChristian Brauner 
399*f2b69ea2SChristian Brauner /*
400*f2b69ea2SChristian Brauner  * The errno a bind is refused with when the writer is a child that has spent
401*f2b69ea2SChristian Brauner  * the budget of a user namespace of its own, 0 if it succeeded and -1 if the
402*f2b69ea2SChristian Brauner  * child could not set itself up. The fd is opened here and inherited, so the
403*f2b69ea2SChristian Brauner  * interpreter is still opened with this process's credentials.
404*f2b69ea2SChristian Brauner  */
405*f2b69ea2SChristian Brauner static int bind_out_of_budget(const char *entry, const char *name,
406*f2b69ea2SChristian Brauner 			      const char *path)
407*f2b69ea2SChristian Brauner {
408*f2b69ea2SChristian Brauner 	char cmd[PATH_MAX], file[PATH_MAX];
409*f2b69ea2SChristian Brauner 	int fd, status, retval;
410*f2b69ea2SChristian Brauner 	pid_t pid;
411*f2b69ea2SChristian Brauner 
412*f2b69ea2SChristian Brauner 	snprintf(file, sizeof(file), BINFMT_DIR "/%s", entry);
413*f2b69ea2SChristian Brauner 	snprintf(cmd, sizeof(cmd), "+%s %s\n", name, path);
414*f2b69ea2SChristian Brauner 
415*f2b69ea2SChristian Brauner 	fd = open(file, O_WRONLY | O_CLOEXEC);
416*f2b69ea2SChristian Brauner 	if (fd < 0)
417*f2b69ea2SChristian Brauner 		return -1;
418*f2b69ea2SChristian Brauner 
419*f2b69ea2SChristian Brauner 	pid = fork();
420*f2b69ea2SChristian Brauner 	if (pid == 0) {
421*f2b69ea2SChristian Brauner 		ssize_t n;
422*f2b69ea2SChristian Brauner 
423*f2b69ea2SChristian Brauner 		/* A namespace of its own, with nothing left in it to spend. */
424*f2b69ea2SChristian Brauner 		if (unshare(CLONE_NEWUSER) || write_interp_limit("0"))
425*f2b69ea2SChristian Brauner 			_exit(BIND_NO_BUDGET);
426*f2b69ea2SChristian Brauner 		n = write(fd, cmd, strlen(cmd));
427*f2b69ea2SChristian Brauner 		_exit(n < 0 ? errno : 0);
428*f2b69ea2SChristian Brauner 	}
429*f2b69ea2SChristian Brauner 	close(fd);
430*f2b69ea2SChristian Brauner 	if (pid < 0 || waitpid(pid, &status, 0) != pid || !WIFEXITED(status))
431*f2b69ea2SChristian Brauner 		return -1;
432*f2b69ea2SChristian Brauner 	retval = WEXITSTATUS(status);
433*f2b69ea2SChristian Brauner 	return retval == BIND_NO_BUDGET ? -1 : retval;
434*f2b69ea2SChristian Brauner }
435*f2b69ea2SChristian Brauner 
4367404b147SChristian Brauner FIXTURE(bound_interp) {
4377404b147SChristian Brauner 	char obj[PATH_MAX];
4387404b147SChristian Brauner 	struct bpf_case c;
4397404b147SChristian Brauner 	bool started;
4407404b147SChristian Brauner };
4417404b147SChristian Brauner 
4427404b147SChristian Brauner FIXTURE_SETUP(bound_interp)
4437404b147SChristian Brauner {
4447404b147SChristian Brauner 	const char *why = bpf_handler_unsupported();
4457404b147SChristian Brauner 
4467404b147SChristian Brauner 	if (why)
4477404b147SChristian Brauner 		SKIP(return, "%s", why);
4487404b147SChristian Brauner 	if (!binfmt_flag_supported('D')) {
4497404b147SChristian Brauner 		ASSERT_EQ(errno, EINVAL);
4507404b147SChristian Brauner 		SKIP(return, "kernel without the 'D' flag");
4517404b147SChristian Brauner 	}
4527404b147SChristian Brauner 
4537404b147SChristian Brauner 	ASSERT_EQ(install_interp(BIND_FIRST), 0);
4547404b147SChristian Brauner 	ASSERT_EQ(install_interp(BIND_SECOND), 0);
4557404b147SChristian Brauner 
4567404b147SChristian Brauner 	ASSERT_EQ(artifact_path(self->obj, sizeof(self->obj),
4577404b147SChristian Brauner 				"interp_bind.bpf.o"), 0);
4587404b147SChristian Brauner 
4597404b147SChristian Brauner 	/*
4607404b147SChristian Brauner 	 * Registered disabled, so it cannot be matched yet and can still be
4617404b147SChristian Brauner 	 * given interpreters. Each path is resolved once, by its write(2);
4627404b147SChristian Brauner 	 * from here on the entry holds the files themselves.
4637404b147SChristian Brauner 	 */
4647404b147SChristian Brauner 	ASSERT_EQ(bpf_case_start_flags(&self->c, self->obj, "interp_bind",
4657404b147SChristian Brauner 				       "test_interp_bind", "D"), 0);
4667404b147SChristian Brauner 	self->started = true;
4677404b147SChristian Brauner 
4687404b147SChristian Brauner 	ASSERT_EQ(entry_bind("test_interp_bind", "first", BIND_FIRST), 0);
4697404b147SChristian Brauner 	ASSERT_EQ(entry_bind("test_interp_bind", "second", BIND_SECOND), 0);
4707404b147SChristian Brauner }
4717404b147SChristian Brauner 
4727404b147SChristian Brauner FIXTURE_TEARDOWN(bound_interp)
4737404b147SChristian Brauner {
4747404b147SChristian Brauner 	if (self->started)
4757404b147SChristian Brauner 		bpf_case_stop(&self->c);
4767404b147SChristian Brauner 	unlink(BIND_FIRST);
4777404b147SChristian Brauner 	unlink(BIND_SECOND);
4787404b147SChristian Brauner 	unlink(AARCH64_PATH);
4797404b147SChristian Brauner 	unlink(BIND_RISCV_PATH);
4807404b147SChristian Brauner 	unlink(BIND_ARM_PATH);
4817404b147SChristian Brauner }
4827404b147SChristian Brauner 
4837404b147SChristian Brauner /* Enabling is what makes the configured entry matchable. */
4847404b147SChristian Brauner static int activate(const char *entry)
4857404b147SChristian Brauner {
4867404b147SChristian Brauner 	return entry_command(entry, "1\n");
4877404b147SChristian Brauner }
4887404b147SChristian Brauner 
4897404b147SChristian Brauner /* One entry, one interpreter per guest architecture, picked per exec. */
4907404b147SChristian Brauner TEST_F(bound_interp, selects_by_name)
4917404b147SChristian Brauner {
4927404b147SChristian Brauner 	ASSERT_EQ(create_fake_elf(AARCH64_PATH, EM_AARCH64), 0);
4937404b147SChristian Brauner 	ASSERT_EQ(create_fake_elf(BIND_RISCV_PATH, EM_RISCV), 0);
4947404b147SChristian Brauner 
4957404b147SChristian Brauner 	/* Disabled, so it does not match and no format claims the binary. */
4967404b147SChristian Brauner 	EXPECT_EQ(exec_errno(AARCH64_PATH), ENOEXEC);
4977404b147SChristian Brauner 
4987404b147SChristian Brauner 	ASSERT_EQ(activate("test_interp_bind"), 0);
4997404b147SChristian Brauner 	EXPECT_EQ(check_output(AARCH64_PATH, BIND_EXPECT BIND_FIRST), 0);
5007404b147SChristian Brauner 	EXPECT_EQ(check_output(BIND_RISCV_PATH, BIND_EXPECT BIND_SECOND), 0);
5017404b147SChristian Brauner }
5027404b147SChristian Brauner 
5037404b147SChristian Brauner /* What was bound is what runs, whatever the path holds afterwards. */
5047404b147SChristian Brauner TEST_F(bound_interp, path_no_longer_decides)
5057404b147SChristian Brauner {
5067404b147SChristian Brauner 	char other[PATH_MAX];
5077404b147SChristian Brauner 
5087404b147SChristian Brauner 	ASSERT_EQ(create_fake_elf(AARCH64_PATH, EM_AARCH64), 0);
5097404b147SChristian Brauner 	ASSERT_EQ(activate("test_interp_bind"), 0);
5107404b147SChristian Brauner 
5117404b147SChristian Brauner 	/* Bound interpreters are pinned against writes, exactly like 'F'. */
5127404b147SChristian Brauner 	EXPECT_TRUE(write_denied(BIND_FIRST));
5137404b147SChristian Brauner 
5147404b147SChristian Brauner 	/* Replace the path with a different binary: a new file, new inode. */
5157404b147SChristian Brauner 	ASSERT_EQ(artifact_path(other, sizeof(other), "binfmt_bpf_interp"), 0);
5167404b147SChristian Brauner 	ASSERT_EQ(unlink(BIND_FIRST), 0);
5177404b147SChristian Brauner 	ASSERT_EQ(copy_file(other, BIND_FIRST), 0);
5187404b147SChristian Brauner 
5197404b147SChristian Brauner 	EXPECT_EQ(check_output(AARCH64_PATH, BIND_EXPECT BIND_FIRST), 0);
5207404b147SChristian Brauner }
5217404b147SChristian Brauner 
5227404b147SChristian Brauner /* The entry reports what it bound, under the names it bound them as. */
5237404b147SChristian Brauner TEST_F(bound_interp, entry_reports_bindings)
5247404b147SChristian Brauner {
5257404b147SChristian Brauner 	EXPECT_TRUE(entry_shows("test_interp_bind",
5267404b147SChristian Brauner 				"bpf-interpreter first " BIND_FIRST));
5277404b147SChristian Brauner 	EXPECT_TRUE(entry_shows("test_interp_bind",
5287404b147SChristian Brauner 				"bpf-interpreter second " BIND_SECOND));
5297404b147SChristian Brauner }
5307404b147SChristian Brauner 
5317404b147SChristian Brauner /* Selecting a name the entry did not bind fails the exec. */
5327404b147SChristian Brauner TEST_F(bound_interp, unbound_name_fails)
5337404b147SChristian Brauner {
5347404b147SChristian Brauner 	ASSERT_EQ(create_fake_elf(BIND_ARM_PATH, EM_ARM), 0);
5357404b147SChristian Brauner 	ASSERT_EQ(activate("test_interp_bind"), 0);
5367404b147SChristian Brauner 
5377404b147SChristian Brauner 	EXPECT_EQ(exec_errno(BIND_ARM_PATH), ENOENT);
5387404b147SChristian Brauner }
5397404b147SChristian Brauner 
5407404b147SChristian Brauner /* Activating seals it: what can be matched cannot be changed. */
5417404b147SChristian Brauner TEST_F(bound_interp, sealed_once_active)
5427404b147SChristian Brauner {
5437404b147SChristian Brauner 	ASSERT_EQ(activate("test_interp_bind"), 0);
5447404b147SChristian Brauner 
5457404b147SChristian Brauner 	EXPECT_EQ(entry_bind("test_interp_bind", "third", BIND_SECOND), -EBUSY);
5467404b147SChristian Brauner 	EXPECT_FALSE(entry_shows("test_interp_bind",
5477404b147SChristian Brauner 				 "bpf-interpreter third " BIND_SECOND));
5487404b147SChristian Brauner }
5497404b147SChristian Brauner 
5507404b147SChristian Brauner /* The seal is for good: disabling the entry again reopens nothing. */
5517404b147SChristian Brauner TEST_F(bound_interp, disable_does_not_unseal)
5527404b147SChristian Brauner {
5537404b147SChristian Brauner 	ASSERT_EQ(activate("test_interp_bind"), 0);
5547404b147SChristian Brauner 	ASSERT_EQ(entry_command("test_interp_bind", "0\n"), 0);
5557404b147SChristian Brauner 
5567404b147SChristian Brauner 	EXPECT_EQ(entry_bind("test_interp_bind", "third", BIND_SECOND), -EBUSY);
5577404b147SChristian Brauner }
5587404b147SChristian Brauner 
5597404b147SChristian Brauner /* An entry registered without 'D' is sealed from the start. */
5607404b147SChristian Brauner TEST_F(bound_interp, born_sealed)
5617404b147SChristian Brauner {
5627404b147SChristian Brauner 	/* A second entry for the handler the fixture already published. */
5637404b147SChristian Brauner 	ASSERT_EQ(register_entry("test_born_sealed", "interp_bind", NULL), 0);
5647404b147SChristian Brauner 
5657404b147SChristian Brauner 	EXPECT_EQ(entry_bind("test_born_sealed", "first", BIND_FIRST), -EBUSY);
5667404b147SChristian Brauner 	unregister("test_born_sealed");
5677404b147SChristian Brauner }
5687404b147SChristian Brauner 
5697404b147SChristian Brauner /* A name is bound once; a second use of it is refused. */
5707404b147SChristian Brauner TEST_F(bound_interp, duplicate_name_refused)
5717404b147SChristian Brauner {
5727404b147SChristian Brauner 	EXPECT_EQ(entry_bind("test_interp_bind", "first", BIND_SECOND), -EEXIST);
5737404b147SChristian Brauner }
5747404b147SChristian Brauner 
5757404b147SChristian Brauner /* A name is a printable word: the entry file reports 'name path' lines. */
5767404b147SChristian Brauner TEST_F(bound_interp, name_must_be_printable)
5777404b147SChristian Brauner {
5787404b147SChristian Brauner 	/* A control character would forge a line into the entry file. */
5797404b147SChristian Brauner 	EXPECT_EQ(entry_bind("test_interp_bind", "a\tb", BIND_FIRST), -EINVAL);
5807404b147SChristian Brauner 	EXPECT_EQ(entry_bind("test_interp_bind", "a\nb", BIND_FIRST), -EINVAL);
5817404b147SChristian Brauner 
5827404b147SChristian Brauner 	/* A space cannot even be spelled: the path starts after the first one. */
5837404b147SChristian Brauner 	EXPECT_EQ(entry_bind("test_interp_bind", "a b", BIND_FIRST), -EINVAL);
5847404b147SChristian Brauner }
5857404b147SChristian Brauner 
5867404b147SChristian Brauner /* The command ends at the write: bytes past an embedded nul are refused. */
5877404b147SChristian Brauner TEST_F(bound_interp, trailing_bytes_refused)
5887404b147SChristian Brauner {
5897404b147SChristian Brauner 	char cmd[PATH_MAX];
5907404b147SChristian Brauner 	size_t len;
5917404b147SChristian Brauner 	int fd;
5927404b147SChristian Brauner 
5937404b147SChristian Brauner 	/* entry_command() cannot spell a nul, so write the buffer raw. */
5947404b147SChristian Brauner 	snprintf(cmd, sizeof(cmd), "+nul %s", BIND_FIRST);
5957404b147SChristian Brauner 	len = strlen(cmd) + 1;
5967404b147SChristian Brauner 	memcpy(cmd + len, "junk", sizeof("junk"));
5977404b147SChristian Brauner 	len += sizeof("junk");
5987404b147SChristian Brauner 
5997404b147SChristian Brauner 	fd = open(BINFMT_DIR "/test_interp_bind", O_WRONLY | O_CLOEXEC);
6007404b147SChristian Brauner 	ASSERT_GE(fd, 0);
6017404b147SChristian Brauner 	EXPECT_EQ(write(fd, cmd, len), -1);
6027404b147SChristian Brauner 	EXPECT_EQ(errno, EINVAL);
6037404b147SChristian Brauner 	close(fd);
6047404b147SChristian Brauner 
6057404b147SChristian Brauner 	EXPECT_FALSE(entry_shows("test_interp_bind",
6067404b147SChristian Brauner 				 "bpf-interpreter nul " BIND_FIRST));
6077404b147SChristian Brauner }
6087404b147SChristian Brauner 
6097404b147SChristian Brauner /* An entry binds at most BIND_MAX interpreters. */
6107404b147SChristian Brauner TEST_F(bound_interp, capped_bindings)
6117404b147SChristian Brauner {
6127404b147SChristian Brauner 	char name[16];
6137404b147SChristian Brauner 	int i;
6147404b147SChristian Brauner 
6157404b147SChristian Brauner 	/* The fixture bound "first" and "second" already. */
6167404b147SChristian Brauner 	for (i = 2; i < BIND_MAX; i++) {
6177404b147SChristian Brauner 		snprintf(name, sizeof(name), "n%d", i);
6187404b147SChristian Brauner 		ASSERT_EQ(entry_bind("test_interp_bind", name, BIND_FIRST), 0);
6197404b147SChristian Brauner 	}
6207404b147SChristian Brauner 	EXPECT_EQ(entry_bind("test_interp_bind", "over", BIND_FIRST), -ENOSPC);
6217404b147SChristian Brauner }
6227404b147SChristian Brauner 
623*f2b69ea2SChristian Brauner /* A binding pins a file: it is charged, and refused once the budget is out. */
624*f2b69ea2SChristian Brauner TEST_F(bound_interp, bindings_are_charged)
625*f2b69ea2SChristian Brauner {
626*f2b69ea2SChristian Brauner 	int err = bind_out_of_budget("test_interp_bind", "third", BIND_FIRST);
627*f2b69ea2SChristian Brauner 
628*f2b69ea2SChristian Brauner 	if (err < 0)
629*f2b69ea2SChristian Brauner 		SKIP(return, "no user namespaces or no " INTERP_LIMIT);
630*f2b69ea2SChristian Brauner 
631*f2b69ea2SChristian Brauner 	/* The charge follows the writer, not the entry file it writes to. */
632*f2b69ea2SChristian Brauner 	EXPECT_EQ(err, ENOSPC);
633*f2b69ea2SChristian Brauner 
634*f2b69ea2SChristian Brauner 	/* The budget was the only thing in the way. */
635*f2b69ea2SChristian Brauner 	EXPECT_EQ(entry_bind("test_interp_bind", "third", BIND_FIRST), 0);
636*f2b69ea2SChristian Brauner }
637*f2b69ea2SChristian Brauner 
63826860105SChristian Brauner TEST_HARNESS_MAIN
639