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