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