1*277d787fSFarid Zakaria // SPDX-License-Identifier: GPL-2.0 2*277d787fSFarid Zakaria /* 3*277d787fSFarid Zakaria * Selftest for binfmt_misc bpf-backed ('B') handlers. 4*277d787fSFarid Zakaria * 5*277d787fSFarid Zakaria * A handler is a struct binfmt_misc_ops struct_ops map with a sleepable match 6*277d787fSFarid Zakaria * and a sleepable load program. Attaching it publishes it by name in the 7*277d787fSFarid Zakaria * caller's user namespace; a 'B' entry referencing it by name in the 8*277d787fSFarid Zakaria * interpreter field activates it: 9*277d787fSFarid Zakaria * 10*277d787fSFarid Zakaria * echo ':name:B::::<handler>:' > /proc/sys/fs/binfmt_misc/register 11*277d787fSFarid Zakaria * 12*277d787fSFarid Zakaria * Two self-contained cases are exercised: 13*277d787fSFarid Zakaria * 14*277d787fSFarid Zakaria * 1. bpf_interp: the match program matches a synthetic aarch64 ELF header 15*277d787fSFarid Zakaria * from the prefetched bprm->buf and the load program routes it to a 16*277d787fSFarid Zakaria * fixed interpreter of its choosing. 17*277d787fSFarid Zakaria * 2. nix_origin: the match program reads the binary's program headers to 18*277d787fSFarid Zakaria * commit only to a "$ORIGIN/..."-relative PT_INTERP and the load program 19*277d787fSFarid Zakaria * resolves it to an interpreter co-located with the binary (the 20*277d787fSFarid Zakaria * relocatable-loader case the kernel ELF loader cannot express). 21*277d787fSFarid Zakaria * 22*277d787fSFarid Zakaria * Both route to a test interpreter that prints BPF_INTERP_RAN, proving the 23*277d787fSFarid Zakaria * program's chosen interpreter actually ran. 24*277d787fSFarid Zakaria */ 25*277d787fSFarid Zakaria #define _GNU_SOURCE 26*277d787fSFarid Zakaria #include <stdio.h> 27*277d787fSFarid Zakaria #include <stdlib.h> 28*277d787fSFarid Zakaria #include <string.h> 29*277d787fSFarid Zakaria #include <unistd.h> 30*277d787fSFarid Zakaria #include <fcntl.h> 31*277d787fSFarid Zakaria #include <libgen.h> 32*277d787fSFarid Zakaria #include <sys/mount.h> 33*277d787fSFarid Zakaria #include <sys/stat.h> 34*277d787fSFarid Zakaria 35*277d787fSFarid Zakaria #include <bpf/btf.h> 36*277d787fSFarid Zakaria #include <bpf/libbpf.h> 37*277d787fSFarid Zakaria 38*277d787fSFarid Zakaria #define INTERP_PATH "/tmp/binfmt_bpf_interp" 39*277d787fSFarid Zakaria #define AARCH64_PATH "/tmp/binfmt_bpf_aarch64" 40*277d787fSFarid Zakaria #define RELOC_DIR "/tmp/binfmt_reloc" 41*277d787fSFarid Zakaria #define BINFMT_REG "/proc/sys/fs/binfmt_misc/register" 42*277d787fSFarid Zakaria #define EXPECT "BPF_INTERP_RAN" 43*277d787fSFarid Zakaria 44*277d787fSFarid Zakaria static char testdir[512]; /* directory holding this test's built artifacts */ 45*277d787fSFarid Zakaria 46*277d787fSFarid Zakaria static int copy_file(const char *src, const char *dst) 47*277d787fSFarid Zakaria { 48*277d787fSFarid Zakaria char buf[4096]; 49*277d787fSFarid Zakaria int in, out; 50*277d787fSFarid Zakaria ssize_t n; 51*277d787fSFarid Zakaria 52*277d787fSFarid Zakaria in = open(src, O_RDONLY); 53*277d787fSFarid Zakaria if (in < 0) 54*277d787fSFarid Zakaria return -1; 55*277d787fSFarid Zakaria out = open(dst, O_WRONLY | O_CREAT | O_TRUNC, 0755); 56*277d787fSFarid Zakaria if (out < 0) { 57*277d787fSFarid Zakaria close(in); 58*277d787fSFarid Zakaria return -1; 59*277d787fSFarid Zakaria } 60*277d787fSFarid Zakaria while ((n = read(in, buf, sizeof(buf))) > 0) { 61*277d787fSFarid Zakaria if (write(out, buf, n) != n) { 62*277d787fSFarid Zakaria close(in); 63*277d787fSFarid Zakaria close(out); 64*277d787fSFarid Zakaria return -1; 65*277d787fSFarid Zakaria } 66*277d787fSFarid Zakaria } 67*277d787fSFarid Zakaria close(in); 68*277d787fSFarid Zakaria close(out); 69*277d787fSFarid Zakaria return n < 0 ? -1 : 0; 70*277d787fSFarid Zakaria } 71*277d787fSFarid Zakaria 72*277d787fSFarid Zakaria /* A minimal 64-bit little-endian aarch64 ELF header, padded to the read size. */ 73*277d787fSFarid Zakaria static int create_fake_aarch64(const char *path) 74*277d787fSFarid Zakaria { 75*277d787fSFarid Zakaria unsigned char hdr[256] = {0}; 76*277d787fSFarid Zakaria int fd; 77*277d787fSFarid Zakaria 78*277d787fSFarid Zakaria hdr[0] = 0x7f; hdr[1] = 'E'; hdr[2] = 'L'; hdr[3] = 'F'; 79*277d787fSFarid Zakaria hdr[4] = 2; /* ELFCLASS64 */ 80*277d787fSFarid Zakaria hdr[5] = 1; /* ELFDATA2LSB */ 81*277d787fSFarid Zakaria hdr[6] = 1; /* EV_CURRENT */ 82*277d787fSFarid Zakaria hdr[16] = 2; /* e_type = ET_EXEC */ 83*277d787fSFarid Zakaria hdr[18] = 183 & 0xff; /* e_machine = EM_AARCH64 */ 84*277d787fSFarid Zakaria hdr[19] = (183 >> 8) & 0xff; 85*277d787fSFarid Zakaria hdr[20] = 1; /* e_version */ 86*277d787fSFarid Zakaria 87*277d787fSFarid Zakaria fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0755); 88*277d787fSFarid Zakaria if (fd < 0) 89*277d787fSFarid Zakaria return -1; 90*277d787fSFarid Zakaria if (write(fd, hdr, sizeof(hdr)) != (ssize_t)sizeof(hdr)) { 91*277d787fSFarid Zakaria close(fd); 92*277d787fSFarid Zakaria return -1; 93*277d787fSFarid Zakaria } 94*277d787fSFarid Zakaria close(fd); 95*277d787fSFarid Zakaria return 0; 96*277d787fSFarid Zakaria } 97*277d787fSFarid Zakaria 98*277d787fSFarid Zakaria static int register_entry(const char *name, const char *handler) 99*277d787fSFarid Zakaria { 100*277d787fSFarid Zakaria char rule[128]; 101*277d787fSFarid Zakaria int fd; 102*277d787fSFarid Zakaria ssize_t n; 103*277d787fSFarid Zakaria 104*277d787fSFarid Zakaria snprintf(rule, sizeof(rule), ":%s:B::::%s:", name, handler); 105*277d787fSFarid Zakaria fd = open(BINFMT_REG, O_WRONLY); 106*277d787fSFarid Zakaria if (fd < 0) 107*277d787fSFarid Zakaria return -1; 108*277d787fSFarid Zakaria n = write(fd, rule, strlen(rule)); 109*277d787fSFarid Zakaria close(fd); 110*277d787fSFarid Zakaria return n < 0 ? -1 : 0; 111*277d787fSFarid Zakaria } 112*277d787fSFarid Zakaria 113*277d787fSFarid Zakaria static void unregister_entry(const char *name) 114*277d787fSFarid Zakaria { 115*277d787fSFarid Zakaria char path[128]; 116*277d787fSFarid Zakaria int fd; 117*277d787fSFarid Zakaria 118*277d787fSFarid Zakaria snprintf(path, sizeof(path), "/proc/sys/fs/binfmt_misc/%s", name); 119*277d787fSFarid Zakaria fd = open(path, O_WRONLY); 120*277d787fSFarid Zakaria if (fd >= 0) { 121*277d787fSFarid Zakaria if (write(fd, "-1", 2) < 0) 122*277d787fSFarid Zakaria ; /* best effort */ 123*277d787fSFarid Zakaria close(fd); 124*277d787fSFarid Zakaria } 125*277d787fSFarid Zakaria } 126*277d787fSFarid Zakaria 127*277d787fSFarid Zakaria static int check_output(const char *cmd, const char *expected) 128*277d787fSFarid Zakaria { 129*277d787fSFarid Zakaria char buf[128]; 130*277d787fSFarid Zakaria FILE *fp; 131*277d787fSFarid Zakaria 132*277d787fSFarid Zakaria fp = popen(cmd, "r"); 133*277d787fSFarid Zakaria if (!fp) 134*277d787fSFarid Zakaria return -1; 135*277d787fSFarid Zakaria if (!fgets(buf, sizeof(buf), fp)) { 136*277d787fSFarid Zakaria pclose(fp); 137*277d787fSFarid Zakaria return -1; 138*277d787fSFarid Zakaria } 139*277d787fSFarid Zakaria pclose(fp); 140*277d787fSFarid Zakaria return strncmp(buf, expected, strlen(expected)) ? -1 : 0; 141*277d787fSFarid Zakaria } 142*277d787fSFarid Zakaria 143*277d787fSFarid Zakaria /* 144*277d787fSFarid Zakaria * Load @objfile, attach its struct_ops map @handler (which publishes the 145*277d787fSFarid Zakaria * handler), activate a 'B' entry named @entry that references it, run @target 146*277d787fSFarid Zakaria * and check it produced @expect. 147*277d787fSFarid Zakaria */ 148*277d787fSFarid Zakaria static int run_case(const char *objfile, const char *handler, 149*277d787fSFarid Zakaria const char *entry, const char *target, const char *expect) 150*277d787fSFarid Zakaria { 151*277d787fSFarid Zakaria struct bpf_object *obj; 152*277d787fSFarid Zakaria struct bpf_map *map; 153*277d787fSFarid Zakaria struct bpf_link *link; 154*277d787fSFarid Zakaria int ret = -1; 155*277d787fSFarid Zakaria 156*277d787fSFarid Zakaria obj = bpf_object__open_file(objfile, NULL); 157*277d787fSFarid Zakaria if (!obj || libbpf_get_error(obj)) { 158*277d787fSFarid Zakaria fprintf(stderr, "open %s failed\n", objfile); 159*277d787fSFarid Zakaria return -1; 160*277d787fSFarid Zakaria } 161*277d787fSFarid Zakaria if (bpf_object__load(obj)) { 162*277d787fSFarid Zakaria fprintf(stderr, "load %s failed (check dmesg for the verifier log)\n", 163*277d787fSFarid Zakaria objfile); 164*277d787fSFarid Zakaria goto close; 165*277d787fSFarid Zakaria } 166*277d787fSFarid Zakaria map = bpf_object__find_map_by_name(obj, handler); 167*277d787fSFarid Zakaria if (!map) { 168*277d787fSFarid Zakaria fprintf(stderr, "no struct_ops map '%s' in %s\n", handler, objfile); 169*277d787fSFarid Zakaria goto close; 170*277d787fSFarid Zakaria } 171*277d787fSFarid Zakaria link = bpf_map__attach_struct_ops(map); 172*277d787fSFarid Zakaria if (!link || libbpf_get_error(link)) { 173*277d787fSFarid Zakaria fprintf(stderr, "attach struct_ops '%s' failed\n", handler); 174*277d787fSFarid Zakaria goto close; 175*277d787fSFarid Zakaria } 176*277d787fSFarid Zakaria if (register_entry(entry, handler)) { 177*277d787fSFarid Zakaria fprintf(stderr, "register 'B' entry '%s' failed\n", entry); 178*277d787fSFarid Zakaria goto detach; 179*277d787fSFarid Zakaria } 180*277d787fSFarid Zakaria ret = check_output(target, expect); 181*277d787fSFarid Zakaria unregister_entry(entry); 182*277d787fSFarid Zakaria detach: 183*277d787fSFarid Zakaria bpf_link__destroy(link); 184*277d787fSFarid Zakaria close: 185*277d787fSFarid Zakaria bpf_object__close(obj); 186*277d787fSFarid Zakaria return ret; 187*277d787fSFarid Zakaria } 188*277d787fSFarid Zakaria 189*277d787fSFarid Zakaria int main(void) 190*277d787fSFarid Zakaria { 191*277d787fSFarid Zakaria char src[600], obj[600], appdst[600], interpdst[600]; 192*277d787fSFarid Zakaria char exe[512]; 193*277d787fSFarid Zakaria ssize_t n; 194*277d787fSFarid Zakaria int fail = 0; 195*277d787fSFarid Zakaria struct stat st; 196*277d787fSFarid Zakaria struct btf *btf; 197*277d787fSFarid Zakaria 198*277d787fSFarid Zakaria if (getuid() != 0) { 199*277d787fSFarid Zakaria fprintf(stderr, "Skipping: test must be run as root\n"); 200*277d787fSFarid Zakaria return 4; /* KSFT_SKIP */ 201*277d787fSFarid Zakaria } 202*277d787fSFarid Zakaria 203*277d787fSFarid Zakaria /* The kernel must know struct binfmt_misc_ops (CONFIG_BINFMT_MISC_BPF). */ 204*277d787fSFarid Zakaria btf = btf__load_vmlinux_btf(); 205*277d787fSFarid Zakaria if (!btf || btf__find_by_name_kind(btf, "binfmt_misc_ops", 206*277d787fSFarid Zakaria BTF_KIND_STRUCT) < 0) { 207*277d787fSFarid Zakaria fprintf(stderr, 208*277d787fSFarid Zakaria "Skipping: no struct binfmt_misc_ops in the kernel BTF (CONFIG_BINFMT_MISC_BPF)\n"); 209*277d787fSFarid Zakaria btf__free(btf); 210*277d787fSFarid Zakaria return 4; /* KSFT_SKIP */ 211*277d787fSFarid Zakaria } 212*277d787fSFarid Zakaria btf__free(btf); 213*277d787fSFarid Zakaria 214*277d787fSFarid Zakaria n = readlink("/proc/self/exe", exe, sizeof(exe) - 1); 215*277d787fSFarid Zakaria if (n < 0) { 216*277d787fSFarid Zakaria perror("readlink"); 217*277d787fSFarid Zakaria return 1; 218*277d787fSFarid Zakaria } 219*277d787fSFarid Zakaria exe[n] = '\0'; 220*277d787fSFarid Zakaria snprintf(testdir, sizeof(testdir), "%s", dirname(exe)); 221*277d787fSFarid Zakaria 222*277d787fSFarid Zakaria if (stat("/sys/fs/bpf", &st) < 0) 223*277d787fSFarid Zakaria mkdir("/sys/fs/bpf", 0755); 224*277d787fSFarid Zakaria mount("bpf", "/sys/fs/bpf", "bpf", 0, NULL); 225*277d787fSFarid Zakaria if (access(BINFMT_REG, F_OK) < 0) 226*277d787fSFarid Zakaria mount("binfmt_misc", "/proc/sys/fs/binfmt_misc", "binfmt_misc", 0, NULL); 227*277d787fSFarid Zakaria 228*277d787fSFarid Zakaria /* Shared test interpreter. */ 229*277d787fSFarid Zakaria snprintf(src, sizeof(src), "%s/binfmt_bpf_interp", testdir); 230*277d787fSFarid Zakaria if (copy_file(src, INTERP_PATH)) { 231*277d787fSFarid Zakaria fprintf(stderr, "cannot install %s\n", INTERP_PATH); 232*277d787fSFarid Zakaria return 1; 233*277d787fSFarid Zakaria } 234*277d787fSFarid Zakaria 235*277d787fSFarid Zakaria /* Case 1: match a synthetic aarch64 header -> fixed interpreter. */ 236*277d787fSFarid Zakaria printf("[*] case 1: match aarch64 header -> program-chosen interpreter\n"); 237*277d787fSFarid Zakaria if (create_fake_aarch64(AARCH64_PATH)) { 238*277d787fSFarid Zakaria fprintf(stderr, "cannot create %s\n", AARCH64_PATH); 239*277d787fSFarid Zakaria return 1; 240*277d787fSFarid Zakaria } 241*277d787fSFarid Zakaria snprintf(obj, sizeof(obj), "%s/bpf_interp.bpf.o", testdir); 242*277d787fSFarid Zakaria if (run_case(obj, "bpf_interp", "test_bpf_interp", AARCH64_PATH, EXPECT) == 0) 243*277d787fSFarid Zakaria printf("[+] case 1 passed\n"); 244*277d787fSFarid Zakaria else { 245*277d787fSFarid Zakaria printf("[-] case 1 FAILED\n"); 246*277d787fSFarid Zakaria fail = 1; 247*277d787fSFarid Zakaria } 248*277d787fSFarid Zakaria unlink(AARCH64_PATH); 249*277d787fSFarid Zakaria 250*277d787fSFarid Zakaria /* Case 2: $ORIGIN-relative PT_INTERP -> co-located interpreter. */ 251*277d787fSFarid Zakaria printf("[*] case 2: $ORIGIN interpreter resolved relative to the binary\n"); 252*277d787fSFarid Zakaria mkdir(RELOC_DIR, 0755); 253*277d787fSFarid Zakaria snprintf(appdst, sizeof(appdst), "%s/app", RELOC_DIR); 254*277d787fSFarid Zakaria snprintf(interpdst, sizeof(interpdst), "%s/binfmt_bpf_interp", RELOC_DIR); 255*277d787fSFarid Zakaria snprintf(src, sizeof(src), "%s/binfmt_bpf_app", testdir); 256*277d787fSFarid Zakaria if (copy_file(src, appdst) || 257*277d787fSFarid Zakaria copy_file(INTERP_PATH, interpdst)) { 258*277d787fSFarid Zakaria fprintf(stderr, "cannot set up %s\n", RELOC_DIR); 259*277d787fSFarid Zakaria fail = 1; 260*277d787fSFarid Zakaria } else { 261*277d787fSFarid Zakaria snprintf(obj, sizeof(obj), "%s/nix_origin.bpf.o", testdir); 262*277d787fSFarid Zakaria if (run_case(obj, "nix_origin", "test_bpf_origin", appdst, EXPECT) == 0) 263*277d787fSFarid Zakaria printf("[+] case 2 passed\n"); 264*277d787fSFarid Zakaria else { 265*277d787fSFarid Zakaria printf("[-] case 2 FAILED\n"); 266*277d787fSFarid Zakaria fail = 1; 267*277d787fSFarid Zakaria } 268*277d787fSFarid Zakaria } 269*277d787fSFarid Zakaria unlink(appdst); 270*277d787fSFarid Zakaria unlink(interpdst); 271*277d787fSFarid Zakaria rmdir(RELOC_DIR); 272*277d787fSFarid Zakaria unlink(INTERP_PATH); 273*277d787fSFarid Zakaria 274*277d787fSFarid Zakaria if (!fail) 275*277d787fSFarid Zakaria printf("[*] all binfmt_misc bpf cases passed\n"); 276*277d787fSFarid Zakaria return fail; 277*277d787fSFarid Zakaria } 278