15b88319eSDaniel Borkmann // SPDX-License-Identifier: GPL-2.0
25b88319eSDaniel Borkmann /* Copyright (c) 2026 Isovalent */
35b88319eSDaniel Borkmann
45b88319eSDaniel Borkmann #include <test_progs.h>
55b88319eSDaniel Borkmann #include <sys/syscall.h>
65b88319eSDaniel Borkmann #include <sys/mman.h>
75b88319eSDaniel Borkmann #include <sys/wait.h>
85b88319eSDaniel Borkmann #include <sys/stat.h>
95b88319eSDaniel Borkmann #include <fcntl.h>
105b88319eSDaniel Borkmann #include <limits.h>
115b88319eSDaniel Borkmann #include <linux/keyctl.h>
125b88319eSDaniel Borkmann #include <linux/bpf.h>
135b88319eSDaniel Borkmann
1499b321ddSDaniel Borkmann #include <bpf/btf.h>
1599b321ddSDaniel Borkmann
165b88319eSDaniel Borkmann #include "bpf/libbpf_internal.h" /* for libbpf_sha256() */
175b88319eSDaniel Borkmann #include "bpf/skel_internal.h" /* for loader ctx layout (bpf_loader_ctx etc) */
185b88319eSDaniel Borkmann
195b88319eSDaniel Borkmann #include "test_signed_loader.skel.h"
205b88319eSDaniel Borkmann #include "test_signed_loader_map.skel.h"
215b88319eSDaniel Borkmann #include "test_signed_loader_data.skel.h"
228ddce416SDaniel Borkmann #include "test_signed_loader_lsm.skel.h"
235b88319eSDaniel Borkmann
248ddce416SDaniel Borkmann enum {
258ddce416SDaniel Borkmann BPF_SIG_UNSIGNED = 0,
268ddce416SDaniel Borkmann BPF_SIG_VERIFIED,
278ddce416SDaniel Borkmann };
288ddce416SDaniel Borkmann
298ddce416SDaniel Borkmann enum {
308ddce416SDaniel Borkmann BPF_SIG_KEYRING_NONE = 0,
318ddce416SDaniel Borkmann BPF_SIG_KEYRING_BUILTIN,
328ddce416SDaniel Borkmann BPF_SIG_KEYRING_SECONDARY,
338ddce416SDaniel Borkmann BPF_SIG_KEYRING_PLATFORM,
348ddce416SDaniel Borkmann BPF_SIG_KEYRING_USER,
358ddce416SDaniel Borkmann };
368ddce416SDaniel Borkmann
load_loader(const void * insns,__u32 insns_sz,int map_fd,const void * sig,__u32 sig_sz,__s32 keyring_id,__u32 fd_array_cnt)375b88319eSDaniel Borkmann static int load_loader(const void *insns, __u32 insns_sz, int map_fd,
3899b321ddSDaniel Borkmann const void *sig, __u32 sig_sz, __s32 keyring_id,
3999b321ddSDaniel Borkmann __u32 fd_array_cnt)
405b88319eSDaniel Borkmann {
415b88319eSDaniel Borkmann union bpf_attr attr;
425b88319eSDaniel Borkmann int fd;
435b88319eSDaniel Borkmann
445b88319eSDaniel Borkmann memset(&attr, 0, sizeof(attr));
455b88319eSDaniel Borkmann attr.prog_type = BPF_PROG_TYPE_SYSCALL;
465b88319eSDaniel Borkmann attr.insns = ptr_to_u64(insns);
475b88319eSDaniel Borkmann attr.insn_cnt = insns_sz / sizeof(struct bpf_insn);
485b88319eSDaniel Borkmann attr.license = ptr_to_u64("Dual BSD/GPL");
495b88319eSDaniel Borkmann attr.prog_flags = BPF_F_SLEEPABLE;
505b88319eSDaniel Borkmann attr.fd_array = ptr_to_u64(&map_fd);
515b88319eSDaniel Borkmann if (sig) {
525b88319eSDaniel Borkmann attr.signature = ptr_to_u64(sig);
535b88319eSDaniel Borkmann attr.signature_size = sig_sz;
545b88319eSDaniel Borkmann attr.keyring_id = keyring_id;
555b88319eSDaniel Borkmann }
5699b321ddSDaniel Borkmann attr.fd_array_cnt = fd_array_cnt;
575b88319eSDaniel Borkmann memcpy(attr.prog_name, "__loader.prog", sizeof("__loader.prog"));
585b88319eSDaniel Borkmann fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
595b88319eSDaniel Borkmann offsetofend(union bpf_attr, keyring_id));
605b88319eSDaniel Borkmann return fd < 0 ? -errno : fd;
615b88319eSDaniel Borkmann }
625b88319eSDaniel Borkmann
run_gen_loader(const void * insns,__u32 insns_sz,const void * data,__u32 data_sz,const void * excl,__u32 excl_sz,const void * sig,__u32 sig_sz,void * ctx,__u32 ctx_sz,bool * loader_ran)635b88319eSDaniel Borkmann static int run_gen_loader(const void *insns, __u32 insns_sz,
645b88319eSDaniel Borkmann const void *data, __u32 data_sz,
655b88319eSDaniel Borkmann const void *excl, __u32 excl_sz,
665b88319eSDaniel Borkmann const void *sig, __u32 sig_sz,
6799b321ddSDaniel Borkmann void *ctx, __u32 ctx_sz, bool *loader_ran)
685b88319eSDaniel Borkmann {
695b88319eSDaniel Borkmann LIBBPF_OPTS(bpf_map_create_opts, mopts,
705b88319eSDaniel Borkmann .excl_prog_hash = excl,
715b88319eSDaniel Borkmann .excl_prog_hash_size = excl_sz);
7299b321ddSDaniel Borkmann __u32 key = 0;
735b88319eSDaniel Borkmann union bpf_attr attr;
745b88319eSDaniel Borkmann int map_fd, prog_fd, ret;
755b88319eSDaniel Borkmann
765b88319eSDaniel Borkmann *loader_ran = false;
775b88319eSDaniel Borkmann
785b88319eSDaniel Borkmann map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "__loader.map",
795b88319eSDaniel Borkmann 4, data_sz, 1, &mopts);
805b88319eSDaniel Borkmann if (map_fd < 0)
815b88319eSDaniel Borkmann return -errno;
825b88319eSDaniel Borkmann if (bpf_map_update_elem(map_fd, &key, data, 0)) {
835b88319eSDaniel Borkmann ret = -errno;
845b88319eSDaniel Borkmann goto out_map;
855b88319eSDaniel Borkmann }
865b88319eSDaniel Borkmann if (bpf_map_freeze(map_fd)) {
875b88319eSDaniel Borkmann ret = -errno;
885b88319eSDaniel Borkmann goto out_map;
895b88319eSDaniel Borkmann }
905b88319eSDaniel Borkmann
915b88319eSDaniel Borkmann memset(&attr, 0, sizeof(attr));
925b88319eSDaniel Borkmann attr.prog_type = BPF_PROG_TYPE_SYSCALL;
935b88319eSDaniel Borkmann attr.insns = ptr_to_u64(insns);
945b88319eSDaniel Borkmann attr.insn_cnt = insns_sz / sizeof(struct bpf_insn);
955b88319eSDaniel Borkmann attr.license = ptr_to_u64("Dual BSD/GPL");
965b88319eSDaniel Borkmann attr.prog_flags = BPF_F_SLEEPABLE;
975b88319eSDaniel Borkmann attr.fd_array = ptr_to_u64(&map_fd);
985b88319eSDaniel Borkmann if (sig) {
995b88319eSDaniel Borkmann attr.signature = ptr_to_u64(sig);
1005b88319eSDaniel Borkmann attr.signature_size = sig_sz;
1015b88319eSDaniel Borkmann attr.keyring_id = KEY_SPEC_SESSION_KEYRING;
10299b321ddSDaniel Borkmann attr.fd_array_cnt = 1;
1035b88319eSDaniel Borkmann }
1045b88319eSDaniel Borkmann memcpy(attr.prog_name, "__loader.prog", sizeof("__loader.prog"));
1055b88319eSDaniel Borkmann prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
1065b88319eSDaniel Borkmann offsetofend(union bpf_attr, keyring_id));
1075b88319eSDaniel Borkmann if (prog_fd < 0) {
1085b88319eSDaniel Borkmann ret = -errno;
1095b88319eSDaniel Borkmann goto out_map;
1105b88319eSDaniel Borkmann }
1115b88319eSDaniel Borkmann
1125b88319eSDaniel Borkmann memset(&attr, 0, sizeof(attr));
1135b88319eSDaniel Borkmann attr.test.prog_fd = prog_fd;
1145b88319eSDaniel Borkmann attr.test.ctx_in = ptr_to_u64(ctx);
1155b88319eSDaniel Borkmann attr.test.ctx_size_in = ctx_sz;
1165b88319eSDaniel Borkmann if (syscall(__NR_bpf, BPF_PROG_RUN, &attr,
1175b88319eSDaniel Borkmann offsetofend(union bpf_attr, test)) < 0) {
1185b88319eSDaniel Borkmann ret = -errno;
1195b88319eSDaniel Borkmann goto out_prog;
1205b88319eSDaniel Borkmann }
1215b88319eSDaniel Borkmann *loader_ran = true;
1225b88319eSDaniel Borkmann ret = (int)attr.test.retval;
1235b88319eSDaniel Borkmann out_prog:
1245b88319eSDaniel Borkmann close(prog_fd);
1255b88319eSDaniel Borkmann out_map:
1265b88319eSDaniel Borkmann close(map_fd);
1275b88319eSDaniel Borkmann return ret;
1285b88319eSDaniel Borkmann }
1295b88319eSDaniel Borkmann
close_loader_ctx_fds(void * ctx,int nr_maps,int nr_progs)1305b88319eSDaniel Borkmann static void close_loader_ctx_fds(void *ctx, int nr_maps, int nr_progs)
1315b88319eSDaniel Borkmann {
1325b88319eSDaniel Borkmann struct bpf_map_desc *md = (struct bpf_map_desc *)((char *)ctx +
1335b88319eSDaniel Borkmann sizeof(struct bpf_loader_ctx));
1345b88319eSDaniel Borkmann struct bpf_prog_desc *pd = (struct bpf_prog_desc *)(md + nr_maps);
1355b88319eSDaniel Borkmann int i;
1365b88319eSDaniel Borkmann
1375b88319eSDaniel Borkmann for (i = 0; i < nr_maps; i++)
1385b88319eSDaniel Borkmann if (md[i].map_fd > 0)
1395b88319eSDaniel Borkmann close(md[i].map_fd);
1405b88319eSDaniel Borkmann for (i = 0; i < nr_progs; i++)
1415b88319eSDaniel Borkmann if (pd[i].prog_fd > 0)
1425b88319eSDaniel Borkmann close(pd[i].prog_fd);
1435b88319eSDaniel Borkmann }
1445b88319eSDaniel Borkmann
run_setup(const char * cmd,const char * dir)1455b88319eSDaniel Borkmann static int run_setup(const char *cmd, const char *dir)
1465b88319eSDaniel Borkmann {
1475b88319eSDaniel Borkmann int pid, status;
1485b88319eSDaniel Borkmann
1495b88319eSDaniel Borkmann pid = fork();
1505b88319eSDaniel Borkmann if (pid < 0)
1515b88319eSDaniel Borkmann return -errno;
1525b88319eSDaniel Borkmann if (pid == 0) {
1535b88319eSDaniel Borkmann execlp("./verify_sig_setup.sh", "./verify_sig_setup.sh",
1545b88319eSDaniel Borkmann cmd, dir, NULL);
1555b88319eSDaniel Borkmann exit(1);
1565b88319eSDaniel Borkmann }
1575b88319eSDaniel Borkmann if (waitpid(pid, &status, 0) < 0)
1585b88319eSDaniel Borkmann return -errno;
1595b88319eSDaniel Borkmann return (WIFEXITED(status) &&
1605b88319eSDaniel Borkmann WEXITSTATUS(status) == 0) ? 0 : -EINVAL;
1615b88319eSDaniel Borkmann }
1625b88319eSDaniel Borkmann
sign_buf(const char * dir,const void * buf,__u32 len,void * sig,__u32 * sig_sz)1635b88319eSDaniel Borkmann static int sign_buf(const char *dir, const void *buf, __u32 len,
1645b88319eSDaniel Borkmann void *sig, __u32 *sig_sz)
1655b88319eSDaniel Borkmann {
1665b88319eSDaniel Borkmann char data_tmpl[PATH_MAX], key[PATH_MAX];
1675b88319eSDaniel Borkmann char sigpath[PATH_MAX + sizeof(".p7s")];
1685b88319eSDaniel Borkmann int fd, pid, status, ret;
1695b88319eSDaniel Borkmann struct stat st;
1705b88319eSDaniel Borkmann
1715b88319eSDaniel Borkmann ret = snprintf(data_tmpl, sizeof(data_tmpl), "%s/dataXXXXXX", dir);
1725b88319eSDaniel Borkmann if (ret < 0 || ret >= (int)sizeof(data_tmpl))
1735b88319eSDaniel Borkmann return -ENAMETOOLONG;
1745b88319eSDaniel Borkmann ret = 0;
1755b88319eSDaniel Borkmann
1765b88319eSDaniel Borkmann fd = mkstemp(data_tmpl);
1775b88319eSDaniel Borkmann if (fd < 0)
1785b88319eSDaniel Borkmann return -errno;
1795b88319eSDaniel Borkmann if (write(fd, buf, len) != (ssize_t)len) {
1805b88319eSDaniel Borkmann close(fd);
1815b88319eSDaniel Borkmann ret = -EIO;
1825b88319eSDaniel Borkmann goto out;
1835b88319eSDaniel Borkmann }
1845b88319eSDaniel Borkmann close(fd);
1855b88319eSDaniel Borkmann
1865b88319eSDaniel Borkmann pid = fork();
1875b88319eSDaniel Borkmann if (pid < 0) {
1885b88319eSDaniel Borkmann ret = -errno;
1895b88319eSDaniel Borkmann goto out;
1905b88319eSDaniel Borkmann }
1915b88319eSDaniel Borkmann if (pid == 0) {
1925b88319eSDaniel Borkmann snprintf(key, sizeof(key), "%s/signing_key.pem", dir);
1935b88319eSDaniel Borkmann execlp("./sign-file", "./sign-file", "-d", "sha256",
1945b88319eSDaniel Borkmann key, key, data_tmpl, NULL);
1955b88319eSDaniel Borkmann exit(1);
1965b88319eSDaniel Borkmann }
1975b88319eSDaniel Borkmann if (waitpid(pid, &status, 0) < 0 ||
1985b88319eSDaniel Borkmann !WIFEXITED(status) || WEXITSTATUS(status)) {
1995b88319eSDaniel Borkmann ret = -EINVAL;
2005b88319eSDaniel Borkmann goto out;
2015b88319eSDaniel Borkmann }
2025b88319eSDaniel Borkmann
2035b88319eSDaniel Borkmann snprintf(sigpath, sizeof(sigpath), "%s.p7s", data_tmpl);
2045b88319eSDaniel Borkmann if (stat(sigpath, &st) < 0) {
2055b88319eSDaniel Borkmann ret = -errno;
2065b88319eSDaniel Borkmann goto out;
2075b88319eSDaniel Borkmann }
2085b88319eSDaniel Borkmann if (st.st_size > (off_t)*sig_sz) {
2095b88319eSDaniel Borkmann ret = -E2BIG;
2105b88319eSDaniel Borkmann goto out_sig;
2115b88319eSDaniel Borkmann }
2125b88319eSDaniel Borkmann fd = open(sigpath, O_RDONLY);
2135b88319eSDaniel Borkmann if (fd < 0) {
2145b88319eSDaniel Borkmann ret = -errno;
2155b88319eSDaniel Borkmann goto out_sig;
2165b88319eSDaniel Borkmann }
2175b88319eSDaniel Borkmann if (read(fd, sig, st.st_size) != st.st_size) {
2185b88319eSDaniel Borkmann close(fd);
2195b88319eSDaniel Borkmann ret = -EIO;
2205b88319eSDaniel Borkmann goto out_sig;
2215b88319eSDaniel Borkmann }
2225b88319eSDaniel Borkmann close(fd);
2235b88319eSDaniel Borkmann *sig_sz = st.st_size;
2245b88319eSDaniel Borkmann out_sig:
2255b88319eSDaniel Borkmann unlink(sigpath);
2265b88319eSDaniel Borkmann out:
2275b88319eSDaniel Borkmann unlink(data_tmpl);
2285b88319eSDaniel Borkmann return ret;
2295b88319eSDaniel Borkmann }
2305b88319eSDaniel Borkmann
2315b88319eSDaniel Borkmann struct gen_loader_fixture {
2325b88319eSDaniel Borkmann struct test_signed_loader *skel;
2335b88319eSDaniel Borkmann struct gen_loader_opts gopts;
2345b88319eSDaniel Borkmann unsigned char *blob;
2355b88319eSDaniel Borkmann void *ctx;
2365b88319eSDaniel Borkmann __u32 data_sz;
2375b88319eSDaniel Borkmann __u32 ctx_sz;
2385b88319eSDaniel Borkmann int nr_maps;
2395b88319eSDaniel Borkmann int nr_progs;
2405b88319eSDaniel Borkmann __u8 excl[SHA256_DIGEST_LENGTH];
2415b88319eSDaniel Borkmann };
2425b88319eSDaniel Borkmann
gen_loader_fixture_init(struct gen_loader_fixture * f)2435b88319eSDaniel Borkmann static int gen_loader_fixture_init(struct gen_loader_fixture *f)
2445b88319eSDaniel Borkmann {
2455b88319eSDaniel Borkmann LIBBPF_OPTS(gen_loader_opts, gopts, .gen_hash = true);
2465b88319eSDaniel Borkmann int nr_maps = 0, nr_progs = 0;
2475b88319eSDaniel Borkmann struct bpf_program *p;
2485b88319eSDaniel Borkmann struct bpf_map *m;
2495b88319eSDaniel Borkmann
2505b88319eSDaniel Borkmann memset(f, 0, sizeof(*f));
2515b88319eSDaniel Borkmann f->skel = test_signed_loader__open();
2525b88319eSDaniel Borkmann if (!ASSERT_OK_PTR(f->skel, "skel_open"))
2535b88319eSDaniel Borkmann return -1;
2545b88319eSDaniel Borkmann if (!ASSERT_OK(bpf_object__gen_loader(f->skel->obj, &gopts), "gen_loader"))
2555b88319eSDaniel Borkmann return -1;
2565b88319eSDaniel Borkmann if (!ASSERT_OK(bpf_object__load(f->skel->obj), "gen_load"))
2575b88319eSDaniel Borkmann return -1;
2585b88319eSDaniel Borkmann f->gopts = gopts;
2595b88319eSDaniel Borkmann
2605b88319eSDaniel Borkmann bpf_object__for_each_program(p, f->skel->obj)
2615b88319eSDaniel Borkmann nr_progs++;
2625b88319eSDaniel Borkmann bpf_object__for_each_map(m, f->skel->obj)
2635b88319eSDaniel Borkmann nr_maps++;
2645b88319eSDaniel Borkmann f->nr_maps = nr_maps;
2655b88319eSDaniel Borkmann f->nr_progs = nr_progs;
2665b88319eSDaniel Borkmann f->ctx_sz = sizeof(struct bpf_loader_ctx) +
2675b88319eSDaniel Borkmann nr_maps * sizeof(struct bpf_map_desc) +
2685b88319eSDaniel Borkmann nr_progs * sizeof(struct bpf_prog_desc);
2695b88319eSDaniel Borkmann f->ctx = calloc(1, f->ctx_sz);
2705b88319eSDaniel Borkmann if (!ASSERT_OK_PTR(f->ctx, "ctx_alloc"))
2715b88319eSDaniel Borkmann return -1;
2725b88319eSDaniel Borkmann ((struct bpf_loader_ctx *)f->ctx)->sz = f->ctx_sz;
2735b88319eSDaniel Borkmann
2745b88319eSDaniel Borkmann f->data_sz = gopts.data_sz;
2755b88319eSDaniel Borkmann f->blob = malloc(f->data_sz);
2765b88319eSDaniel Borkmann if (!ASSERT_OK_PTR(f->blob, "blob_alloc"))
2775b88319eSDaniel Borkmann return -1;
2785b88319eSDaniel Borkmann memcpy(f->blob, gopts.data, f->data_sz);
2795b88319eSDaniel Borkmann
2805b88319eSDaniel Borkmann /* excl_prog_hash = SHA256(loader insns) == the loader's prog->digest. */
2815b88319eSDaniel Borkmann libbpf_sha256(gopts.insns, gopts.insns_sz, f->excl);
2825b88319eSDaniel Borkmann return 0;
2835b88319eSDaniel Borkmann }
2845b88319eSDaniel Borkmann
gen_loader_fixture_fini(struct gen_loader_fixture * f)2855b88319eSDaniel Borkmann static void gen_loader_fixture_fini(struct gen_loader_fixture *f)
2865b88319eSDaniel Borkmann {
2875b88319eSDaniel Borkmann if (f->ctx)
2885b88319eSDaniel Borkmann close_loader_ctx_fds(f->ctx, f->nr_maps, f->nr_progs);
2895b88319eSDaniel Borkmann free(f->blob);
2905b88319eSDaniel Borkmann free(f->ctx);
2915b88319eSDaniel Borkmann test_signed_loader__destroy(f->skel);
2925b88319eSDaniel Borkmann }
2935b88319eSDaniel Borkmann
metadata_match(void)2945b88319eSDaniel Borkmann static void metadata_match(void)
2955b88319eSDaniel Borkmann {
2965b88319eSDaniel Borkmann struct gen_loader_fixture f;
2975b88319eSDaniel Borkmann bool ran;
2985b88319eSDaniel Borkmann int r;
2995b88319eSDaniel Borkmann
3005b88319eSDaniel Borkmann if (gen_loader_fixture_init(&f) == 0) {
3015b88319eSDaniel Borkmann r = run_gen_loader(f.gopts.insns, f.gopts.insns_sz, f.blob,
3025b88319eSDaniel Borkmann f.data_sz, f.excl, sizeof(f.excl), NULL, 0,
30399b321ddSDaniel Borkmann f.ctx, f.ctx_sz, &ran);
3045b88319eSDaniel Borkmann ASSERT_TRUE(ran, "loader ran");
3055b88319eSDaniel Borkmann ASSERT_EQ(r, 0, "honest loader retval");
3065b88319eSDaniel Borkmann }
3075b88319eSDaniel Borkmann gen_loader_fixture_fini(&f);
3085b88319eSDaniel Borkmann }
3095b88319eSDaniel Borkmann
signature_enforced(void)3105b88319eSDaniel Borkmann static void signature_enforced(void)
3115b88319eSDaniel Borkmann {
3125b88319eSDaniel Borkmann static const __u8 junk[64] = { 0x30, 0x42, 0x13, 0x37, };
3135b88319eSDaniel Borkmann struct gen_loader_fixture f;
3145b88319eSDaniel Borkmann int fd;
3155b88319eSDaniel Borkmann
3165b88319eSDaniel Borkmann if (gen_loader_fixture_init(&f) == 0) {
3175b88319eSDaniel Borkmann /*
3185b88319eSDaniel Borkmann * A present-but-invalid signature (the cert bytes are not a
3195b88319eSDaniel Borkmann * PKCS#7 signature) must be rejected at load: the signature
3205b88319eSDaniel Borkmann * path is honored, not ignored. (The valid path is covered by
32199b321ddSDaniel Borkmann * the signed lskels.) Pin -EBADMSG, the PKCS#7 parse failure:
32299b321ddSDaniel Borkmann * a looser fd < 0 check could also be satisfied by the sparse
32399b321ddSDaniel Borkmann * fd_array rejection (-EACCES) that the loader's map reference
32499b321ddSDaniel Borkmann * would trip even if the signature were silently ignored.
3255b88319eSDaniel Borkmann */
3265b88319eSDaniel Borkmann fd = load_loader(f.gopts.insns, f.gopts.insns_sz, -1, junk,
32799b321ddSDaniel Borkmann sizeof(junk), KEY_SPEC_SESSION_KEYRING, 0);
32899b321ddSDaniel Borkmann ASSERT_EQ(fd, -EBADMSG, "invalid signature rejected at load");
329*43f129d2SDaniel Borkmann if (fd >= 0)
330*43f129d2SDaniel Borkmann close(fd);
33199b321ddSDaniel Borkmann }
33299b321ddSDaniel Borkmann gen_loader_fixture_fini(&f);
33399b321ddSDaniel Borkmann }
33499b321ddSDaniel Borkmann
signed_nonexcl_fd_array_rejected(void)33599b321ddSDaniel Borkmann static void signed_nonexcl_fd_array_rejected(void)
33699b321ddSDaniel Borkmann {
33799b321ddSDaniel Borkmann static const __u8 junk[64] = { 0x30, 0x42, 0x13, 0x37, };
33899b321ddSDaniel Borkmann struct gen_loader_fixture f;
33999b321ddSDaniel Borkmann int map_fd, fd;
34099b321ddSDaniel Borkmann
34199b321ddSDaniel Borkmann if (gen_loader_fixture_init(&f) == 0) {
34299b321ddSDaniel Borkmann /*
34399b321ddSDaniel Borkmann * A signed program may only bind exclusive maps through fd_array
34499b321ddSDaniel Borkmann * (their contents are folded into the signature). Binding a
34599b321ddSDaniel Borkmann * non-exclusive map is rejected, before the signature is even
34699b321ddSDaniel Borkmann * examined.
34799b321ddSDaniel Borkmann */
34899b321ddSDaniel Borkmann map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "nonexcl", 4,
34999b321ddSDaniel Borkmann f.data_sz, 1, NULL);
35099b321ddSDaniel Borkmann if (ASSERT_OK_FD(map_fd, "nonexcl_map")) {
35199b321ddSDaniel Borkmann if (ASSERT_OK(bpf_map_freeze(map_fd), "freeze")) {
35299b321ddSDaniel Borkmann fd = load_loader(f.gopts.insns, f.gopts.insns_sz,
35399b321ddSDaniel Borkmann map_fd, junk, sizeof(junk),
35499b321ddSDaniel Borkmann KEY_SPEC_SESSION_KEYRING, 1);
35599b321ddSDaniel Borkmann ASSERT_EQ(fd, -EPERM,
35699b321ddSDaniel Borkmann "non-exclusive map in signed fd_array rejected");
35799b321ddSDaniel Borkmann if (fd >= 0)
35899b321ddSDaniel Borkmann close(fd);
35999b321ddSDaniel Borkmann }
36099b321ddSDaniel Borkmann close(map_fd);
36199b321ddSDaniel Borkmann }
36299b321ddSDaniel Borkmann }
36399b321ddSDaniel Borkmann gen_loader_fixture_fini(&f);
36499b321ddSDaniel Borkmann }
36599b321ddSDaniel Borkmann
signed_unfrozen_fd_array_rejected(void)36699b321ddSDaniel Borkmann static void signed_unfrozen_fd_array_rejected(void)
36799b321ddSDaniel Borkmann {
36899b321ddSDaniel Borkmann static const __u8 junk[64] = { 0x30, 0x42, 0x13, 0x37, };
36999b321ddSDaniel Borkmann LIBBPF_OPTS(bpf_map_create_opts, mopts);
37099b321ddSDaniel Borkmann struct gen_loader_fixture f;
37199b321ddSDaniel Borkmann __u32 key = 0;
37299b321ddSDaniel Borkmann int map_fd, fd;
37399b321ddSDaniel Borkmann
37499b321ddSDaniel Borkmann if (gen_loader_fixture_init(&f) == 0) {
37599b321ddSDaniel Borkmann /*
37699b321ddSDaniel Borkmann * The metadata map must be frozen before a signed load so the
37799b321ddSDaniel Borkmann * folded bytes cannot change afterwards. Bind an exclusive map
37899b321ddSDaniel Borkmann * with matching contents but skip the freeze: the load must be
37999b321ddSDaniel Borkmann * rejected by the frozen check with -EPERM. The exclusivity
38099b321ddSDaniel Borkmann * check right after it would pass, so the errno uniquely pins
38199b321ddSDaniel Borkmann * the freeze requirement.
38299b321ddSDaniel Borkmann */
38399b321ddSDaniel Borkmann mopts.excl_prog_hash = f.excl;
38499b321ddSDaniel Borkmann mopts.excl_prog_hash_size = sizeof(f.excl);
38599b321ddSDaniel Borkmann map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "unfrozen", 4,
38699b321ddSDaniel Borkmann f.data_sz, 1, &mopts);
38799b321ddSDaniel Borkmann if (ASSERT_OK_FD(map_fd, "unfrozen_map")) {
38899b321ddSDaniel Borkmann if (ASSERT_OK(bpf_map_update_elem(map_fd, &key, f.blob, 0),
38999b321ddSDaniel Borkmann "update")) {
39099b321ddSDaniel Borkmann fd = load_loader(f.gopts.insns, f.gopts.insns_sz,
39199b321ddSDaniel Borkmann map_fd, junk, sizeof(junk),
39299b321ddSDaniel Borkmann KEY_SPEC_SESSION_KEYRING, 1);
39399b321ddSDaniel Borkmann ASSERT_EQ(fd, -EPERM,
39499b321ddSDaniel Borkmann "unfrozen map in signed fd_array rejected");
39599b321ddSDaniel Borkmann if (fd >= 0)
39699b321ddSDaniel Borkmann close(fd);
39799b321ddSDaniel Borkmann }
39899b321ddSDaniel Borkmann close(map_fd);
39999b321ddSDaniel Borkmann }
40099b321ddSDaniel Borkmann }
40199b321ddSDaniel Borkmann gen_loader_fixture_fini(&f);
40299b321ddSDaniel Borkmann }
40399b321ddSDaniel Borkmann
signed_nonarray_fd_array_rejected(void)40499b321ddSDaniel Borkmann static void signed_nonarray_fd_array_rejected(void)
40599b321ddSDaniel Borkmann {
40699b321ddSDaniel Borkmann static const __u8 junk[64] = { 0x30, 0x42, 0x13, 0x37, };
40799b321ddSDaniel Borkmann LIBBPF_OPTS(bpf_map_create_opts, mopts);
40899b321ddSDaniel Borkmann struct gen_loader_fixture f;
40999b321ddSDaniel Borkmann int map_fd, fd;
41099b321ddSDaniel Borkmann
41199b321ddSDaniel Borkmann if (gen_loader_fixture_init(&f) == 0) {
41299b321ddSDaniel Borkmann /*
41399b321ddSDaniel Borkmann * Only a plain BPF_MAP_TYPE_ARRAY may be folded into the
41499b321ddSDaniel Borkmann * signature. An exclusive map of any other type is rejected
41599b321ddSDaniel Borkmann * (-EINVAL) rather than folded - this is the type gate that
41699b321ddSDaniel Borkmann * keeps arena maps (map_direct_value_addr() returns a user
41799b321ddSDaniel Borkmann * address) and insn-array maps (buffer smaller than value_size)
41899b321ddSDaniel Borkmann * out of the hashed region, where the old code would have
41999b321ddSDaniel Borkmann * memcpy()'d from them. A hash map stands in here: it is
42099b321ddSDaniel Borkmann * exclusive (bound to the loader digest) but not an array.
42199b321ddSDaniel Borkmann */
42299b321ddSDaniel Borkmann mopts.excl_prog_hash = f.excl;
42399b321ddSDaniel Borkmann mopts.excl_prog_hash_size = sizeof(f.excl);
42499b321ddSDaniel Borkmann map_fd = bpf_map_create(BPF_MAP_TYPE_HASH, "excl_hash", 4, 4, 1,
42599b321ddSDaniel Borkmann &mopts);
42699b321ddSDaniel Borkmann if (ASSERT_OK_FD(map_fd, "excl_hash_map")) {
42799b321ddSDaniel Borkmann fd = load_loader(f.gopts.insns, f.gopts.insns_sz, map_fd,
42899b321ddSDaniel Borkmann junk, sizeof(junk),
42999b321ddSDaniel Borkmann KEY_SPEC_SESSION_KEYRING, 1);
43099b321ddSDaniel Borkmann ASSERT_EQ(fd, -EINVAL,
43199b321ddSDaniel Borkmann "non-array map in signed fd_array rejected");
43299b321ddSDaniel Borkmann if (fd >= 0)
43399b321ddSDaniel Borkmann close(fd);
43499b321ddSDaniel Borkmann close(map_fd);
43599b321ddSDaniel Borkmann }
43699b321ddSDaniel Borkmann }
43799b321ddSDaniel Borkmann gen_loader_fixture_fini(&f);
43899b321ddSDaniel Borkmann }
43999b321ddSDaniel Borkmann
44099b321ddSDaniel Borkmann static int setup_meta_map(const struct gen_loader_fixture *f);
44199b321ddSDaniel Borkmann
signed_btf_fd_array_rejected(void)44299b321ddSDaniel Borkmann static void signed_btf_fd_array_rejected(void)
44399b321ddSDaniel Borkmann {
44499b321ddSDaniel Borkmann char dir_tmpl[] = "/tmp/signed_loader_btfXXXXXX", *dir = NULL;
44599b321ddSDaniel Borkmann __u32 sig_sz = 8192;
44699b321ddSDaniel Borkmann int map_fd = -1, prog_fd = -1;
44799b321ddSDaniel Borkmann unsigned char *buf = NULL;
44899b321ddSDaniel Borkmann struct gen_loader_fixture f;
44999b321ddSDaniel Borkmann bool have_fixture = false;
45099b321ddSDaniel Borkmann struct btf *btf = NULL;
45199b321ddSDaniel Borkmann union bpf_attr attr;
45299b321ddSDaniel Borkmann int fds[2];
45399b321ddSDaniel Borkmann __u8 sig[8192];
45499b321ddSDaniel Borkmann
45599b321ddSDaniel Borkmann syscall(__NR_request_key, "keyring", "_uid.0", NULL,
45699b321ddSDaniel Borkmann KEY_SPEC_SESSION_KEYRING);
45799b321ddSDaniel Borkmann dir = mkdtemp(dir_tmpl);
45899b321ddSDaniel Borkmann if (!ASSERT_OK_PTR(dir, "mkdtemp"))
45999b321ddSDaniel Borkmann return;
46099b321ddSDaniel Borkmann if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
46199b321ddSDaniel Borkmann rmdir(dir);
46299b321ddSDaniel Borkmann return;
46399b321ddSDaniel Borkmann }
46499b321ddSDaniel Borkmann have_fixture = true;
46599b321ddSDaniel Borkmann if (gen_loader_fixture_init(&f) != 0)
46699b321ddSDaniel Borkmann goto out;
46799b321ddSDaniel Borkmann
46899b321ddSDaniel Borkmann /*
46999b321ddSDaniel Borkmann * fd_array binds maps and BTFs alike, but only exclusive array maps are
47099b321ddSDaniel Borkmann * folded into the signature. Build an otherwise genuinely signed load -
47199b321ddSDaniel Borkmann * insns || metadata, exclusive frozen map at fd_array[0] - then smuggle
47299b321ddSDaniel Borkmann * an extra BTF into fd_array[1]. A signed program may not bind any BTF,
47399b321ddSDaniel Borkmann * so resolving the fd_array entries rejects the BTF with -EACCES (in
47499b321ddSDaniel Borkmann * __add_used_btf(), before the signature is even verified).
47599b321ddSDaniel Borkmann */
47699b321ddSDaniel Borkmann buf = malloc((size_t)f.gopts.insns_sz + f.data_sz);
47799b321ddSDaniel Borkmann if (!ASSERT_OK_PTR(buf, "signbuf"))
47899b321ddSDaniel Borkmann goto out;
47999b321ddSDaniel Borkmann memcpy(buf, f.gopts.insns, f.gopts.insns_sz);
48099b321ddSDaniel Borkmann memcpy(buf + f.gopts.insns_sz, f.blob, f.data_sz);
48199b321ddSDaniel Borkmann if (!ASSERT_OK(sign_buf(dir, buf, f.gopts.insns_sz + f.data_sz, sig,
48299b321ddSDaniel Borkmann &sig_sz), "sign insns||metadata"))
48399b321ddSDaniel Borkmann goto out;
48499b321ddSDaniel Borkmann
48599b321ddSDaniel Borkmann map_fd = setup_meta_map(&f);
48699b321ddSDaniel Borkmann if (!ASSERT_OK_FD(map_fd, "meta_map"))
48799b321ddSDaniel Borkmann goto out;
48899b321ddSDaniel Borkmann btf = btf__new_empty();
48999b321ddSDaniel Borkmann if (!ASSERT_OK_PTR(btf, "btf_new_empty"))
49099b321ddSDaniel Borkmann goto out;
49199b321ddSDaniel Borkmann btf__add_int(btf, "int", 4, BTF_INT_SIGNED);
49299b321ddSDaniel Borkmann if (!ASSERT_OK(btf__load_into_kernel(btf), "btf_load"))
49399b321ddSDaniel Borkmann goto out;
49499b321ddSDaniel Borkmann
49599b321ddSDaniel Borkmann fds[0] = map_fd;
49699b321ddSDaniel Borkmann fds[1] = btf__fd(btf);
49799b321ddSDaniel Borkmann memset(&attr, 0, sizeof(attr));
49899b321ddSDaniel Borkmann attr.prog_type = BPF_PROG_TYPE_SYSCALL;
49999b321ddSDaniel Borkmann attr.insns = ptr_to_u64(f.gopts.insns);
50099b321ddSDaniel Borkmann attr.insn_cnt = f.gopts.insns_sz / sizeof(struct bpf_insn);
50199b321ddSDaniel Borkmann attr.license = ptr_to_u64("Dual BSD/GPL");
50299b321ddSDaniel Borkmann attr.prog_flags = BPF_F_SLEEPABLE;
50399b321ddSDaniel Borkmann attr.fd_array = ptr_to_u64(fds);
50499b321ddSDaniel Borkmann attr.fd_array_cnt = 2;
50599b321ddSDaniel Borkmann attr.signature = ptr_to_u64(sig);
50699b321ddSDaniel Borkmann attr.signature_size = sig_sz;
50799b321ddSDaniel Borkmann attr.keyring_id = KEY_SPEC_SESSION_KEYRING;
50899b321ddSDaniel Borkmann memcpy(attr.prog_name, "__loader.prog", sizeof("__loader.prog"));
50999b321ddSDaniel Borkmann prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
51099b321ddSDaniel Borkmann offsetofend(union bpf_attr, keyring_id));
51199b321ddSDaniel Borkmann ASSERT_EQ(prog_fd < 0 ? -errno : prog_fd, -EACCES,
51299b321ddSDaniel Borkmann "BTF in signed fd_array rejected");
51399b321ddSDaniel Borkmann if (prog_fd >= 0)
51499b321ddSDaniel Borkmann close(prog_fd);
51599b321ddSDaniel Borkmann out:
51699b321ddSDaniel Borkmann if (btf)
51799b321ddSDaniel Borkmann btf__free(btf);
51899b321ddSDaniel Borkmann if (map_fd >= 0)
51999b321ddSDaniel Borkmann close(map_fd);
52099b321ddSDaniel Borkmann if (have_fixture)
52199b321ddSDaniel Borkmann gen_loader_fixture_fini(&f);
52299b321ddSDaniel Borkmann if (dir)
52399b321ddSDaniel Borkmann run_setup("cleanup", dir);
52499b321ddSDaniel Borkmann free(buf);
52599b321ddSDaniel Borkmann }
52699b321ddSDaniel Borkmann
signature_failure_logs(void)52799b321ddSDaniel Borkmann static void signature_failure_logs(void)
52899b321ddSDaniel Borkmann {
52999b321ddSDaniel Borkmann static const __u8 junk[64] = { 0x30, 0x42, 0x13, 0x37, };
53099b321ddSDaniel Borkmann char log_buf[1024] = {};
53199b321ddSDaniel Borkmann struct gen_loader_fixture f;
53299b321ddSDaniel Borkmann union bpf_attr attr;
53399b321ddSDaniel Borkmann int fd;
53499b321ddSDaniel Borkmann
53599b321ddSDaniel Borkmann if (gen_loader_fixture_init(&f) == 0) {
53699b321ddSDaniel Borkmann /*
53799b321ddSDaniel Borkmann * Signature verification now runs inside bpf_check(), so a
53899b321ddSDaniel Borkmann * failure is reported through the verifier log. A present-but-
53999b321ddSDaniel Borkmann * invalid signature is rejected and the log says why.
54099b321ddSDaniel Borkmann */
54199b321ddSDaniel Borkmann memset(&attr, 0, sizeof(attr));
54299b321ddSDaniel Borkmann attr.prog_type = BPF_PROG_TYPE_SYSCALL;
54399b321ddSDaniel Borkmann attr.insns = ptr_to_u64(f.gopts.insns);
54499b321ddSDaniel Borkmann attr.insn_cnt = f.gopts.insns_sz / sizeof(struct bpf_insn);
54599b321ddSDaniel Borkmann attr.license = ptr_to_u64("Dual BSD/GPL");
54699b321ddSDaniel Borkmann attr.prog_flags = BPF_F_SLEEPABLE;
54799b321ddSDaniel Borkmann attr.signature = ptr_to_u64(junk);
54899b321ddSDaniel Borkmann attr.signature_size = sizeof(junk);
54999b321ddSDaniel Borkmann attr.keyring_id = KEY_SPEC_SESSION_KEYRING;
55099b321ddSDaniel Borkmann attr.log_level = 1;
55199b321ddSDaniel Borkmann attr.log_buf = ptr_to_u64(log_buf);
55299b321ddSDaniel Borkmann attr.log_size = sizeof(log_buf);
55399b321ddSDaniel Borkmann memcpy(attr.prog_name, "__loader.prog", sizeof("__loader.prog"));
55499b321ddSDaniel Borkmann
55599b321ddSDaniel Borkmann fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
55699b321ddSDaniel Borkmann offsetofend(union bpf_attr, keyring_id));
5575b88319eSDaniel Borkmann ASSERT_LT(fd, 0, "invalid signature rejected at load");
55899b321ddSDaniel Borkmann if (fd >= 0)
55999b321ddSDaniel Borkmann close(fd);
56099b321ddSDaniel Borkmann ASSERT_HAS_SUBSTR(log_buf, "signature verification failed",
56199b321ddSDaniel Borkmann "verifier logs signature failure");
5625b88319eSDaniel Borkmann }
5635b88319eSDaniel Borkmann gen_loader_fixture_fini(&f);
5645b88319eSDaniel Borkmann }
5655b88319eSDaniel Borkmann
signature_too_large(void)5665b88319eSDaniel Borkmann static void signature_too_large(void)
5675b88319eSDaniel Borkmann {
5685b88319eSDaniel Borkmann static const __u8 junk[64] = {};
5695b88319eSDaniel Borkmann struct gen_loader_fixture f;
5705b88319eSDaniel Borkmann int fd;
5715b88319eSDaniel Borkmann
5725b88319eSDaniel Borkmann if (gen_loader_fixture_init(&f) == 0) {
5735b88319eSDaniel Borkmann /*
5745b88319eSDaniel Borkmann * signature_size beyond the kernel's bound (KMALLOC_MAX_CACHE_SIZE)
5755b88319eSDaniel Borkmann * is rejected before the buffer is read.
5765b88319eSDaniel Borkmann */
5775b88319eSDaniel Borkmann fd = load_loader(f.gopts.insns, f.gopts.insns_sz, -1, junk,
57899b321ddSDaniel Borkmann 64 << 20, KEY_SPEC_SESSION_KEYRING, 0);
5795b88319eSDaniel Borkmann ASSERT_EQ(fd, -EINVAL, "oversized signature rejected");
580*43f129d2SDaniel Borkmann if (fd >= 0)
581*43f129d2SDaniel Borkmann close(fd);
5825b88319eSDaniel Borkmann }
5835b88319eSDaniel Borkmann gen_loader_fixture_fini(&f);
5845b88319eSDaniel Borkmann }
5855b88319eSDaniel Borkmann
signature_zero_size(void)58699b321ddSDaniel Borkmann static void signature_zero_size(void)
58799b321ddSDaniel Borkmann {
58899b321ddSDaniel Borkmann static const __u8 junk[64] = {};
58999b321ddSDaniel Borkmann struct gen_loader_fixture f;
59099b321ddSDaniel Borkmann int fd;
59199b321ddSDaniel Borkmann
59299b321ddSDaniel Borkmann if (gen_loader_fixture_init(&f) == 0) {
59399b321ddSDaniel Borkmann /*
59499b321ddSDaniel Borkmann * A present signature with signature_size == 0 is rejected
59599b321ddSDaniel Borkmann * up front, before the keyring is resolved or the signature
59699b321ddSDaniel Borkmann * buffer is read.
59799b321ddSDaniel Borkmann */
59899b321ddSDaniel Borkmann fd = load_loader(f.gopts.insns, f.gopts.insns_sz, -1, junk,
59999b321ddSDaniel Borkmann 0, KEY_SPEC_SESSION_KEYRING, 0);
60099b321ddSDaniel Borkmann ASSERT_EQ(fd, -EINVAL, "zero-size signature rejected");
601*43f129d2SDaniel Borkmann if (fd >= 0)
602*43f129d2SDaniel Borkmann close(fd);
60399b321ddSDaniel Borkmann }
60499b321ddSDaniel Borkmann gen_loader_fixture_fini(&f);
60599b321ddSDaniel Borkmann }
60699b321ddSDaniel Borkmann
signature_bad_keyring(void)6075b88319eSDaniel Borkmann static void signature_bad_keyring(void)
6085b88319eSDaniel Borkmann {
6095b88319eSDaniel Borkmann static const __u8 junk[64] = {};
6105b88319eSDaniel Borkmann struct gen_loader_fixture f;
6115b88319eSDaniel Borkmann int fd;
6125b88319eSDaniel Borkmann
6135b88319eSDaniel Borkmann if (gen_loader_fixture_init(&f) == 0) {
6145b88319eSDaniel Borkmann /*
6155b88319eSDaniel Borkmann * A present signature with a keyring_id that resolves to no key is
6165b88319eSDaniel Borkmann * rejected up front: bpf_prog_verify_signature() fails the keyring
6175b88319eSDaniel Borkmann * lookup (-EINVAL) before it ever looks at the signature bytes. A
6185b88319eSDaniel Borkmann * large positive serial takes the user-keyring path and won't exist.
6195b88319eSDaniel Borkmann */
6205b88319eSDaniel Borkmann fd = load_loader(f.gopts.insns, f.gopts.insns_sz, -1, junk,
62199b321ddSDaniel Borkmann sizeof(junk), INT_MAX, 0);
6225b88319eSDaniel Borkmann ASSERT_EQ(fd, -EINVAL, "signature with bad keyring_id rejected");
623*43f129d2SDaniel Borkmann if (fd >= 0)
624*43f129d2SDaniel Borkmann close(fd);
6255b88319eSDaniel Borkmann }
6265b88319eSDaniel Borkmann gen_loader_fixture_fini(&f);
6275b88319eSDaniel Borkmann }
6285b88319eSDaniel Borkmann
6295b88319eSDaniel Borkmann /*
6305b88319eSDaniel Borkmann * A signed loader must ignore ctx-supplied map dimensions: the host cannot
6315b88319eSDaniel Borkmann * resize a signed program's maps via the loader ctx. Drive a one-map program
6325b88319eSDaniel Borkmann * through gen_loader, ask (via ctx) for every map to be resized to a bogus
6335b88319eSDaniel Borkmann * value, and confirm the created maps keep their attested size.
6345b88319eSDaniel Borkmann */
6355b88319eSDaniel Borkmann #define GATING_BOGUS_MAX 0x4000
6365b88319eSDaniel Borkmann
metadata_ctx_max_entries_ignored(void)6375b88319eSDaniel Borkmann static void metadata_ctx_max_entries_ignored(void)
6385b88319eSDaniel Borkmann {
6395b88319eSDaniel Borkmann LIBBPF_OPTS(gen_loader_opts, gopts, .gen_hash = true);
6405b88319eSDaniel Borkmann struct test_signed_loader_map *skel;
6415b88319eSDaniel Borkmann __u8 excl[SHA256_DIGEST_LENGTH];
6425b88319eSDaniel Borkmann int nr_maps = 0, nr_progs = 0, i, checked = 0, r;
6435b88319eSDaniel Borkmann struct bpf_program *p;
6445b88319eSDaniel Borkmann struct bpf_map *m;
6455b88319eSDaniel Borkmann struct bpf_map_desc *md;
6465b88319eSDaniel Borkmann unsigned char *blob;
6475b88319eSDaniel Borkmann __u32 ctx_sz, data_sz;
6485b88319eSDaniel Borkmann void *ctx;
6495b88319eSDaniel Borkmann bool ran;
6505b88319eSDaniel Borkmann
6515b88319eSDaniel Borkmann skel = test_signed_loader_map__open();
6525b88319eSDaniel Borkmann if (!ASSERT_OK_PTR(skel, "skel_open"))
6535b88319eSDaniel Borkmann return;
6545b88319eSDaniel Borkmann if (!ASSERT_OK(bpf_object__gen_loader(skel->obj, &gopts), "gen_loader"))
6555b88319eSDaniel Borkmann goto destroy;
6565b88319eSDaniel Borkmann if (!ASSERT_OK(bpf_object__load(skel->obj), "gen_load"))
6575b88319eSDaniel Borkmann goto destroy;
6585b88319eSDaniel Borkmann
6595b88319eSDaniel Borkmann bpf_object__for_each_program(p, skel->obj)
6605b88319eSDaniel Borkmann nr_progs++;
6615b88319eSDaniel Borkmann bpf_object__for_each_map(m, skel->obj)
6625b88319eSDaniel Borkmann nr_maps++;
6635b88319eSDaniel Borkmann ctx_sz = sizeof(struct bpf_loader_ctx) +
6645b88319eSDaniel Borkmann nr_maps * sizeof(struct bpf_map_desc) +
6655b88319eSDaniel Borkmann nr_progs * sizeof(struct bpf_prog_desc);
6665b88319eSDaniel Borkmann ctx = calloc(1, ctx_sz);
6675b88319eSDaniel Borkmann if (!ASSERT_OK_PTR(ctx, "ctx_alloc"))
6685b88319eSDaniel Borkmann goto destroy;
6695b88319eSDaniel Borkmann ((struct bpf_loader_ctx *)ctx)->sz = ctx_sz;
6705b88319eSDaniel Borkmann
6715b88319eSDaniel Borkmann md = (struct bpf_map_desc *)((char *)ctx + sizeof(struct bpf_loader_ctx));
6725b88319eSDaniel Borkmann for (i = 0; i < nr_maps; i++)
6735b88319eSDaniel Borkmann md[i].max_entries = GATING_BOGUS_MAX;
6745b88319eSDaniel Borkmann
6755b88319eSDaniel Borkmann libbpf_sha256(gopts.insns, gopts.insns_sz, excl);
6765b88319eSDaniel Borkmann data_sz = gopts.data_sz;
6775b88319eSDaniel Borkmann blob = malloc(data_sz);
6785b88319eSDaniel Borkmann if (!ASSERT_OK_PTR(blob, "blob_alloc"))
6795b88319eSDaniel Borkmann goto free_ctx;
6805b88319eSDaniel Borkmann memcpy(blob, gopts.data, data_sz);
6815b88319eSDaniel Borkmann
6825b88319eSDaniel Borkmann r = run_gen_loader(gopts.insns, gopts.insns_sz, blob, data_sz,
68399b321ddSDaniel Borkmann excl, sizeof(excl), NULL, 0, ctx, ctx_sz, &ran);
6845b88319eSDaniel Borkmann if (!ASSERT_TRUE(ran, "loader ran") ||
6855b88319eSDaniel Borkmann !ASSERT_EQ(r, 0, "loader retval"))
6865b88319eSDaniel Borkmann goto free_blob;
6875b88319eSDaniel Borkmann
6885b88319eSDaniel Borkmann for (i = 0; i < nr_maps; i++) {
6895b88319eSDaniel Borkmann struct bpf_map_info info;
6905b88319eSDaniel Borkmann __u32 ilen = sizeof(info);
6915b88319eSDaniel Borkmann int fd = md[i].map_fd;
6925b88319eSDaniel Borkmann
6935b88319eSDaniel Borkmann if (fd <= 0)
6945b88319eSDaniel Borkmann continue;
6955b88319eSDaniel Borkmann memset(&info, 0, sizeof(info));
6965b88319eSDaniel Borkmann if (ASSERT_OK(bpf_map_get_info_by_fd(fd, &info, &ilen), "map_info")) {
6975b88319eSDaniel Borkmann ASSERT_NEQ(info.max_entries, GATING_BOGUS_MAX,
6985b88319eSDaniel Borkmann "ctx max_entries ignored for signed loader");
6995b88319eSDaniel Borkmann checked++;
7005b88319eSDaniel Borkmann }
7015b88319eSDaniel Borkmann }
7025b88319eSDaniel Borkmann ASSERT_GT(checked, 0, "inspected a created map");
7035b88319eSDaniel Borkmann
7045b88319eSDaniel Borkmann free_blob:
7055b88319eSDaniel Borkmann free(blob);
7065b88319eSDaniel Borkmann free_ctx:
7075b88319eSDaniel Borkmann close_loader_ctx_fds(ctx, nr_maps, nr_progs);
7085b88319eSDaniel Borkmann free(ctx);
7095b88319eSDaniel Borkmann destroy:
7105b88319eSDaniel Borkmann test_signed_loader_map__destroy(skel);
7115b88319eSDaniel Borkmann }
7125b88319eSDaniel Borkmann
7135b88319eSDaniel Borkmann /*
7145b88319eSDaniel Borkmann * A signed loader must also ignore ctx-supplied initial_value: the host cannot
7155b88319eSDaniel Borkmann * re-seed a signed program's map contents through the loader ctx. Drive a
7165b88319eSDaniel Borkmann * program with one initialized global (a .data map) through gen_loader, point
7175b88319eSDaniel Borkmann * every map's ctx initial_value at an adversarial buffer, and confirm the
7185b88319eSDaniel Borkmann * created map still holds the attested value, never the ctx bytes.
7195b88319eSDaniel Borkmann */
7205b88319eSDaniel Borkmann #define DATA_MAGIC 0x5eed1234abad1deaULL
7215b88319eSDaniel Borkmann
metadata_ctx_initial_value_ignored(void)7225b88319eSDaniel Borkmann static void metadata_ctx_initial_value_ignored(void)
7235b88319eSDaniel Borkmann {
7245b88319eSDaniel Borkmann LIBBPF_OPTS(gen_loader_opts, gopts, .gen_hash = true);
7255b88319eSDaniel Borkmann struct test_signed_loader_data *skel;
7265b88319eSDaniel Borkmann __u8 excl[SHA256_DIGEST_LENGTH], evil[64];
7275b88319eSDaniel Borkmann int nr_maps = 0, nr_progs = 0, i, found = 0, r;
7285b88319eSDaniel Borkmann struct bpf_program *p;
7295b88319eSDaniel Borkmann struct bpf_map *m;
7305b88319eSDaniel Borkmann struct bpf_map_desc *md;
7315b88319eSDaniel Borkmann unsigned char *blob;
7325b88319eSDaniel Borkmann __u32 ctx_sz, data_sz;
7335b88319eSDaniel Borkmann void *ctx;
7345b88319eSDaniel Borkmann bool ran;
7355b88319eSDaniel Borkmann
7365b88319eSDaniel Borkmann skel = test_signed_loader_data__open();
7375b88319eSDaniel Borkmann if (!ASSERT_OK_PTR(skel, "skel_open"))
7385b88319eSDaniel Borkmann return;
7395b88319eSDaniel Borkmann if (!ASSERT_OK(bpf_object__gen_loader(skel->obj, &gopts), "gen_loader"))
7405b88319eSDaniel Borkmann goto destroy;
7415b88319eSDaniel Borkmann if (!ASSERT_OK(bpf_object__load(skel->obj), "gen_load"))
7425b88319eSDaniel Borkmann goto destroy;
7435b88319eSDaniel Borkmann
7445b88319eSDaniel Borkmann bpf_object__for_each_program(p, skel->obj)
7455b88319eSDaniel Borkmann nr_progs++;
7465b88319eSDaniel Borkmann bpf_object__for_each_map(m, skel->obj)
7475b88319eSDaniel Borkmann nr_maps++;
7485b88319eSDaniel Borkmann ctx_sz = sizeof(struct bpf_loader_ctx) +
7495b88319eSDaniel Borkmann nr_maps * sizeof(struct bpf_map_desc) +
7505b88319eSDaniel Borkmann nr_progs * sizeof(struct bpf_prog_desc);
7515b88319eSDaniel Borkmann ctx = calloc(1, ctx_sz);
7525b88319eSDaniel Borkmann if (!ASSERT_OK_PTR(ctx, "ctx_alloc"))
7535b88319eSDaniel Borkmann goto destroy;
7545b88319eSDaniel Borkmann ((struct bpf_loader_ctx *)ctx)->sz = ctx_sz;
7555b88319eSDaniel Borkmann
7565b88319eSDaniel Borkmann memset(evil, 0xAA, sizeof(evil));
7575b88319eSDaniel Borkmann md = (struct bpf_map_desc *)((char *)ctx + sizeof(struct bpf_loader_ctx));
7585b88319eSDaniel Borkmann for (i = 0; i < nr_maps; i++)
7595b88319eSDaniel Borkmann md[i].initial_value = ptr_to_u64(evil);
7605b88319eSDaniel Borkmann
7615b88319eSDaniel Borkmann libbpf_sha256(gopts.insns, gopts.insns_sz, excl);
7625b88319eSDaniel Borkmann data_sz = gopts.data_sz;
7635b88319eSDaniel Borkmann blob = malloc(data_sz);
7645b88319eSDaniel Borkmann if (!ASSERT_OK_PTR(blob, "blob_alloc"))
7655b88319eSDaniel Borkmann goto free_ctx;
7665b88319eSDaniel Borkmann memcpy(blob, gopts.data, data_sz);
7675b88319eSDaniel Borkmann
7685b88319eSDaniel Borkmann r = run_gen_loader(gopts.insns, gopts.insns_sz, blob, data_sz,
76999b321ddSDaniel Borkmann excl, sizeof(excl), NULL, 0, ctx, ctx_sz, &ran);
7705b88319eSDaniel Borkmann if (!ASSERT_TRUE(ran, "loader ran") ||
7715b88319eSDaniel Borkmann !ASSERT_EQ(r, 0, "loader retval"))
7725b88319eSDaniel Borkmann goto free_blob;
7735b88319eSDaniel Borkmann
7745b88319eSDaniel Borkmann for (i = 0; i < nr_maps; i++) {
7755b88319eSDaniel Borkmann struct bpf_map_info info;
7765b88319eSDaniel Borkmann __u32 ilen = sizeof(info), key = 0;
7775b88319eSDaniel Borkmann __u8 value[64] = {};
7785b88319eSDaniel Borkmann __u64 got;
7795b88319eSDaniel Borkmann int fd = md[i].map_fd;
7805b88319eSDaniel Borkmann
7815b88319eSDaniel Borkmann if (fd <= 0)
7825b88319eSDaniel Borkmann continue;
7835b88319eSDaniel Borkmann memset(&info, 0, sizeof(info));
7845b88319eSDaniel Borkmann if (!ASSERT_OK(bpf_map_get_info_by_fd(fd, &info, &ilen), "map_info"))
7855b88319eSDaniel Borkmann continue;
7865b88319eSDaniel Borkmann if (info.value_size <= sizeof(value) &&
7875b88319eSDaniel Borkmann bpf_map_lookup_elem(fd, &key, value) == 0) {
7885b88319eSDaniel Borkmann memcpy(&got, value, sizeof(got));
7895b88319eSDaniel Borkmann /* attested .data survives; ctx bytes (0xAA..) ignored */
7905b88319eSDaniel Borkmann if (got == DATA_MAGIC)
7915b88319eSDaniel Borkmann found = 1;
7925b88319eSDaniel Borkmann ASSERT_NEQ(got, 0xAAAAAAAAAAAAAAAAULL,
7935b88319eSDaniel Borkmann "ctx initial_value ignored for signed loader");
7945b88319eSDaniel Borkmann }
7955b88319eSDaniel Borkmann }
7965b88319eSDaniel Borkmann ASSERT_EQ(found, 1, "attested .data value preserved");
7975b88319eSDaniel Borkmann
7985b88319eSDaniel Borkmann free_blob:
7995b88319eSDaniel Borkmann free(blob);
8005b88319eSDaniel Borkmann free_ctx:
8015b88319eSDaniel Borkmann close_loader_ctx_fds(ctx, nr_maps, nr_progs);
8025b88319eSDaniel Borkmann free(ctx);
8035b88319eSDaniel Borkmann destroy:
8045b88319eSDaniel Borkmann test_signed_loader_data__destroy(skel);
8055b88319eSDaniel Borkmann }
8065b88319eSDaniel Borkmann
8075b88319eSDaniel Borkmann /*
8085b88319eSDaniel Borkmann * The load-time signature must authenticate the loader instructions: a valid
8095b88319eSDaniel Borkmann * signature loads, and the very same signature over one-byte-tampered insns is
8105b88319eSDaniel Borkmann * rejected. Uses ./verify_sig_setup.sh + ./sign-file at runtime, like
8115b88319eSDaniel Borkmann * verify_pkcs7_sig, and verifies against the session keyring the key was added
8125b88319eSDaniel Borkmann * to. (signature_enforced/_too_large only cover a malformed signature.)
8135b88319eSDaniel Borkmann */
signature_authenticates_insns(void)8145b88319eSDaniel Borkmann static void signature_authenticates_insns(void)
8155b88319eSDaniel Borkmann {
8165b88319eSDaniel Borkmann LIBBPF_OPTS(gen_loader_opts, gopts, .gen_hash = true);
8175b88319eSDaniel Borkmann char dir_tmpl[] = "/tmp/signed_loaderXXXXXX", *dir;
8185b88319eSDaniel Borkmann struct test_signed_loader *skel = NULL;
8195b88319eSDaniel Borkmann __u8 excl[SHA256_DIGEST_LENGTH], sig[8192];
8205b88319eSDaniel Borkmann __u32 sig_sz = sizeof(sig), insns_sz, data_sz, ctx_sz;
8215b88319eSDaniel Borkmann unsigned char *insns = NULL, *tampered = NULL, *blob = NULL;
82299b321ddSDaniel Borkmann unsigned char *signbuf = NULL;
8235b88319eSDaniel Borkmann int nr_maps = 0, nr_progs = 0, r;
8245b88319eSDaniel Borkmann struct bpf_program *p;
8255b88319eSDaniel Borkmann struct bpf_map *m;
8265b88319eSDaniel Borkmann void *ctx = NULL;
8275b88319eSDaniel Borkmann bool ran;
8285b88319eSDaniel Borkmann
8295b88319eSDaniel Borkmann syscall(__NR_request_key, "keyring", "_uid.0", NULL,
8305b88319eSDaniel Borkmann KEY_SPEC_SESSION_KEYRING);
8315b88319eSDaniel Borkmann dir = mkdtemp(dir_tmpl);
8325b88319eSDaniel Borkmann if (!ASSERT_OK_PTR(dir, "mkdtemp"))
8335b88319eSDaniel Borkmann return;
8345b88319eSDaniel Borkmann if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
8355b88319eSDaniel Borkmann rmdir(dir);
8365b88319eSDaniel Borkmann return;
8375b88319eSDaniel Borkmann }
8385b88319eSDaniel Borkmann
8395b88319eSDaniel Borkmann skel = test_signed_loader__open();
8405b88319eSDaniel Borkmann if (!ASSERT_OK_PTR(skel, "skel_open"))
8415b88319eSDaniel Borkmann goto cleanup;
8425b88319eSDaniel Borkmann if (!ASSERT_OK(bpf_object__gen_loader(skel->obj, &gopts), "gen_loader"))
8435b88319eSDaniel Borkmann goto cleanup;
8445b88319eSDaniel Borkmann if (!ASSERT_OK(bpf_object__load(skel->obj), "gen_load"))
8455b88319eSDaniel Borkmann goto cleanup;
8465b88319eSDaniel Borkmann
8475b88319eSDaniel Borkmann bpf_object__for_each_program(p, skel->obj)
8485b88319eSDaniel Borkmann nr_progs++;
8495b88319eSDaniel Borkmann bpf_object__for_each_map(m, skel->obj)
8505b88319eSDaniel Borkmann nr_maps++;
8515b88319eSDaniel Borkmann ctx_sz = sizeof(struct bpf_loader_ctx) +
8525b88319eSDaniel Borkmann nr_maps * sizeof(struct bpf_map_desc) +
8535b88319eSDaniel Borkmann nr_progs * sizeof(struct bpf_prog_desc);
8545b88319eSDaniel Borkmann insns_sz = gopts.insns_sz;
8555b88319eSDaniel Borkmann data_sz = gopts.data_sz;
8565b88319eSDaniel Borkmann ctx = calloc(1, ctx_sz);
8575b88319eSDaniel Borkmann insns = malloc(insns_sz);
8585b88319eSDaniel Borkmann tampered = malloc(insns_sz);
8595b88319eSDaniel Borkmann blob = malloc(data_sz);
8605b88319eSDaniel Borkmann if (!ASSERT_OK_PTR(ctx, "ctx") ||
8615b88319eSDaniel Borkmann !ASSERT_OK_PTR(insns, "insns") ||
8625b88319eSDaniel Borkmann !ASSERT_OK_PTR(tampered, "tampered") ||
8635b88319eSDaniel Borkmann !ASSERT_OK_PTR(blob, "blob"))
8645b88319eSDaniel Borkmann goto cleanup;
8655b88319eSDaniel Borkmann memcpy(insns, gopts.insns, insns_sz);
8665b88319eSDaniel Borkmann memcpy(blob, gopts.data, data_sz);
8675b88319eSDaniel Borkmann libbpf_sha256(insns, insns_sz, excl);
8685b88319eSDaniel Borkmann
86999b321ddSDaniel Borkmann signbuf = malloc((size_t)insns_sz + data_sz);
87099b321ddSDaniel Borkmann if (!ASSERT_OK_PTR(signbuf, "signbuf"))
87199b321ddSDaniel Borkmann goto cleanup;
87299b321ddSDaniel Borkmann memcpy(signbuf, insns, insns_sz);
87399b321ddSDaniel Borkmann memcpy(signbuf + insns_sz, blob, data_sz);
87499b321ddSDaniel Borkmann if (!ASSERT_OK(sign_buf(dir, signbuf, insns_sz + data_sz, sig, &sig_sz),
87599b321ddSDaniel Borkmann "sign-file"))
8765b88319eSDaniel Borkmann goto cleanup;
8775b88319eSDaniel Borkmann
8785b88319eSDaniel Borkmann memset(ctx, 0, ctx_sz);
8795b88319eSDaniel Borkmann ((struct bpf_loader_ctx *)ctx)->sz = ctx_sz;
8805b88319eSDaniel Borkmann r = run_gen_loader(insns, insns_sz, blob, data_sz, excl, sizeof(excl),
88199b321ddSDaniel Borkmann sig, sig_sz, ctx, ctx_sz, &ran);
8825b88319eSDaniel Borkmann ASSERT_TRUE(ran, "valid signature: loader loaded and ran");
8835b88319eSDaniel Borkmann ASSERT_EQ(r, 0, "valid signature accepted");
8845b88319eSDaniel Borkmann close_loader_ctx_fds(ctx, nr_maps, nr_progs);
8855b88319eSDaniel Borkmann
8865b88319eSDaniel Borkmann memcpy(tampered, insns, insns_sz);
8875b88319eSDaniel Borkmann tampered[insns_sz / 2] ^= 0xff;
88899b321ddSDaniel Borkmann /*
88999b321ddSDaniel Borkmann * Bind the metadata map to the tampered loader's own digest, so the
89099b321ddSDaniel Borkmann * verifier's exclusive-map check (excl_prog_sha == prog->digest) passes
89199b321ddSDaniel Borkmann * and the signature - verified after the maps are resolved - is what
89299b321ddSDaniel Borkmann * rejects the load. This is the attacker's best case: even after
89399b321ddSDaniel Borkmann * re-binding the exclusive map to their tampered loader, the signature
89499b321ddSDaniel Borkmann * over the original insns || metadata still fails. (Leaving the map
89599b321ddSDaniel Borkmann * bound to the original digest would instead trip the excl check first.)
89699b321ddSDaniel Borkmann */
89799b321ddSDaniel Borkmann libbpf_sha256(tampered, insns_sz, excl);
8985b88319eSDaniel Borkmann memset(ctx, 0, ctx_sz);
8995b88319eSDaniel Borkmann ((struct bpf_loader_ctx *)ctx)->sz = ctx_sz;
9005b88319eSDaniel Borkmann r = run_gen_loader(tampered, insns_sz, blob, data_sz, excl, sizeof(excl),
90199b321ddSDaniel Borkmann sig, sig_sz, ctx, ctx_sz, &ran);
9025b88319eSDaniel Borkmann ASSERT_FALSE(ran, "tampered loader rejected before run");
9035b88319eSDaniel Borkmann ASSERT_EQ(r, -EKEYREJECTED, "signature is bound to the instructions");
9045b88319eSDaniel Borkmann cleanup:
9055b88319eSDaniel Borkmann free(insns);
9065b88319eSDaniel Borkmann free(tampered);
9075b88319eSDaniel Borkmann free(blob);
90899b321ddSDaniel Borkmann free(signbuf);
90999b321ddSDaniel Borkmann free(ctx);
91099b321ddSDaniel Borkmann test_signed_loader__destroy(skel);
91199b321ddSDaniel Borkmann run_setup("cleanup", dir);
91299b321ddSDaniel Borkmann }
91399b321ddSDaniel Borkmann
signature_authenticates_metadata(void)91499b321ddSDaniel Borkmann static void signature_authenticates_metadata(void)
91599b321ddSDaniel Borkmann {
91699b321ddSDaniel Borkmann LIBBPF_OPTS(gen_loader_opts, gopts, .gen_hash = true);
91799b321ddSDaniel Borkmann char dir_tmpl[] = "/tmp/signed_loaderXXXXXX", *dir;
91899b321ddSDaniel Borkmann struct test_signed_loader *skel = NULL;
91999b321ddSDaniel Borkmann __u8 excl[SHA256_DIGEST_LENGTH], sig[8192];
92099b321ddSDaniel Borkmann __u32 sig_sz = sizeof(sig), insns_sz, data_sz, ctx_sz;
92199b321ddSDaniel Borkmann unsigned char *insns = NULL, *blob = NULL;
92299b321ddSDaniel Borkmann unsigned char *signbuf = NULL;
92399b321ddSDaniel Borkmann int nr_maps = 0, nr_progs = 0, r;
92499b321ddSDaniel Borkmann struct bpf_program *p;
92599b321ddSDaniel Borkmann struct bpf_map *m;
92699b321ddSDaniel Borkmann void *ctx = NULL;
92799b321ddSDaniel Borkmann bool ran;
92899b321ddSDaniel Borkmann
92999b321ddSDaniel Borkmann syscall(__NR_request_key, "keyring", "_uid.0", NULL,
93099b321ddSDaniel Borkmann KEY_SPEC_SESSION_KEYRING);
93199b321ddSDaniel Borkmann dir = mkdtemp(dir_tmpl);
93299b321ddSDaniel Borkmann if (!ASSERT_OK_PTR(dir, "mkdtemp"))
93399b321ddSDaniel Borkmann return;
93499b321ddSDaniel Borkmann if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
93599b321ddSDaniel Borkmann rmdir(dir);
93699b321ddSDaniel Borkmann return;
93799b321ddSDaniel Borkmann }
93899b321ddSDaniel Borkmann
93999b321ddSDaniel Borkmann skel = test_signed_loader__open();
94099b321ddSDaniel Borkmann if (!ASSERT_OK_PTR(skel, "skel_open"))
94199b321ddSDaniel Borkmann goto cleanup;
94299b321ddSDaniel Borkmann if (!ASSERT_OK(bpf_object__gen_loader(skel->obj, &gopts), "gen_loader"))
94399b321ddSDaniel Borkmann goto cleanup;
94499b321ddSDaniel Borkmann if (!ASSERT_OK(bpf_object__load(skel->obj), "gen_load"))
94599b321ddSDaniel Borkmann goto cleanup;
94699b321ddSDaniel Borkmann
94799b321ddSDaniel Borkmann bpf_object__for_each_program(p, skel->obj)
94899b321ddSDaniel Borkmann nr_progs++;
94999b321ddSDaniel Borkmann bpf_object__for_each_map(m, skel->obj)
95099b321ddSDaniel Borkmann nr_maps++;
95199b321ddSDaniel Borkmann ctx_sz = sizeof(struct bpf_loader_ctx) +
95299b321ddSDaniel Borkmann nr_maps * sizeof(struct bpf_map_desc) +
95399b321ddSDaniel Borkmann nr_progs * sizeof(struct bpf_prog_desc);
95499b321ddSDaniel Borkmann insns_sz = gopts.insns_sz;
95599b321ddSDaniel Borkmann data_sz = gopts.data_sz;
95699b321ddSDaniel Borkmann ctx = calloc(1, ctx_sz);
95799b321ddSDaniel Borkmann insns = malloc(insns_sz);
95899b321ddSDaniel Borkmann blob = malloc(data_sz);
95999b321ddSDaniel Borkmann if (!ASSERT_OK_PTR(ctx, "ctx") ||
96099b321ddSDaniel Borkmann !ASSERT_OK_PTR(insns, "insns") ||
96199b321ddSDaniel Borkmann !ASSERT_OK_PTR(blob, "blob"))
96299b321ddSDaniel Borkmann goto cleanup;
96399b321ddSDaniel Borkmann memcpy(insns, gopts.insns, insns_sz);
96499b321ddSDaniel Borkmann memcpy(blob, gopts.data, data_sz);
96599b321ddSDaniel Borkmann libbpf_sha256(insns, insns_sz, excl);
96699b321ddSDaniel Borkmann
96799b321ddSDaniel Borkmann signbuf = malloc((size_t)insns_sz + data_sz);
96899b321ddSDaniel Borkmann if (!ASSERT_OK_PTR(signbuf, "signbuf"))
96999b321ddSDaniel Borkmann goto cleanup;
97099b321ddSDaniel Borkmann memcpy(signbuf, insns, insns_sz);
97199b321ddSDaniel Borkmann memcpy(signbuf + insns_sz, blob, data_sz);
97299b321ddSDaniel Borkmann if (!ASSERT_OK(sign_buf(dir, signbuf, insns_sz + data_sz, sig, &sig_sz),
97399b321ddSDaniel Borkmann "sign-file"))
97499b321ddSDaniel Borkmann goto cleanup;
97599b321ddSDaniel Borkmann
97699b321ddSDaniel Borkmann memset(ctx, 0, ctx_sz);
97799b321ddSDaniel Borkmann ((struct bpf_loader_ctx *)ctx)->sz = ctx_sz;
97899b321ddSDaniel Borkmann r = run_gen_loader(insns, insns_sz, blob, data_sz, excl, sizeof(excl),
97999b321ddSDaniel Borkmann sig, sig_sz, ctx, ctx_sz, &ran);
98099b321ddSDaniel Borkmann ASSERT_TRUE(ran, "valid signature: loader loaded and ran");
98199b321ddSDaniel Borkmann ASSERT_EQ(r, 0, "valid signature accepted");
98299b321ddSDaniel Borkmann close_loader_ctx_fds(ctx, nr_maps, nr_progs);
98399b321ddSDaniel Borkmann
98499b321ddSDaniel Borkmann /*
98599b321ddSDaniel Borkmann * Tamper the metadata after signing while leaving the instructions
98699b321ddSDaniel Borkmann * and thus the exclusive hash binding untouched: the map freezes
98799b321ddSDaniel Borkmann * fine and excl_prog_sha still matches the loader's digest, so the
98899b321ddSDaniel Borkmann * load reaches signature verification, which folds the live frozen
98999b321ddSDaniel Borkmann * map bytes into the checked payload and must reject the modified
99099b321ddSDaniel Borkmann * blob. A kernel folding anything but the map contents themselves
99199b321ddSDaniel Borkmann * would wrongly accept this load.
99299b321ddSDaniel Borkmann */
99399b321ddSDaniel Borkmann blob[data_sz / 2] ^= 0xff;
99499b321ddSDaniel Borkmann memset(ctx, 0, ctx_sz);
99599b321ddSDaniel Borkmann ((struct bpf_loader_ctx *)ctx)->sz = ctx_sz;
99699b321ddSDaniel Borkmann r = run_gen_loader(insns, insns_sz, blob, data_sz, excl, sizeof(excl),
99799b321ddSDaniel Borkmann sig, sig_sz, ctx, ctx_sz, &ran);
99899b321ddSDaniel Borkmann ASSERT_FALSE(ran, "tampered metadata rejected before run");
99999b321ddSDaniel Borkmann ASSERT_EQ(r, -EKEYREJECTED, "signature is bound to the metadata");
100099b321ddSDaniel Borkmann cleanup:
100199b321ddSDaniel Borkmann free(insns);
100299b321ddSDaniel Borkmann free(blob);
100399b321ddSDaniel Borkmann free(signbuf);
10045b88319eSDaniel Borkmann free(ctx);
10055b88319eSDaniel Borkmann test_signed_loader__destroy(skel);
10065b88319eSDaniel Borkmann run_setup("cleanup", dir);
10075b88319eSDaniel Borkmann }
10085b88319eSDaniel Borkmann
make_excl_map(__u32 flags,__u32 value_size)10095b88319eSDaniel Borkmann static int make_excl_map(__u32 flags, __u32 value_size)
10105b88319eSDaniel Borkmann {
10115b88319eSDaniel Borkmann LIBBPF_OPTS(bpf_map_create_opts, opts);
10125b88319eSDaniel Borkmann __u8 hash[SHA256_DIGEST_LENGTH] = { 1 }; /* any 32-byte value */
10135b88319eSDaniel Borkmann
10145b88319eSDaniel Borkmann opts.excl_prog_hash = hash;
10155b88319eSDaniel Borkmann opts.excl_prog_hash_size = sizeof(hash);
10165b88319eSDaniel Borkmann opts.map_flags = flags;
10175b88319eSDaniel Borkmann return bpf_map_create(BPF_MAP_TYPE_ARRAY, "md", 4, value_size, 1, &opts);
10185b88319eSDaniel Borkmann }
10195b88319eSDaniel Borkmann
hash_requires_frozen(void)10205b88319eSDaniel Borkmann static void hash_requires_frozen(void)
10215b88319eSDaniel Borkmann {
10225b88319eSDaniel Borkmann __u8 hbuf[SHA256_DIGEST_LENGTH], val[64] = {};
10235b88319eSDaniel Borkmann struct bpf_map_info info;
10245b88319eSDaniel Borkmann __u32 ilen, key = 0;
10255b88319eSDaniel Borkmann int fd;
10265b88319eSDaniel Borkmann
10275b88319eSDaniel Borkmann fd = make_excl_map(0, sizeof(val));
10285b88319eSDaniel Borkmann if (!ASSERT_OK_FD(fd, "excl_map"))
10295b88319eSDaniel Borkmann return;
10305b88319eSDaniel Borkmann ASSERT_OK(bpf_map_update_elem(fd, &key, val, 0), "update");
10315b88319eSDaniel Borkmann
10325b88319eSDaniel Borkmann memset(&info, 0, sizeof(info));
10335b88319eSDaniel Borkmann info.hash = ptr_to_u64(hbuf);
10345b88319eSDaniel Borkmann info.hash_size = sizeof(hbuf);
10355b88319eSDaniel Borkmann ilen = sizeof(info);
10365b88319eSDaniel Borkmann ASSERT_EQ(bpf_map_get_info_by_fd(fd, &info, &ilen), -EPERM,
10375b88319eSDaniel Borkmann "hash of unfrozen map rejected");
10385b88319eSDaniel Borkmann close(fd);
10395b88319eSDaniel Borkmann }
10405b88319eSDaniel Borkmann
no_update_after_freeze(void)10415b88319eSDaniel Borkmann static void no_update_after_freeze(void)
10425b88319eSDaniel Borkmann {
10435b88319eSDaniel Borkmann __u8 val[64] = {};
10445b88319eSDaniel Borkmann __u32 key = 0;
10455b88319eSDaniel Borkmann int fd;
10465b88319eSDaniel Borkmann
10475b88319eSDaniel Borkmann fd = make_excl_map(0, sizeof(val));
10485b88319eSDaniel Borkmann if (!ASSERT_OK_FD(fd, "excl_map"))
10495b88319eSDaniel Borkmann return;
10505b88319eSDaniel Borkmann ASSERT_OK(bpf_map_update_elem(fd, &key, val, 0), "update");
10515b88319eSDaniel Borkmann ASSERT_OK(bpf_map_freeze(fd), "freeze");
10525b88319eSDaniel Borkmann ASSERT_EQ(bpf_map_update_elem(fd, &key, val, 0), -EPERM,
10535b88319eSDaniel Borkmann "update after freeze rejected");
10545b88319eSDaniel Borkmann close(fd);
10555b88319eSDaniel Borkmann }
10565b88319eSDaniel Borkmann
freeze_writable_mmap(void)10575b88319eSDaniel Borkmann static void freeze_writable_mmap(void)
10585b88319eSDaniel Borkmann {
10595b88319eSDaniel Borkmann void *w;
10605b88319eSDaniel Borkmann int fd;
10615b88319eSDaniel Borkmann
10625b88319eSDaniel Borkmann fd = make_excl_map(BPF_F_MMAPABLE, 4096);
10635b88319eSDaniel Borkmann if (!ASSERT_OK_FD(fd, "excl_mmapable_map"))
10645b88319eSDaniel Borkmann return;
10655b88319eSDaniel Borkmann w = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
10665b88319eSDaniel Borkmann if (ASSERT_OK_PTR(w, "writable_mmap")) {
10675b88319eSDaniel Borkmann ASSERT_EQ(bpf_map_freeze(fd), -EBUSY,
10685b88319eSDaniel Borkmann "freeze rejected while writable mmap held");
10695b88319eSDaniel Borkmann munmap(w, 4096);
10705b88319eSDaniel Borkmann }
10715b88319eSDaniel Borkmann close(fd);
10725b88319eSDaniel Borkmann }
10735b88319eSDaniel Borkmann
no_writable_mmap_frozen(void)10745b88319eSDaniel Borkmann static void no_writable_mmap_frozen(void)
10755b88319eSDaniel Borkmann {
10765b88319eSDaniel Borkmann void *w;
10775b88319eSDaniel Borkmann int fd;
10785b88319eSDaniel Borkmann
10795b88319eSDaniel Borkmann fd = make_excl_map(BPF_F_MMAPABLE, 4096);
10805b88319eSDaniel Borkmann if (!ASSERT_OK_FD(fd, "excl_mmapable_map"))
10815b88319eSDaniel Borkmann return;
10825b88319eSDaniel Borkmann ASSERT_OK(bpf_map_freeze(fd), "freeze");
10835b88319eSDaniel Borkmann w = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
10845b88319eSDaniel Borkmann ASSERT_EQ(w, MAP_FAILED, "writable mmap of frozen map rejected");
10855b88319eSDaniel Borkmann if (w != MAP_FAILED)
10865b88319eSDaniel Borkmann munmap(w, 4096);
10875b88319eSDaniel Borkmann close(fd);
10885b88319eSDaniel Borkmann }
10895b88319eSDaniel Borkmann
map_hash_matches_libbpf(void)10905b88319eSDaniel Borkmann static void map_hash_matches_libbpf(void)
10915b88319eSDaniel Borkmann {
10925b88319eSDaniel Borkmann __u8 kbuf[SHA256_DIGEST_LENGTH], lbuf[SHA256_DIGEST_LENGTH], val[64] = {};
10935b88319eSDaniel Borkmann struct bpf_map_info info;
10945b88319eSDaniel Borkmann __u32 ilen, key = 0;
10955b88319eSDaniel Borkmann int fd, i;
10965b88319eSDaniel Borkmann
10975b88319eSDaniel Borkmann /*
10985b88319eSDaniel Borkmann * The signing scheme assumes the kernel's map hash equals what libbpf
10995b88319eSDaniel Borkmann * computes over the same bytes (gen_loader bakes libbpf_sha256(blob);
11005b88319eSDaniel Borkmann * the kernel recomputes via array_map_get_hash). Pin that they agree.
11015b88319eSDaniel Borkmann */
11025b88319eSDaniel Borkmann for (i = 0; i < (int)sizeof(val); i++)
11035b88319eSDaniel Borkmann val[i] = i * 7 + 1;
11045b88319eSDaniel Borkmann fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "h", 4, sizeof(val), 1, NULL);
11055b88319eSDaniel Borkmann if (!ASSERT_OK_FD(fd, "array_map"))
11065b88319eSDaniel Borkmann return;
11075b88319eSDaniel Borkmann ASSERT_OK(bpf_map_update_elem(fd, &key, val, 0), "update");
11085b88319eSDaniel Borkmann ASSERT_OK(bpf_map_freeze(fd), "freeze");
11095b88319eSDaniel Borkmann memset(&info, 0, sizeof(info));
11105b88319eSDaniel Borkmann info.hash = ptr_to_u64(kbuf);
11115b88319eSDaniel Borkmann info.hash_size = sizeof(kbuf);
11125b88319eSDaniel Borkmann ilen = sizeof(info);
11135b88319eSDaniel Borkmann if (ASSERT_OK(bpf_map_get_info_by_fd(fd, &info, &ilen), "get_hash")) {
11145b88319eSDaniel Borkmann libbpf_sha256(val, sizeof(val), lbuf);
11155b88319eSDaniel Borkmann ASSERT_EQ(memcmp(kbuf, lbuf, sizeof(kbuf)), 0,
11165b88319eSDaniel Borkmann "kernel map hash matches libbpf_sha256");
11175b88319eSDaniel Borkmann }
11185b88319eSDaniel Borkmann close(fd);
11195b88319eSDaniel Borkmann }
11205b88319eSDaniel Borkmann
map_hash_multi_element(void)11215b88319eSDaniel Borkmann static void map_hash_multi_element(void)
11225b88319eSDaniel Borkmann {
11235b88319eSDaniel Borkmann const __u32 nr = 8, value_size = 64;
11245b88319eSDaniel Borkmann __u8 kbuf[SHA256_DIGEST_LENGTH], lbuf[SHA256_DIGEST_LENGTH];
11255b88319eSDaniel Borkmann struct bpf_map_info info;
11265b88319eSDaniel Borkmann __u32 ilen, i, j;
11275b88319eSDaniel Borkmann __u8 *full;
11285b88319eSDaniel Borkmann int fd;
11295b88319eSDaniel Borkmann
11305b88319eSDaniel Borkmann /*
11315b88319eSDaniel Borkmann * array_map_get_hash() hashes elem_size * max_entries (the whole value
11325b88319eSDaniel Borkmann * area), not just element 0. With an 8-aligned value_size elem_size has
11335b88319eSDaniel Borkmann * no padding, so pin that a >1-entry array's kernel hash equals
11345b88319eSDaniel Borkmann * libbpf_sha256() over the full, concatenated element contents.
11355b88319eSDaniel Borkmann */
11365b88319eSDaniel Borkmann fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "h", 4, value_size, nr, NULL);
11375b88319eSDaniel Borkmann if (!ASSERT_OK_FD(fd, "array_map"))
11385b88319eSDaniel Borkmann return;
11395b88319eSDaniel Borkmann full = calloc(nr, value_size);
11405b88319eSDaniel Borkmann if (!ASSERT_OK_PTR(full, "buf"))
11415b88319eSDaniel Borkmann goto close_fd;
11425b88319eSDaniel Borkmann for (i = 0; i < nr; i++) {
11435b88319eSDaniel Borkmann __u8 *v = full + i * value_size;
11445b88319eSDaniel Borkmann
11455b88319eSDaniel Borkmann for (j = 0; j < value_size; j++)
11465b88319eSDaniel Borkmann v[j] = i * 31 + j * 7 + 1;
11475b88319eSDaniel Borkmann ASSERT_OK(bpf_map_update_elem(fd, &i, v, 0), "update");
11485b88319eSDaniel Borkmann }
11495b88319eSDaniel Borkmann ASSERT_OK(bpf_map_freeze(fd), "freeze");
11505b88319eSDaniel Borkmann memset(&info, 0, sizeof(info));
11515b88319eSDaniel Borkmann info.hash = ptr_to_u64(kbuf);
11525b88319eSDaniel Borkmann info.hash_size = sizeof(kbuf);
11535b88319eSDaniel Borkmann ilen = sizeof(info);
11545b88319eSDaniel Borkmann if (ASSERT_OK(bpf_map_get_info_by_fd(fd, &info, &ilen), "get_hash")) {
11555b88319eSDaniel Borkmann libbpf_sha256(full, (size_t)nr * value_size, lbuf);
11565b88319eSDaniel Borkmann ASSERT_EQ(memcmp(kbuf, lbuf, sizeof(kbuf)), 0,
11575b88319eSDaniel Borkmann "kernel hash covers full multi-element value area");
11585b88319eSDaniel Borkmann }
11595b88319eSDaniel Borkmann free(full);
11605b88319eSDaniel Borkmann close_fd:
11615b88319eSDaniel Borkmann close(fd);
11625b88319eSDaniel Borkmann }
11635b88319eSDaniel Borkmann
map_hash_bad_size(void)11645b88319eSDaniel Borkmann static void map_hash_bad_size(void)
11655b88319eSDaniel Borkmann {
11665b88319eSDaniel Borkmann __u8 kbuf[SHA256_DIGEST_LENGTH], val[64] = {};
11675b88319eSDaniel Borkmann struct bpf_map_info info;
11685b88319eSDaniel Borkmann __u32 ilen, key = 0;
11695b88319eSDaniel Borkmann int fd;
11705b88319eSDaniel Borkmann
11715b88319eSDaniel Borkmann fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "h", 4, sizeof(val), 1, NULL);
11725b88319eSDaniel Borkmann if (!ASSERT_OK_FD(fd, "array_map"))
11735b88319eSDaniel Borkmann return;
11745b88319eSDaniel Borkmann ASSERT_OK(bpf_map_update_elem(fd, &key, val, 0), "update");
11755b88319eSDaniel Borkmann ASSERT_OK(bpf_map_freeze(fd), "freeze");
11765b88319eSDaniel Borkmann memset(&info, 0, sizeof(info));
11775b88319eSDaniel Borkmann info.hash = ptr_to_u64(kbuf);
11785b88319eSDaniel Borkmann info.hash_size = sizeof(kbuf) / 2;
11795b88319eSDaniel Borkmann ilen = sizeof(info);
11805b88319eSDaniel Borkmann ASSERT_EQ(bpf_map_get_info_by_fd(fd, &info, &ilen), -EINVAL,
11815b88319eSDaniel Borkmann "wrong hash_size rejected");
11825b88319eSDaniel Borkmann close(fd);
11835b88319eSDaniel Borkmann }
11845b88319eSDaniel Borkmann
map_hash_unsupported_type(void)11855b88319eSDaniel Borkmann static void map_hash_unsupported_type(void)
11865b88319eSDaniel Borkmann {
11875b88319eSDaniel Borkmann __u8 kbuf[SHA256_DIGEST_LENGTH];
11885b88319eSDaniel Borkmann struct bpf_map_info info;
11895b88319eSDaniel Borkmann __u32 ilen;
11905b88319eSDaniel Borkmann int fd;
11915b88319eSDaniel Borkmann
11925b88319eSDaniel Borkmann /* Only arrays implement map_get_hash; a hash map must be refused. */
11935b88319eSDaniel Borkmann fd = bpf_map_create(BPF_MAP_TYPE_HASH, "h", 4, 8, 4, NULL);
11945b88319eSDaniel Borkmann if (!ASSERT_OK_FD(fd, "hash_map"))
11955b88319eSDaniel Borkmann return;
11965b88319eSDaniel Borkmann memset(&info, 0, sizeof(info));
11975b88319eSDaniel Borkmann info.hash = ptr_to_u64(kbuf);
11985b88319eSDaniel Borkmann info.hash_size = sizeof(kbuf);
11995b88319eSDaniel Borkmann ilen = sizeof(info);
12005b88319eSDaniel Borkmann ASSERT_EQ(bpf_map_get_info_by_fd(fd, &info, &ilen), -EINVAL,
12015b88319eSDaniel Borkmann "hash unsupported for non-array map");
12025b88319eSDaniel Borkmann close(fd);
12035b88319eSDaniel Borkmann }
12045b88319eSDaniel Borkmann
setup_meta_map(const struct gen_loader_fixture * f)12058ddce416SDaniel Borkmann static int setup_meta_map(const struct gen_loader_fixture *f)
12068ddce416SDaniel Borkmann {
12078ddce416SDaniel Borkmann LIBBPF_OPTS(bpf_map_create_opts, mopts,
12088ddce416SDaniel Borkmann .excl_prog_hash = f->excl,
12098ddce416SDaniel Borkmann .excl_prog_hash_size = sizeof(f->excl));
12108ddce416SDaniel Borkmann __u32 key = 0;
12118ddce416SDaniel Borkmann int fd;
12128ddce416SDaniel Borkmann
12138ddce416SDaniel Borkmann fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "__loader.map", 4,
12148ddce416SDaniel Borkmann f->data_sz, 1, &mopts);
12158ddce416SDaniel Borkmann if (fd < 0)
12168ddce416SDaniel Borkmann return -errno;
12178ddce416SDaniel Borkmann if (bpf_map_update_elem(fd, &key, f->blob, 0) || bpf_map_freeze(fd)) {
12188ddce416SDaniel Borkmann close(fd);
12198ddce416SDaniel Borkmann return -errno;
12208ddce416SDaniel Borkmann }
12218ddce416SDaniel Borkmann return fd;
12228ddce416SDaniel Borkmann }
12238ddce416SDaniel Borkmann
lsm_signature_verdict(void)12248ddce416SDaniel Borkmann static void lsm_signature_verdict(void)
12258ddce416SDaniel Borkmann {
12268ddce416SDaniel Borkmann char dir_tmpl[] = "/tmp/signed_loader_lsmXXXXXX", *dir = NULL;
12278ddce416SDaniel Borkmann struct test_signed_loader_lsm *lsm = NULL;
122899b321ddSDaniel Borkmann __u32 sig_sz = 8192, msig_sz = 8192;
12298ddce416SDaniel Borkmann int map_fd = -1, prog_fd = -1;
12308ddce416SDaniel Borkmann bool have_fixture = false;
12318ddce416SDaniel Borkmann struct gen_loader_fixture f;
123299b321ddSDaniel Borkmann unsigned char *buf;
12338ddce416SDaniel Borkmann __s32 ses_serial;
12348ddce416SDaniel Borkmann __u8 sig[8192];
12358ddce416SDaniel Borkmann
12368ddce416SDaniel Borkmann lsm = test_signed_loader_lsm__open_and_load();
12378ddce416SDaniel Borkmann if (!ASSERT_OK_PTR(lsm, "lsm_skel_load"))
12388ddce416SDaniel Borkmann return;
12398ddce416SDaniel Borkmann lsm->bss->monitored_tid = sys_gettid();
12408ddce416SDaniel Borkmann if (!ASSERT_OK(test_signed_loader_lsm__attach(lsm), "lsm_attach"))
12418ddce416SDaniel Borkmann goto out;
12428ddce416SDaniel Borkmann
12438ddce416SDaniel Borkmann have_fixture = true;
12448ddce416SDaniel Borkmann if (gen_loader_fixture_init(&f) != 0)
12458ddce416SDaniel Borkmann goto out;
12468ddce416SDaniel Borkmann
12478ddce416SDaniel Borkmann map_fd = setup_meta_map(&f);
12488ddce416SDaniel Borkmann if (!ASSERT_OK_FD(map_fd, "meta_map_unsigned"))
12498ddce416SDaniel Borkmann goto out;
12508ddce416SDaniel Borkmann lsm->bss->seen = 0;
125199b321ddSDaniel Borkmann prog_fd = load_loader(f.gopts.insns, f.gopts.insns_sz, map_fd, NULL, 0, 0, 0);
12528ddce416SDaniel Borkmann close(map_fd);
12538ddce416SDaniel Borkmann map_fd = -1;
12548ddce416SDaniel Borkmann if (!ASSERT_OK_FD(prog_fd, "unsigned loader load"))
12558ddce416SDaniel Borkmann goto out;
12568ddce416SDaniel Borkmann close(prog_fd);
12578ddce416SDaniel Borkmann prog_fd = -1;
12588ddce416SDaniel Borkmann if (!ASSERT_NEQ(lsm->bss->seen, 0, "bpf LSM in the active LSM set"))
12598ddce416SDaniel Borkmann goto out;
12608ddce416SDaniel Borkmann ASSERT_EQ(lsm->bss->seen, 1, "unsigned: one observed load");
12618ddce416SDaniel Borkmann ASSERT_EQ(lsm->bss->sig_verdict, BPF_SIG_UNSIGNED, "unsigned verdict");
12628ddce416SDaniel Borkmann ASSERT_EQ(lsm->bss->sig_keyring_type, BPF_SIG_KEYRING_NONE, "unsigned keyring type");
12638ddce416SDaniel Borkmann ASSERT_EQ(lsm->bss->sig_keyring_serial, 0, "unsigned: no keyring serial");
12648ddce416SDaniel Borkmann
12658ddce416SDaniel Borkmann syscall(__NR_request_key, "keyring", "_uid.0", NULL,
12668ddce416SDaniel Borkmann KEY_SPEC_SESSION_KEYRING);
12678ddce416SDaniel Borkmann dir = mkdtemp(dir_tmpl);
12688ddce416SDaniel Borkmann if (!ASSERT_OK_PTR(dir, "mkdtemp"))
12698ddce416SDaniel Borkmann goto out;
12708ddce416SDaniel Borkmann if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
12718ddce416SDaniel Borkmann rmdir(dir);
12728ddce416SDaniel Borkmann dir = NULL;
12738ddce416SDaniel Borkmann goto out;
12748ddce416SDaniel Borkmann }
12758ddce416SDaniel Borkmann if (!ASSERT_OK(sign_buf(dir, f.gopts.insns, f.gopts.insns_sz, sig,
12768ddce416SDaniel Borkmann &sig_sz), "sign-file"))
12778ddce416SDaniel Borkmann goto out;
12788ddce416SDaniel Borkmann
12798ddce416SDaniel Borkmann map_fd = setup_meta_map(&f);
12808ddce416SDaniel Borkmann if (!ASSERT_OK_FD(map_fd, "meta_map_signed"))
12818ddce416SDaniel Borkmann goto out;
12828ddce416SDaniel Borkmann lsm->bss->seen = 0;
12838ddce416SDaniel Borkmann prog_fd = load_loader(f.gopts.insns, f.gopts.insns_sz, map_fd, sig,
128499b321ddSDaniel Borkmann sig_sz, KEY_SPEC_SESSION_KEYRING, 0);
12858ddce416SDaniel Borkmann close(map_fd);
12868ddce416SDaniel Borkmann map_fd = -1;
128799b321ddSDaniel Borkmann ASSERT_EQ(prog_fd, -EACCES, "unfolded metadata rejected");
128899b321ddSDaniel Borkmann if (prog_fd >= 0)
12898ddce416SDaniel Borkmann close(prog_fd);
12908ddce416SDaniel Borkmann prog_fd = -1;
12918ddce416SDaniel Borkmann
12928ddce416SDaniel Borkmann ses_serial = syscall(__NR_keyctl, KEYCTL_GET_KEYRING_ID,
12938ddce416SDaniel Borkmann KEY_SPEC_SESSION_KEYRING, 0);
12948ddce416SDaniel Borkmann ASSERT_EQ(lsm->bss->seen, 1, "signed: one observed load");
129599b321ddSDaniel Borkmann ASSERT_EQ(lsm->bss->sig_verdict, BPF_SIG_VERIFIED,
129699b321ddSDaniel Borkmann "admission saw a valid signature");
12978ddce416SDaniel Borkmann ASSERT_EQ(lsm->bss->sig_keyring_type, BPF_SIG_KEYRING_USER, "signed keyring type");
12988ddce416SDaniel Borkmann ASSERT_GT(ses_serial, 0, "session keyring serial resolved");
12998ddce416SDaniel Borkmann ASSERT_EQ(lsm->bss->sig_keyring_serial, ses_serial,
13008ddce416SDaniel Borkmann "signed: validated against session keyring");
130199b321ddSDaniel Borkmann
130299b321ddSDaniel Borkmann buf = malloc((size_t)f.gopts.insns_sz + f.data_sz);
130399b321ddSDaniel Borkmann if (!ASSERT_OK_PTR(buf, "meta_signbuf"))
130499b321ddSDaniel Borkmann goto out;
130599b321ddSDaniel Borkmann memcpy(buf, f.gopts.insns, f.gopts.insns_sz);
130699b321ddSDaniel Borkmann memcpy(buf + f.gopts.insns_sz, f.blob, f.data_sz);
130799b321ddSDaniel Borkmann if (!ASSERT_OK(sign_buf(dir, buf, f.gopts.insns_sz + f.data_sz,
130899b321ddSDaniel Borkmann sig, &msig_sz), "sign insns||metadata")) {
130999b321ddSDaniel Borkmann free(buf);
131099b321ddSDaniel Borkmann goto out;
131199b321ddSDaniel Borkmann }
131299b321ddSDaniel Borkmann free(buf);
131399b321ddSDaniel Borkmann
131499b321ddSDaniel Borkmann map_fd = setup_meta_map(&f);
131599b321ddSDaniel Borkmann if (!ASSERT_OK_FD(map_fd, "meta_map_bound"))
131699b321ddSDaniel Borkmann goto out;
131799b321ddSDaniel Borkmann lsm->bss->seen = 0;
131899b321ddSDaniel Borkmann prog_fd = load_loader(f.gopts.insns, f.gopts.insns_sz, map_fd, sig,
131999b321ddSDaniel Borkmann msig_sz, KEY_SPEC_SESSION_KEYRING, 1);
132099b321ddSDaniel Borkmann close(map_fd);
132199b321ddSDaniel Borkmann map_fd = -1;
132299b321ddSDaniel Borkmann if (!ASSERT_OK_FD(prog_fd, "metadata-bound loader load"))
132399b321ddSDaniel Borkmann goto out;
132499b321ddSDaniel Borkmann close(prog_fd);
132599b321ddSDaniel Borkmann prog_fd = -1;
132699b321ddSDaniel Borkmann ASSERT_EQ(lsm->bss->seen, 1, "metadata: one observed load");
132799b321ddSDaniel Borkmann ASSERT_EQ(lsm->bss->sig_verdict, BPF_SIG_VERIFIED,
132899b321ddSDaniel Borkmann "metadata-bound verdict");
13298ddce416SDaniel Borkmann out:
13308ddce416SDaniel Borkmann if (map_fd >= 0)
13318ddce416SDaniel Borkmann close(map_fd);
13328ddce416SDaniel Borkmann if (prog_fd >= 0)
13338ddce416SDaniel Borkmann close(prog_fd);
13348ddce416SDaniel Borkmann if (have_fixture)
13358ddce416SDaniel Borkmann gen_loader_fixture_fini(&f);
13368ddce416SDaniel Borkmann if (dir)
13378ddce416SDaniel Borkmann run_setup("cleanup", dir);
13388ddce416SDaniel Borkmann test_signed_loader_lsm__destroy(lsm);
13398ddce416SDaniel Borkmann }
13408ddce416SDaniel Borkmann
134199b321ddSDaniel Borkmann /*
134299b321ddSDaniel Borkmann * Load-time metadata verification: the kernel folds the frozen metadata map
134399b321ddSDaniel Borkmann * into the signature (insns || metadata) and checks it at BPF_PROG_LOAD via
134499b321ddSDaniel Borkmann * fd_array_cnt, rather than the loader checking from within BPF. Sign that
134599b321ddSDaniel Borkmann * concatenation, hand the kernel the map, and confirm the signed loader loads,
134699b321ddSDaniel Borkmann * runs, and installs its target.
134799b321ddSDaniel Borkmann */
loadtime_drive(const char * dir,const void * insns,__u32 insns_sz,const void * data,__u32 data_sz,const __u8 * excl,void * ctx,__u32 ctx_sz,int * load_ret,bool * ran)134899b321ddSDaniel Borkmann static int loadtime_drive(const char *dir, const void *insns, __u32 insns_sz,
134999b321ddSDaniel Borkmann const void *data, __u32 data_sz, const __u8 *excl,
135099b321ddSDaniel Borkmann void *ctx, __u32 ctx_sz, int *load_ret, bool *ran)
135199b321ddSDaniel Borkmann {
135299b321ddSDaniel Borkmann LIBBPF_OPTS(bpf_map_create_opts, mopts,
135399b321ddSDaniel Borkmann .excl_prog_hash = excl,
135499b321ddSDaniel Borkmann .excl_prog_hash_size = SHA256_DIGEST_LENGTH);
135599b321ddSDaniel Borkmann __u32 sig_sz = 8192, key = 0;
135699b321ddSDaniel Borkmann unsigned char *buf = NULL;
135799b321ddSDaniel Borkmann int map_fd, prog_fd, ret = 0;
135899b321ddSDaniel Borkmann union bpf_attr attr;
135999b321ddSDaniel Borkmann __u8 sig[8192];
136099b321ddSDaniel Borkmann
136199b321ddSDaniel Borkmann *ran = false;
136299b321ddSDaniel Borkmann *load_ret = 0;
136399b321ddSDaniel Borkmann
136499b321ddSDaniel Borkmann /*
136599b321ddSDaniel Borkmann * Metadata map, bound to the loader digest and frozen, exactly as
136699b321ddSDaniel Borkmann * skel_internal.h's bpf_load_and_run() sets it up.
136799b321ddSDaniel Borkmann */
136899b321ddSDaniel Borkmann map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "__loader.map", 4,
136999b321ddSDaniel Borkmann data_sz, 1, &mopts);
137099b321ddSDaniel Borkmann if (map_fd < 0) {
137199b321ddSDaniel Borkmann ret = -errno;
137299b321ddSDaniel Borkmann goto out_load;
137399b321ddSDaniel Borkmann }
137499b321ddSDaniel Borkmann if (bpf_map_update_elem(map_fd, &key, data, 0) || bpf_map_freeze(map_fd)) {
137599b321ddSDaniel Borkmann ret = -errno;
137699b321ddSDaniel Borkmann goto out_load;
137799b321ddSDaniel Borkmann }
137899b321ddSDaniel Borkmann
137999b321ddSDaniel Borkmann /* Sign insns || metadata, the same bytes the kernel reconstructs. */
138099b321ddSDaniel Borkmann buf = malloc((size_t)insns_sz + data_sz);
138199b321ddSDaniel Borkmann if (!buf) {
138299b321ddSDaniel Borkmann ret = -ENOMEM;
138399b321ddSDaniel Borkmann goto out_load;
138499b321ddSDaniel Borkmann }
138599b321ddSDaniel Borkmann memcpy(buf, insns, insns_sz);
138699b321ddSDaniel Borkmann memcpy(buf + insns_sz, data, data_sz);
138799b321ddSDaniel Borkmann ret = sign_buf(dir, buf, insns_sz + data_sz, sig, &sig_sz);
138899b321ddSDaniel Borkmann if (ret)
138999b321ddSDaniel Borkmann goto out_load;
139099b321ddSDaniel Borkmann
139199b321ddSDaniel Borkmann memset(&attr, 0, sizeof(attr));
139299b321ddSDaniel Borkmann attr.prog_type = BPF_PROG_TYPE_SYSCALL;
139399b321ddSDaniel Borkmann attr.insns = ptr_to_u64(insns);
139499b321ddSDaniel Borkmann attr.insn_cnt = insns_sz / sizeof(struct bpf_insn);
139599b321ddSDaniel Borkmann attr.license = ptr_to_u64("Dual BSD/GPL");
139699b321ddSDaniel Borkmann attr.prog_flags = BPF_F_SLEEPABLE;
139799b321ddSDaniel Borkmann attr.fd_array = ptr_to_u64(&map_fd);
139899b321ddSDaniel Borkmann attr.signature = ptr_to_u64(sig);
139999b321ddSDaniel Borkmann attr.signature_size = sig_sz;
140099b321ddSDaniel Borkmann attr.keyring_id = KEY_SPEC_SESSION_KEYRING;
140199b321ddSDaniel Borkmann attr.fd_array_cnt = 1;
140299b321ddSDaniel Borkmann memcpy(attr.prog_name, "__loader.prog", sizeof("__loader.prog"));
140399b321ddSDaniel Borkmann prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
140499b321ddSDaniel Borkmann offsetofend(union bpf_attr, keyring_id));
140599b321ddSDaniel Borkmann if (prog_fd < 0) {
140699b321ddSDaniel Borkmann ret = -errno;
140799b321ddSDaniel Borkmann goto out_load;
140899b321ddSDaniel Borkmann }
140999b321ddSDaniel Borkmann
141099b321ddSDaniel Borkmann memset(&attr, 0, sizeof(attr));
141199b321ddSDaniel Borkmann attr.test.prog_fd = prog_fd;
141299b321ddSDaniel Borkmann attr.test.ctx_in = ptr_to_u64(ctx);
141399b321ddSDaniel Borkmann attr.test.ctx_size_in = ctx_sz;
141499b321ddSDaniel Borkmann if (syscall(__NR_bpf, BPF_PROG_RUN, &attr,
141599b321ddSDaniel Borkmann offsetofend(union bpf_attr, test)) < 0) {
141699b321ddSDaniel Borkmann ret = -errno;
141799b321ddSDaniel Borkmann goto out_prog;
141899b321ddSDaniel Borkmann }
141999b321ddSDaniel Borkmann *ran = true;
142099b321ddSDaniel Borkmann ret = (int)attr.test.retval;
142199b321ddSDaniel Borkmann out_prog:
142299b321ddSDaniel Borkmann close(prog_fd);
142399b321ddSDaniel Borkmann goto out_map;
142499b321ddSDaniel Borkmann out_load:
142599b321ddSDaniel Borkmann *load_ret = ret;
142699b321ddSDaniel Borkmann out_map:
142799b321ddSDaniel Borkmann free(buf);
142899b321ddSDaniel Borkmann if (map_fd >= 0)
142999b321ddSDaniel Borkmann close(map_fd);
143099b321ddSDaniel Borkmann return ret;
143199b321ddSDaniel Borkmann }
143299b321ddSDaniel Borkmann
loadtime_verify(struct bpf_object * obj,int expect_maps)143399b321ddSDaniel Borkmann static void loadtime_verify(struct bpf_object *obj, int expect_maps)
143499b321ddSDaniel Borkmann {
143599b321ddSDaniel Borkmann LIBBPF_OPTS(gen_loader_opts, gopts, .gen_hash = true);
143699b321ddSDaniel Borkmann char dir_tmpl[] = "/tmp/signed_loader_ltXXXXXX", *dir = NULL;
143799b321ddSDaniel Borkmann int nr_maps = 0, nr_progs = 0, load_ret = 0, r;
143899b321ddSDaniel Borkmann __u8 excl[SHA256_DIGEST_LENGTH];
143999b321ddSDaniel Borkmann struct bpf_prog_desc *pd;
144099b321ddSDaniel Borkmann struct bpf_map_desc *md;
144199b321ddSDaniel Borkmann unsigned char *blob = NULL;
144299b321ddSDaniel Borkmann struct bpf_program *p;
144399b321ddSDaniel Borkmann struct bpf_map *m;
144499b321ddSDaniel Borkmann __u32 ctx_sz, data_sz;
144599b321ddSDaniel Borkmann void *ctx = NULL;
144699b321ddSDaniel Borkmann bool ran = false;
144799b321ddSDaniel Borkmann
144899b321ddSDaniel Borkmann syscall(__NR_request_key, "keyring", "_uid.0", NULL,
144999b321ddSDaniel Borkmann KEY_SPEC_SESSION_KEYRING);
145099b321ddSDaniel Borkmann dir = mkdtemp(dir_tmpl);
145199b321ddSDaniel Borkmann if (!ASSERT_OK_PTR(dir, "mkdtemp"))
145299b321ddSDaniel Borkmann return;
145399b321ddSDaniel Borkmann if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
145499b321ddSDaniel Borkmann rmdir(dir);
145599b321ddSDaniel Borkmann return;
145699b321ddSDaniel Borkmann }
145799b321ddSDaniel Borkmann
145899b321ddSDaniel Borkmann if (!ASSERT_OK(bpf_object__gen_loader(obj, &gopts), "gen_loader"))
145999b321ddSDaniel Borkmann goto out;
146099b321ddSDaniel Borkmann if (!ASSERT_OK(bpf_object__load(obj), "gen_load"))
146199b321ddSDaniel Borkmann goto out;
146299b321ddSDaniel Borkmann
146399b321ddSDaniel Borkmann bpf_object__for_each_program(p, obj)
146499b321ddSDaniel Borkmann nr_progs++;
146599b321ddSDaniel Borkmann bpf_object__for_each_map(m, obj)
146699b321ddSDaniel Borkmann nr_maps++;
146799b321ddSDaniel Borkmann if (!ASSERT_EQ(nr_maps, expect_maps, "fixture map count"))
146899b321ddSDaniel Borkmann goto out;
146999b321ddSDaniel Borkmann
147099b321ddSDaniel Borkmann ctx_sz = sizeof(struct bpf_loader_ctx) +
147199b321ddSDaniel Borkmann nr_maps * sizeof(struct bpf_map_desc) +
147299b321ddSDaniel Borkmann nr_progs * sizeof(struct bpf_prog_desc);
147399b321ddSDaniel Borkmann ctx = calloc(1, ctx_sz);
147499b321ddSDaniel Borkmann if (!ASSERT_OK_PTR(ctx, "ctx_alloc"))
147599b321ddSDaniel Borkmann goto out;
147699b321ddSDaniel Borkmann ((struct bpf_loader_ctx *)ctx)->sz = ctx_sz;
147799b321ddSDaniel Borkmann
147899b321ddSDaniel Borkmann data_sz = gopts.data_sz;
147999b321ddSDaniel Borkmann blob = malloc(data_sz);
148099b321ddSDaniel Borkmann if (!ASSERT_OK_PTR(blob, "blob_alloc"))
148199b321ddSDaniel Borkmann goto out;
148299b321ddSDaniel Borkmann memcpy(blob, gopts.data, data_sz);
148399b321ddSDaniel Borkmann
148499b321ddSDaniel Borkmann /* excl_prog_hash = SHA256(loader insns) == the loader's prog->digest. */
148599b321ddSDaniel Borkmann libbpf_sha256(gopts.insns, gopts.insns_sz, excl);
148699b321ddSDaniel Borkmann
148799b321ddSDaniel Borkmann r = loadtime_drive(dir, gopts.insns, gopts.insns_sz, blob, data_sz,
148899b321ddSDaniel Borkmann excl, ctx, ctx_sz, &load_ret, &ran);
148999b321ddSDaniel Borkmann ASSERT_OK(load_ret, "signed loader loaded (insns || metadata)");
149099b321ddSDaniel Borkmann ASSERT_TRUE(ran, "loader ran");
149199b321ddSDaniel Borkmann ASSERT_EQ(r, 0, "loader installed its target");
149299b321ddSDaniel Borkmann
149399b321ddSDaniel Borkmann md = (struct bpf_map_desc *)((char *)ctx + sizeof(struct bpf_loader_ctx));
149499b321ddSDaniel Borkmann pd = (struct bpf_prog_desc *)(md + nr_maps);
149599b321ddSDaniel Borkmann ASSERT_GT(pd[0].prog_fd, 0, "target program installed");
149699b321ddSDaniel Borkmann if (nr_maps)
149799b321ddSDaniel Borkmann ASSERT_GT(md[0].map_fd, 0, "target map installed");
149899b321ddSDaniel Borkmann
149999b321ddSDaniel Borkmann close_loader_ctx_fds(ctx, nr_maps, nr_progs);
150099b321ddSDaniel Borkmann out:
150199b321ddSDaniel Borkmann free(blob);
150299b321ddSDaniel Borkmann free(ctx);
150399b321ddSDaniel Borkmann if (dir)
150499b321ddSDaniel Borkmann run_setup("cleanup", dir);
150599b321ddSDaniel Borkmann }
150699b321ddSDaniel Borkmann
loadtime_no_map(void)150799b321ddSDaniel Borkmann static void loadtime_no_map(void)
150899b321ddSDaniel Borkmann {
150999b321ddSDaniel Borkmann struct test_signed_loader *skel = test_signed_loader__open();
151099b321ddSDaniel Borkmann
151199b321ddSDaniel Borkmann if (!ASSERT_OK_PTR(skel, "skel_open"))
151299b321ddSDaniel Borkmann return;
151399b321ddSDaniel Borkmann loadtime_verify(skel->obj, 0);
151499b321ddSDaniel Borkmann test_signed_loader__destroy(skel);
151599b321ddSDaniel Borkmann }
151699b321ddSDaniel Borkmann
loadtime_with_map(void)151799b321ddSDaniel Borkmann static void loadtime_with_map(void)
151899b321ddSDaniel Borkmann {
151999b321ddSDaniel Borkmann struct test_signed_loader_map *skel = test_signed_loader_map__open();
152099b321ddSDaniel Borkmann
152199b321ddSDaniel Borkmann if (!ASSERT_OK_PTR(skel, "skel_open"))
152299b321ddSDaniel Borkmann return;
152399b321ddSDaniel Borkmann loadtime_verify(skel->obj, 1);
152499b321ddSDaniel Borkmann test_signed_loader_map__destroy(skel);
152599b321ddSDaniel Borkmann }
152699b321ddSDaniel Borkmann
152799b321ddSDaniel Borkmann /*
152899b321ddSDaniel Borkmann * A signed program need not bind any map. A plain BPF_PROG_TYPE_SYSCALL
152999b321ddSDaniel Borkmann * program with no fd_array is signed over its instructions alone: the kernel
153099b321ddSDaniel Borkmann * verifies the signature, folds no metadata, and the program loads. Exercise
153199b321ddSDaniel Borkmann * the fd_array == NULL / fd_array_cnt == 0 path, and confirm the signature
153299b321ddSDaniel Borkmann * still authenticates the instructions (a tampered copy is rejected).
153399b321ddSDaniel Borkmann */
signed_no_fd_array(void)153499b321ddSDaniel Borkmann static void signed_no_fd_array(void)
153599b321ddSDaniel Borkmann {
153699b321ddSDaniel Borkmann struct bpf_insn insns[] = {
153799b321ddSDaniel Borkmann BPF_MOV64_IMM(BPF_REG_0, 0),
153899b321ddSDaniel Borkmann BPF_EXIT_INSN(),
153999b321ddSDaniel Borkmann };
154099b321ddSDaniel Borkmann char dir_tmpl[] = "/tmp/signed_loaderXXXXXX", *dir;
154199b321ddSDaniel Borkmann __u32 sig_sz = 8192;
154299b321ddSDaniel Borkmann union bpf_attr attr;
154399b321ddSDaniel Borkmann __u8 sig[8192];
154499b321ddSDaniel Borkmann int prog_fd, err;
154599b321ddSDaniel Borkmann
154699b321ddSDaniel Borkmann syscall(__NR_request_key, "keyring", "_uid.0", NULL,
154799b321ddSDaniel Borkmann KEY_SPEC_SESSION_KEYRING);
154899b321ddSDaniel Borkmann dir = mkdtemp(dir_tmpl);
154999b321ddSDaniel Borkmann if (!ASSERT_OK_PTR(dir, "mkdtemp"))
155099b321ddSDaniel Borkmann return;
155199b321ddSDaniel Borkmann if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
155299b321ddSDaniel Borkmann rmdir(dir);
155399b321ddSDaniel Borkmann return;
155499b321ddSDaniel Borkmann }
155599b321ddSDaniel Borkmann
155699b321ddSDaniel Borkmann /* No metadata map: the signed payload is the instructions alone. */
155799b321ddSDaniel Borkmann if (!ASSERT_OK(sign_buf(dir, insns, sizeof(insns), sig, &sig_sz),
155899b321ddSDaniel Borkmann "sign-file"))
155999b321ddSDaniel Borkmann goto cleanup;
156099b321ddSDaniel Borkmann
156199b321ddSDaniel Borkmann memset(&attr, 0, sizeof(attr));
156299b321ddSDaniel Borkmann attr.prog_type = BPF_PROG_TYPE_SYSCALL;
156399b321ddSDaniel Borkmann attr.insns = ptr_to_u64(insns);
156499b321ddSDaniel Borkmann attr.insn_cnt = ARRAY_SIZE(insns);
156599b321ddSDaniel Borkmann attr.license = ptr_to_u64("Dual BSD/GPL");
156699b321ddSDaniel Borkmann attr.prog_flags = BPF_F_SLEEPABLE;
156799b321ddSDaniel Borkmann attr.signature = ptr_to_u64(sig);
156899b321ddSDaniel Borkmann attr.signature_size = sig_sz;
156999b321ddSDaniel Borkmann attr.keyring_id = KEY_SPEC_SESSION_KEYRING;
157099b321ddSDaniel Borkmann /* fd_array and fd_array_cnt deliberately left NULL/0. */
157199b321ddSDaniel Borkmann memcpy(attr.prog_name, "signed_nomap", sizeof("signed_nomap"));
157299b321ddSDaniel Borkmann
157399b321ddSDaniel Borkmann prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
157499b321ddSDaniel Borkmann offsetofend(union bpf_attr, keyring_id));
157599b321ddSDaniel Borkmann if (!ASSERT_GE(prog_fd, 0, "map-less signed program loaded")) {
157699b321ddSDaniel Borkmann if (prog_fd >= 0)
157799b321ddSDaniel Borkmann close(prog_fd);
157899b321ddSDaniel Borkmann goto cleanup;
157999b321ddSDaniel Borkmann }
158099b321ddSDaniel Borkmann close(prog_fd);
158199b321ddSDaniel Borkmann
158299b321ddSDaniel Borkmann /* The signature covers the instructions, so tampering must be rejected. */
158399b321ddSDaniel Borkmann insns[0].imm = 1;
158499b321ddSDaniel Borkmann prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
158599b321ddSDaniel Borkmann offsetofend(union bpf_attr, keyring_id));
158699b321ddSDaniel Borkmann err = prog_fd < 0 ? -errno : prog_fd;
158799b321ddSDaniel Borkmann ASSERT_EQ(err, -EKEYREJECTED, "tampered map-less program rejected");
158899b321ddSDaniel Borkmann if (prog_fd >= 0)
158999b321ddSDaniel Borkmann close(prog_fd);
159099b321ddSDaniel Borkmann cleanup:
159199b321ddSDaniel Borkmann run_setup("cleanup", dir);
159299b321ddSDaniel Borkmann }
159399b321ddSDaniel Borkmann
159499b321ddSDaniel Borkmann /*
159599b321ddSDaniel Borkmann * A signed program may reach maps only through fd_array indices, so the kernel
159699b321ddSDaniel Borkmann * folds (and thus attests) them. A direct BPF_PSEUDO_MAP_FD reference - a raw,
159799b321ddSDaniel Borkmann * unfolded fd baked into the signed instructions - is rejected by the verifier.
159899b321ddSDaniel Borkmann */
signed_map_by_fd_rejected(void)159999b321ddSDaniel Borkmann static void signed_map_by_fd_rejected(void)
160099b321ddSDaniel Borkmann {
160199b321ddSDaniel Borkmann struct bpf_insn insns[] = {
160299b321ddSDaniel Borkmann BPF_LD_MAP_FD(BPF_REG_1, 0),
160399b321ddSDaniel Borkmann BPF_MOV64_IMM(BPF_REG_0, 0),
160499b321ddSDaniel Borkmann BPF_EXIT_INSN(),
160599b321ddSDaniel Borkmann };
160699b321ddSDaniel Borkmann char dir_tmpl[] = "/tmp/signed_loaderXXXXXX", *dir;
160799b321ddSDaniel Borkmann __u32 sig_sz = 8192;
160899b321ddSDaniel Borkmann union bpf_attr attr;
160999b321ddSDaniel Borkmann __u8 sig[8192];
161099b321ddSDaniel Borkmann int map_fd, prog_fd, err;
161199b321ddSDaniel Borkmann
161299b321ddSDaniel Borkmann map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "sig_mapfd", 4, 4, 1, NULL);
161399b321ddSDaniel Borkmann if (!ASSERT_GE(map_fd, 0, "map_create"))
161499b321ddSDaniel Borkmann return;
161599b321ddSDaniel Borkmann insns[0].imm = map_fd; /* bake the raw map fd into the ld_imm64 */
161699b321ddSDaniel Borkmann
161799b321ddSDaniel Borkmann syscall(__NR_request_key, "keyring", "_uid.0", NULL,
161899b321ddSDaniel Borkmann KEY_SPEC_SESSION_KEYRING);
161999b321ddSDaniel Borkmann dir = mkdtemp(dir_tmpl);
162099b321ddSDaniel Borkmann if (!ASSERT_OK_PTR(dir, "mkdtemp"))
162199b321ddSDaniel Borkmann goto out_map;
162299b321ddSDaniel Borkmann if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
162399b321ddSDaniel Borkmann rmdir(dir);
162499b321ddSDaniel Borkmann goto out_map;
162599b321ddSDaniel Borkmann }
162699b321ddSDaniel Borkmann
162799b321ddSDaniel Borkmann /* Sign the instructions, raw map fd and all. */
162899b321ddSDaniel Borkmann if (!ASSERT_OK(sign_buf(dir, insns, sizeof(insns), sig, &sig_sz),
162999b321ddSDaniel Borkmann "sign-file"))
163099b321ddSDaniel Borkmann goto cleanup;
163199b321ddSDaniel Borkmann
163299b321ddSDaniel Borkmann memset(&attr, 0, sizeof(attr));
163399b321ddSDaniel Borkmann attr.prog_type = BPF_PROG_TYPE_SYSCALL;
163499b321ddSDaniel Borkmann attr.insns = ptr_to_u64(insns);
163599b321ddSDaniel Borkmann attr.insn_cnt = ARRAY_SIZE(insns);
163699b321ddSDaniel Borkmann attr.license = ptr_to_u64("Dual BSD/GPL");
163799b321ddSDaniel Borkmann attr.prog_flags = BPF_F_SLEEPABLE;
163899b321ddSDaniel Borkmann attr.signature = ptr_to_u64(sig);
163999b321ddSDaniel Borkmann attr.signature_size = sig_sz;
164099b321ddSDaniel Borkmann attr.keyring_id = KEY_SPEC_SESSION_KEYRING;
164199b321ddSDaniel Borkmann /* No fd_array: the map is reached by a raw fd in the instructions. */
164299b321ddSDaniel Borkmann memcpy(attr.prog_name, "signed_mapfd", sizeof("signed_mapfd"));
164399b321ddSDaniel Borkmann
164499b321ddSDaniel Borkmann prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
164599b321ddSDaniel Borkmann offsetofend(union bpf_attr, keyring_id));
164699b321ddSDaniel Borkmann err = prog_fd < 0 ? -errno : prog_fd;
164799b321ddSDaniel Borkmann ASSERT_EQ(err, -EINVAL, "signed program referencing a map by fd rejected");
164899b321ddSDaniel Borkmann if (prog_fd >= 0)
164999b321ddSDaniel Borkmann close(prog_fd);
165099b321ddSDaniel Borkmann cleanup:
165199b321ddSDaniel Borkmann run_setup("cleanup", dir);
165299b321ddSDaniel Borkmann out_map:
165399b321ddSDaniel Borkmann close(map_fd);
165499b321ddSDaniel Borkmann }
165599b321ddSDaniel Borkmann
165699b321ddSDaniel Borkmann /*
165799b321ddSDaniel Borkmann * A signed program may reach maps only through the continuous fd_array, so the
165899b321ddSDaniel Borkmann * kernel folds (and thus attests) them. Referencing a map by fd_array *index*
165999b321ddSDaniel Borkmann * while leaving fd_array_cnt at 0 selects the sparse path, which resolves a map
166099b321ddSDaniel Borkmann * the signature never covered; the verifier rejects it up front with -EACCES.
166199b321ddSDaniel Borkmann */
signed_sparse_fd_array_rejected(void)166299b321ddSDaniel Borkmann static void signed_sparse_fd_array_rejected(void)
166399b321ddSDaniel Borkmann {
166499b321ddSDaniel Borkmann struct bpf_insn insns[] = {
166599b321ddSDaniel Borkmann BPF_LD_IMM64_RAW(BPF_REG_1, BPF_PSEUDO_MAP_IDX, 0),
166699b321ddSDaniel Borkmann BPF_MOV64_IMM(BPF_REG_0, 0),
166799b321ddSDaniel Borkmann BPF_EXIT_INSN(),
166899b321ddSDaniel Borkmann };
166999b321ddSDaniel Borkmann char dir_tmpl[] = "/tmp/signed_loader_spXXXXXX", *dir;
167099b321ddSDaniel Borkmann __u32 sig_sz = 8192;
167199b321ddSDaniel Borkmann union bpf_attr attr;
167299b321ddSDaniel Borkmann __u8 sig[8192];
167399b321ddSDaniel Borkmann int map_fd, prog_fd, err;
167499b321ddSDaniel Borkmann
167599b321ddSDaniel Borkmann map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "sig_sparse", 4, 4, 1, NULL);
167699b321ddSDaniel Borkmann if (!ASSERT_GE(map_fd, 0, "map_create"))
167799b321ddSDaniel Borkmann return;
167899b321ddSDaniel Borkmann
167999b321ddSDaniel Borkmann syscall(__NR_request_key, "keyring", "_uid.0", NULL,
168099b321ddSDaniel Borkmann KEY_SPEC_SESSION_KEYRING);
168199b321ddSDaniel Borkmann dir = mkdtemp(dir_tmpl);
168299b321ddSDaniel Borkmann if (!ASSERT_OK_PTR(dir, "mkdtemp"))
168399b321ddSDaniel Borkmann goto out_map;
168499b321ddSDaniel Borkmann if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
168599b321ddSDaniel Borkmann rmdir(dir);
168699b321ddSDaniel Borkmann goto out_map;
168799b321ddSDaniel Borkmann }
168899b321ddSDaniel Borkmann
168999b321ddSDaniel Borkmann /* Sign the instructions alone; the sparse map is not folded. */
169099b321ddSDaniel Borkmann if (!ASSERT_OK(sign_buf(dir, insns, sizeof(insns), sig, &sig_sz),
169199b321ddSDaniel Borkmann "sign-file"))
169299b321ddSDaniel Borkmann goto cleanup;
169399b321ddSDaniel Borkmann
169499b321ddSDaniel Borkmann memset(&attr, 0, sizeof(attr));
169599b321ddSDaniel Borkmann attr.prog_type = BPF_PROG_TYPE_SYSCALL;
169699b321ddSDaniel Borkmann attr.insns = ptr_to_u64(insns);
169799b321ddSDaniel Borkmann attr.insn_cnt = ARRAY_SIZE(insns);
169899b321ddSDaniel Borkmann attr.license = ptr_to_u64("Dual BSD/GPL");
169999b321ddSDaniel Borkmann attr.prog_flags = BPF_F_SLEEPABLE;
170099b321ddSDaniel Borkmann attr.fd_array = ptr_to_u64(&map_fd);
170199b321ddSDaniel Borkmann attr.fd_array_cnt = 0; /* sparse: force lazy map resolution */
170299b321ddSDaniel Borkmann attr.signature = ptr_to_u64(sig);
170399b321ddSDaniel Borkmann attr.signature_size = sig_sz;
170499b321ddSDaniel Borkmann attr.keyring_id = KEY_SPEC_SESSION_KEYRING;
170599b321ddSDaniel Borkmann memcpy(attr.prog_name, "signed_sparse", sizeof("signed_sparse"));
170699b321ddSDaniel Borkmann
170799b321ddSDaniel Borkmann prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
170899b321ddSDaniel Borkmann offsetofend(union bpf_attr, keyring_id));
170999b321ddSDaniel Borkmann err = prog_fd < 0 ? -errno : prog_fd;
171099b321ddSDaniel Borkmann ASSERT_EQ(err, -EACCES, "signed program binding a sparse fd_array map rejected");
171199b321ddSDaniel Borkmann if (prog_fd >= 0)
171299b321ddSDaniel Borkmann close(prog_fd);
171399b321ddSDaniel Borkmann cleanup:
171499b321ddSDaniel Borkmann run_setup("cleanup", dir);
171599b321ddSDaniel Borkmann out_map:
171699b321ddSDaniel Borkmann close(map_fd);
171799b321ddSDaniel Borkmann }
171899b321ddSDaniel Borkmann
signed_module_kfunc_rejected(void)171999b321ddSDaniel Borkmann static void signed_module_kfunc_rejected(void)
172099b321ddSDaniel Borkmann {
172199b321ddSDaniel Borkmann struct bpf_insn insns[] = {
172299b321ddSDaniel Borkmann BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, BPF_PSEUDO_KFUNC_CALL, 1, 1),
172399b321ddSDaniel Borkmann BPF_MOV64_IMM(BPF_REG_0, 0),
172499b321ddSDaniel Borkmann BPF_EXIT_INSN(),
172599b321ddSDaniel Borkmann };
172699b321ddSDaniel Borkmann char dir_tmpl[] = "/tmp/signed_loader_kfnXXXXXX", *dir;
172799b321ddSDaniel Borkmann int prog_fd, err, fds[2];
172899b321ddSDaniel Borkmann struct btf *btf = NULL;
172999b321ddSDaniel Borkmann __u32 sig_sz = 8192;
173099b321ddSDaniel Borkmann union bpf_attr attr;
173199b321ddSDaniel Borkmann __u8 sig[8192];
173299b321ddSDaniel Borkmann
173399b321ddSDaniel Borkmann syscall(__NR_request_key, "keyring", "_uid.0", NULL,
173499b321ddSDaniel Borkmann KEY_SPEC_SESSION_KEYRING);
173599b321ddSDaniel Borkmann dir = mkdtemp(dir_tmpl);
173699b321ddSDaniel Borkmann if (!ASSERT_OK_PTR(dir, "mkdtemp"))
173799b321ddSDaniel Borkmann return;
173899b321ddSDaniel Borkmann if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
173999b321ddSDaniel Borkmann rmdir(dir);
174099b321ddSDaniel Borkmann return;
174199b321ddSDaniel Borkmann }
174299b321ddSDaniel Borkmann if (!ASSERT_OK(sign_buf(dir, insns, sizeof(insns), sig, &sig_sz),
174399b321ddSDaniel Borkmann "sign-file"))
174499b321ddSDaniel Borkmann goto cleanup;
174599b321ddSDaniel Borkmann btf = btf__new_empty();
174699b321ddSDaniel Borkmann if (!ASSERT_OK_PTR(btf, "btf_new_empty"))
174799b321ddSDaniel Borkmann goto cleanup;
174899b321ddSDaniel Borkmann btf__add_int(btf, "int", 4, BTF_INT_SIGNED);
174999b321ddSDaniel Borkmann if (!ASSERT_OK(btf__load_into_kernel(btf), "btf_load"))
175099b321ddSDaniel Borkmann goto cleanup;
175199b321ddSDaniel Borkmann fds[0] = -1;
175299b321ddSDaniel Borkmann fds[1] = btf__fd(btf);
175399b321ddSDaniel Borkmann
175499b321ddSDaniel Borkmann memset(&attr, 0, sizeof(attr));
175599b321ddSDaniel Borkmann attr.prog_type = BPF_PROG_TYPE_SYSCALL;
175699b321ddSDaniel Borkmann attr.insns = ptr_to_u64(insns);
175799b321ddSDaniel Borkmann attr.insn_cnt = ARRAY_SIZE(insns);
175899b321ddSDaniel Borkmann attr.license = ptr_to_u64("Dual BSD/GPL");
175999b321ddSDaniel Borkmann attr.prog_flags = BPF_F_SLEEPABLE;
176099b321ddSDaniel Borkmann attr.fd_array = ptr_to_u64(fds);
176199b321ddSDaniel Borkmann attr.fd_array_cnt = 0; /* sparse: force lazy kfunc BTF resolution */
176299b321ddSDaniel Borkmann attr.signature = ptr_to_u64(sig);
176399b321ddSDaniel Borkmann attr.signature_size = sig_sz;
176499b321ddSDaniel Borkmann attr.keyring_id = KEY_SPEC_SESSION_KEYRING;
176599b321ddSDaniel Borkmann memcpy(attr.prog_name, "signed_kfunc", sizeof("signed_kfunc"));
176699b321ddSDaniel Borkmann
176799b321ddSDaniel Borkmann prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
176899b321ddSDaniel Borkmann offsetofend(union bpf_attr, keyring_id));
176999b321ddSDaniel Borkmann err = prog_fd < 0 ? -errno : prog_fd;
177099b321ddSDaniel Borkmann if (prog_fd >= 0)
177199b321ddSDaniel Borkmann close(prog_fd);
177299b321ddSDaniel Borkmann
177399b321ddSDaniel Borkmann ASSERT_EQ(err, -EACCES, "module kfunc BTF in signed program rejected");
177499b321ddSDaniel Borkmann cleanup:
177599b321ddSDaniel Borkmann if (btf)
177699b321ddSDaniel Borkmann btf__free(btf);
177799b321ddSDaniel Borkmann run_setup("cleanup", dir);
177899b321ddSDaniel Borkmann }
177999b321ddSDaniel Borkmann
test_signed_loader(void)17805b88319eSDaniel Borkmann void test_signed_loader(void)
17815b88319eSDaniel Borkmann {
178299b321ddSDaniel Borkmann if (test__start_subtest("loadtime_no_map"))
178399b321ddSDaniel Borkmann loadtime_no_map();
178499b321ddSDaniel Borkmann if (test__start_subtest("loadtime_with_map"))
178599b321ddSDaniel Borkmann loadtime_with_map();
17865b88319eSDaniel Borkmann if (test__start_subtest("metadata_match"))
17875b88319eSDaniel Borkmann metadata_match();
17885b88319eSDaniel Borkmann if (test__start_subtest("signature_enforced"))
17895b88319eSDaniel Borkmann signature_enforced();
179099b321ddSDaniel Borkmann if (test__start_subtest("signed_nonexcl_fd_array_rejected"))
179199b321ddSDaniel Borkmann signed_nonexcl_fd_array_rejected();
179299b321ddSDaniel Borkmann if (test__start_subtest("signed_unfrozen_fd_array_rejected"))
179399b321ddSDaniel Borkmann signed_unfrozen_fd_array_rejected();
179499b321ddSDaniel Borkmann if (test__start_subtest("signed_nonarray_fd_array_rejected"))
179599b321ddSDaniel Borkmann signed_nonarray_fd_array_rejected();
179699b321ddSDaniel Borkmann if (test__start_subtest("signed_btf_fd_array_rejected"))
179799b321ddSDaniel Borkmann signed_btf_fd_array_rejected();
179899b321ddSDaniel Borkmann if (test__start_subtest("signed_module_kfunc_rejected"))
179999b321ddSDaniel Borkmann signed_module_kfunc_rejected();
180099b321ddSDaniel Borkmann if (test__start_subtest("signature_failure_logs"))
180199b321ddSDaniel Borkmann signature_failure_logs();
18025b88319eSDaniel Borkmann if (test__start_subtest("signature_too_large"))
18035b88319eSDaniel Borkmann signature_too_large();
180499b321ddSDaniel Borkmann if (test__start_subtest("signature_zero_size"))
180599b321ddSDaniel Borkmann signature_zero_size();
18065b88319eSDaniel Borkmann if (test__start_subtest("signature_bad_keyring"))
18075b88319eSDaniel Borkmann signature_bad_keyring();
18085b88319eSDaniel Borkmann if (test__start_subtest("metadata_ctx_max_entries_ignored"))
18095b88319eSDaniel Borkmann metadata_ctx_max_entries_ignored();
18105b88319eSDaniel Borkmann if (test__start_subtest("metadata_ctx_initial_value_ignored"))
18115b88319eSDaniel Borkmann metadata_ctx_initial_value_ignored();
18125b88319eSDaniel Borkmann if (test__start_subtest("signature_authenticates_insns"))
18135b88319eSDaniel Borkmann signature_authenticates_insns();
181499b321ddSDaniel Borkmann if (test__start_subtest("signature_authenticates_metadata"))
181599b321ddSDaniel Borkmann signature_authenticates_metadata();
18165b88319eSDaniel Borkmann if (test__start_subtest("hash_requires_frozen"))
18175b88319eSDaniel Borkmann hash_requires_frozen();
18185b88319eSDaniel Borkmann if (test__start_subtest("no_update_after_freeze"))
18195b88319eSDaniel Borkmann no_update_after_freeze();
18205b88319eSDaniel Borkmann if (test__start_subtest("freeze_writable_mmap"))
18215b88319eSDaniel Borkmann freeze_writable_mmap();
18225b88319eSDaniel Borkmann if (test__start_subtest("no_writable_mmap_frozen"))
18235b88319eSDaniel Borkmann no_writable_mmap_frozen();
18245b88319eSDaniel Borkmann if (test__start_subtest("map_hash_matches_libbpf"))
18255b88319eSDaniel Borkmann map_hash_matches_libbpf();
18265b88319eSDaniel Borkmann if (test__start_subtest("map_hash_multi_element"))
18275b88319eSDaniel Borkmann map_hash_multi_element();
18285b88319eSDaniel Borkmann if (test__start_subtest("map_hash_bad_size"))
18295b88319eSDaniel Borkmann map_hash_bad_size();
18305b88319eSDaniel Borkmann if (test__start_subtest("map_hash_unsupported_type"))
18315b88319eSDaniel Borkmann map_hash_unsupported_type();
18328ddce416SDaniel Borkmann if (test__start_subtest("lsm_signature_verdict"))
18338ddce416SDaniel Borkmann lsm_signature_verdict();
183499b321ddSDaniel Borkmann if (test__start_subtest("signed_no_fd_array"))
183599b321ddSDaniel Borkmann signed_no_fd_array();
183699b321ddSDaniel Borkmann if (test__start_subtest("signed_map_by_fd_rejected"))
183799b321ddSDaniel Borkmann signed_map_by_fd_rejected();
183899b321ddSDaniel Borkmann if (test__start_subtest("signed_sparse_fd_array_rejected"))
183999b321ddSDaniel Borkmann signed_sparse_fd_array_rejected();
18405b88319eSDaniel Borkmann }
1841