1 // SPDX-License-Identifier: GPL-2.0 2 /* Copyright (c) 2026 Isovalent */ 3 4 #include <test_progs.h> 5 #include <sys/syscall.h> 6 #include <sys/mman.h> 7 #include <sys/wait.h> 8 #include <sys/stat.h> 9 #include <fcntl.h> 10 #include <limits.h> 11 #include <linux/keyctl.h> 12 #include <linux/bpf.h> 13 14 #include "bpf/libbpf_internal.h" /* for libbpf_sha256() */ 15 #include "bpf/skel_internal.h" /* for loader ctx layout (bpf_loader_ctx etc) */ 16 17 #include "test_signed_loader.skel.h" 18 #include "test_signed_loader_map.skel.h" 19 #include "test_signed_loader_data.skel.h" 20 21 #define SIG_MATCH_INSNS 33 /* excl (5) + 4 * sha-dword (7) */ 22 23 static int load_loader(const void *insns, __u32 insns_sz, int map_fd, 24 const void *sig, __u32 sig_sz, __s32 keyring_id) 25 { 26 union bpf_attr attr; 27 int fd; 28 29 memset(&attr, 0, sizeof(attr)); 30 attr.prog_type = BPF_PROG_TYPE_SYSCALL; 31 attr.insns = ptr_to_u64(insns); 32 attr.insn_cnt = insns_sz / sizeof(struct bpf_insn); 33 attr.license = ptr_to_u64("Dual BSD/GPL"); 34 attr.prog_flags = BPF_F_SLEEPABLE; 35 attr.fd_array = ptr_to_u64(&map_fd); 36 if (sig) { 37 attr.signature = ptr_to_u64(sig); 38 attr.signature_size = sig_sz; 39 attr.keyring_id = keyring_id; 40 } 41 memcpy(attr.prog_name, "__loader.prog", sizeof("__loader.prog")); 42 fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr, 43 offsetofend(union bpf_attr, keyring_id)); 44 return fd < 0 ? -errno : fd; 45 } 46 47 static int run_gen_loader(const void *insns, __u32 insns_sz, 48 const void *data, __u32 data_sz, 49 const void *excl, __u32 excl_sz, 50 const void *sig, __u32 sig_sz, 51 bool get_hash, void *ctx, __u32 ctx_sz, bool *loader_ran) 52 { 53 LIBBPF_OPTS(bpf_map_create_opts, mopts, 54 .excl_prog_hash = excl, 55 .excl_prog_hash_size = excl_sz); 56 __u8 hbuf[SHA256_DIGEST_LENGTH]; 57 struct bpf_map_info info; 58 __u32 ilen = sizeof(info), key = 0; 59 union bpf_attr attr; 60 int map_fd, prog_fd, ret; 61 62 *loader_ran = false; 63 64 map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "__loader.map", 65 4, data_sz, 1, &mopts); 66 if (map_fd < 0) 67 return -errno; 68 if (bpf_map_update_elem(map_fd, &key, data, 0)) { 69 ret = -errno; 70 goto out_map; 71 } 72 if (bpf_map_freeze(map_fd)) { 73 ret = -errno; 74 goto out_map; 75 } 76 if (get_hash) { 77 memset(&info, 0, sizeof(info)); 78 info.hash = ptr_to_u64(hbuf); 79 info.hash_size = sizeof(hbuf); 80 if (bpf_map_get_info_by_fd(map_fd, &info, &ilen)) { 81 ret = -errno; 82 goto out_map; 83 } 84 } 85 86 memset(&attr, 0, sizeof(attr)); 87 attr.prog_type = BPF_PROG_TYPE_SYSCALL; 88 attr.insns = ptr_to_u64(insns); 89 attr.insn_cnt = insns_sz / sizeof(struct bpf_insn); 90 attr.license = ptr_to_u64("Dual BSD/GPL"); 91 attr.prog_flags = BPF_F_SLEEPABLE; 92 attr.fd_array = ptr_to_u64(&map_fd); 93 if (sig) { 94 attr.signature = ptr_to_u64(sig); 95 attr.signature_size = sig_sz; 96 attr.keyring_id = KEY_SPEC_SESSION_KEYRING; 97 } 98 memcpy(attr.prog_name, "__loader.prog", sizeof("__loader.prog")); 99 prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr, 100 offsetofend(union bpf_attr, keyring_id)); 101 if (prog_fd < 0) { 102 ret = -errno; 103 goto out_map; 104 } 105 106 memset(&attr, 0, sizeof(attr)); 107 attr.test.prog_fd = prog_fd; 108 attr.test.ctx_in = ptr_to_u64(ctx); 109 attr.test.ctx_size_in = ctx_sz; 110 if (syscall(__NR_bpf, BPF_PROG_RUN, &attr, 111 offsetofend(union bpf_attr, test)) < 0) { 112 ret = -errno; 113 goto out_prog; 114 } 115 *loader_ran = true; 116 ret = (int)attr.test.retval; 117 out_prog: 118 close(prog_fd); 119 out_map: 120 close(map_fd); 121 return ret; 122 } 123 124 static void close_loader_ctx_fds(void *ctx, int nr_maps, int nr_progs) 125 { 126 struct bpf_map_desc *md = (struct bpf_map_desc *)((char *)ctx + 127 sizeof(struct bpf_loader_ctx)); 128 struct bpf_prog_desc *pd = (struct bpf_prog_desc *)(md + nr_maps); 129 int i; 130 131 for (i = 0; i < nr_maps; i++) 132 if (md[i].map_fd > 0) 133 close(md[i].map_fd); 134 for (i = 0; i < nr_progs; i++) 135 if (pd[i].prog_fd > 0) 136 close(pd[i].prog_fd); 137 } 138 139 static int run_setup(const char *cmd, const char *dir) 140 { 141 int pid, status; 142 143 pid = fork(); 144 if (pid < 0) 145 return -errno; 146 if (pid == 0) { 147 execlp("./verify_sig_setup.sh", "./verify_sig_setup.sh", 148 cmd, dir, NULL); 149 exit(1); 150 } 151 if (waitpid(pid, &status, 0) < 0) 152 return -errno; 153 return (WIFEXITED(status) && 154 WEXITSTATUS(status) == 0) ? 0 : -EINVAL; 155 } 156 157 static int sign_buf(const char *dir, const void *buf, __u32 len, 158 void *sig, __u32 *sig_sz) 159 { 160 char data_tmpl[PATH_MAX], key[PATH_MAX]; 161 char sigpath[PATH_MAX + sizeof(".p7s")]; 162 int fd, pid, status, ret; 163 struct stat st; 164 165 ret = snprintf(data_tmpl, sizeof(data_tmpl), "%s/dataXXXXXX", dir); 166 if (ret < 0 || ret >= (int)sizeof(data_tmpl)) 167 return -ENAMETOOLONG; 168 ret = 0; 169 170 fd = mkstemp(data_tmpl); 171 if (fd < 0) 172 return -errno; 173 if (write(fd, buf, len) != (ssize_t)len) { 174 close(fd); 175 ret = -EIO; 176 goto out; 177 } 178 close(fd); 179 180 pid = fork(); 181 if (pid < 0) { 182 ret = -errno; 183 goto out; 184 } 185 if (pid == 0) { 186 snprintf(key, sizeof(key), "%s/signing_key.pem", dir); 187 execlp("./sign-file", "./sign-file", "-d", "sha256", 188 key, key, data_tmpl, NULL); 189 exit(1); 190 } 191 if (waitpid(pid, &status, 0) < 0 || 192 !WIFEXITED(status) || WEXITSTATUS(status)) { 193 ret = -EINVAL; 194 goto out; 195 } 196 197 snprintf(sigpath, sizeof(sigpath), "%s.p7s", data_tmpl); 198 if (stat(sigpath, &st) < 0) { 199 ret = -errno; 200 goto out; 201 } 202 if (st.st_size > (off_t)*sig_sz) { 203 ret = -E2BIG; 204 goto out_sig; 205 } 206 fd = open(sigpath, O_RDONLY); 207 if (fd < 0) { 208 ret = -errno; 209 goto out_sig; 210 } 211 if (read(fd, sig, st.st_size) != st.st_size) { 212 close(fd); 213 ret = -EIO; 214 goto out_sig; 215 } 216 close(fd); 217 *sig_sz = st.st_size; 218 out_sig: 219 unlink(sigpath); 220 out: 221 unlink(data_tmpl); 222 return ret; 223 } 224 225 static void check_sig_match_shape(const struct bpf_insn *in, int n) 226 { 227 int a = -1, cleanup = -1, i, base, t, br[5], nb = 0; 228 229 /* BPF_PSEUDO_MAP_IDX (the struct bpf_map * form) is used only here. */ 230 for (i = 0; i + 1 < n; i++) { 231 if (in[i].code == (BPF_LD | BPF_IMM | BPF_DW) && 232 in[i].src_reg == BPF_PSEUDO_MAP_IDX) { 233 a = i; 234 break; 235 } 236 } 237 if (!ASSERT_GE(a, 0, "emit_signature_match present")) 238 return; 239 if (!ASSERT_LE(a + SIG_MATCH_INSNS, n, "block fits in program")) 240 return; 241 242 /* excl check: r2 = *(u32 *)(map + 32); if r2 != 1 goto cleanup */ 243 ASSERT_EQ(in[a + 2].code, (BPF_LDX | BPF_MEM | BPF_W), "excl load width"); 244 ASSERT_EQ(in[a + 2].off, SHA256_DIGEST_LENGTH, "excl field offset"); 245 ASSERT_EQ(in[a + 4].code, (BPF_JMP | BPF_JNE | BPF_K), "excl branch op"); 246 ASSERT_EQ(in[a + 4].imm, 1, "excl compared to 1"); 247 br[nb++] = a + 4; 248 249 /* 4 sha-dword checks: r2 = *(u64 *)(map + i*8); if r2 != r3 goto cleanup */ 250 for (i = 0; i < 4; i++) { 251 base = a + 5 + i * 7; 252 ASSERT_EQ(in[base + 2].code, (BPF_LDX | BPF_MEM | BPF_DW), "sha load width"); 253 ASSERT_EQ(in[base + 2].off, i * 8, "sha dword offset"); 254 ASSERT_EQ(in[base + 3].code, (BPF_LD | BPF_IMM | BPF_DW), "sha imm64 (H_meta)"); 255 ASSERT_EQ(in[base + 6].code, (BPF_JMP | BPF_JNE | BPF_X), "sha branch op"); 256 br[nb++] = base + 6; 257 } 258 259 /* 260 * Locate the real cleanup label so we can pin the exact jump target, 261 * not just "some backward label". bpf_gen__init() emits the cleanup 262 * block as a prog-fd close loop whose first instruction is the label 263 * every error branch jumps to. 264 */ 265 for (i = 0; i + 2 < a; i++) { 266 if (in[i].code == (BPF_LDX | BPF_MEM | BPF_W) && 267 in[i].dst_reg == BPF_REG_1 && in[i].src_reg == BPF_REG_10 && 268 in[i + 1].code == (BPF_JMP | BPF_JSLE | BPF_K) && 269 in[i + 1].dst_reg == BPF_REG_1 && in[i + 1].imm == 0 && 270 in[i + 1].off == 1 && 271 in[i + 2].code == (BPF_JMP | BPF_CALL) && 272 in[i + 2].imm == BPF_FUNC_sys_close) { 273 cleanup = i; 274 break; 275 } 276 } 277 if (!ASSERT_GE(cleanup, 0, "cleanup label located")) 278 return; 279 for (i = 0; i < nb; i++) { 280 t = br[i] + 1 + in[br[i]].off; 281 ASSERT_EQ(t, cleanup, "sig-match lands on cleanup"); 282 } 283 /* 284 * Same invariant for every other cleanup-bound jump in the program: 285 * emit_check_err() is the only source of "if (r7 < 0) goto cleanup", 286 * so each of those must also resolve exactly to cleanup. 287 */ 288 for (i = 0, t = 0; i < n; i++) { 289 if (in[i].code != (BPF_JMP | BPF_JSLT | BPF_K) || 290 in[i].dst_reg != BPF_REG_7 || in[i].imm != 0 || in[i].off >= 0) 291 continue; 292 ASSERT_EQ(i + 1 + in[i].off, cleanup, "err-check lands on cleanup"); 293 t++; 294 } 295 ASSERT_GT(t, 0, "found emit_check_err jumps"); 296 } 297 298 struct gen_loader_fixture { 299 struct test_signed_loader *skel; 300 struct gen_loader_opts gopts; 301 unsigned char *blob; 302 void *ctx; 303 __u32 data_sz; 304 __u32 ctx_sz; 305 int nr_maps; 306 int nr_progs; 307 __u8 excl[SHA256_DIGEST_LENGTH]; 308 }; 309 310 static int gen_loader_fixture_init(struct gen_loader_fixture *f) 311 { 312 LIBBPF_OPTS(gen_loader_opts, gopts, .gen_hash = true); 313 int nr_maps = 0, nr_progs = 0; 314 struct bpf_program *p; 315 struct bpf_map *m; 316 317 memset(f, 0, sizeof(*f)); 318 f->skel = test_signed_loader__open(); 319 if (!ASSERT_OK_PTR(f->skel, "skel_open")) 320 return -1; 321 if (!ASSERT_OK(bpf_object__gen_loader(f->skel->obj, &gopts), "gen_loader")) 322 return -1; 323 if (!ASSERT_OK(bpf_object__load(f->skel->obj), "gen_load")) 324 return -1; 325 f->gopts = gopts; 326 327 bpf_object__for_each_program(p, f->skel->obj) 328 nr_progs++; 329 bpf_object__for_each_map(m, f->skel->obj) 330 nr_maps++; 331 f->nr_maps = nr_maps; 332 f->nr_progs = nr_progs; 333 f->ctx_sz = sizeof(struct bpf_loader_ctx) + 334 nr_maps * sizeof(struct bpf_map_desc) + 335 nr_progs * sizeof(struct bpf_prog_desc); 336 f->ctx = calloc(1, f->ctx_sz); 337 if (!ASSERT_OK_PTR(f->ctx, "ctx_alloc")) 338 return -1; 339 ((struct bpf_loader_ctx *)f->ctx)->sz = f->ctx_sz; 340 341 f->data_sz = gopts.data_sz; 342 f->blob = malloc(f->data_sz); 343 if (!ASSERT_OK_PTR(f->blob, "blob_alloc")) 344 return -1; 345 memcpy(f->blob, gopts.data, f->data_sz); 346 347 /* excl_prog_hash = SHA256(loader insns) == the loader's prog->digest. */ 348 libbpf_sha256(gopts.insns, gopts.insns_sz, f->excl); 349 return 0; 350 } 351 352 static void gen_loader_fixture_fini(struct gen_loader_fixture *f) 353 { 354 if (f->ctx) 355 close_loader_ctx_fds(f->ctx, f->nr_maps, f->nr_progs); 356 free(f->blob); 357 free(f->ctx); 358 test_signed_loader__destroy(f->skel); 359 } 360 361 static void metadata_check_shape(void) 362 { 363 struct gen_loader_fixture f; 364 365 if (gen_loader_fixture_init(&f) == 0) 366 check_sig_match_shape((const struct bpf_insn *)f.gopts.insns, 367 f.gopts.insns_sz / sizeof(struct bpf_insn)); 368 gen_loader_fixture_fini(&f); 369 } 370 371 static void metadata_match(void) 372 { 373 struct gen_loader_fixture f; 374 bool ran; 375 int r; 376 377 if (gen_loader_fixture_init(&f) == 0) { 378 r = run_gen_loader(f.gopts.insns, f.gopts.insns_sz, f.blob, 379 f.data_sz, f.excl, sizeof(f.excl), NULL, 0, 380 true, f.ctx, f.ctx_sz, &ran); 381 ASSERT_TRUE(ran, "loader ran"); 382 ASSERT_EQ(r, 0, "honest loader retval"); 383 } 384 gen_loader_fixture_fini(&f); 385 } 386 387 static void metadata_sha_mismatch(void) 388 { 389 struct gen_loader_fixture f; 390 bool ran; 391 int r; 392 393 if (gen_loader_fixture_init(&f) == 0) { 394 /* 395 * blob[0] lives in the loader's fd_array scratch (first add_data in 396 * bpf_gen__init); a 0-map program never reads it, so flipping it 397 * changes only map->sha. The metadata check is the only thing that 398 * can notice -> isolates emit_signature_match. 399 */ 400 f.blob[0] ^= 0xff; 401 r = run_gen_loader(f.gopts.insns, f.gopts.insns_sz, f.blob, 402 f.data_sz, f.excl, sizeof(f.excl), NULL, 0, 403 true, f.ctx, f.ctx_sz, &ran); 404 ASSERT_TRUE(ran, "loader ran"); 405 ASSERT_EQ(r, -EINVAL, "tampered blob rejected by emit_signature_match"); 406 } 407 gen_loader_fixture_fini(&f); 408 } 409 410 static void metadata_not_exclusive(void) 411 { 412 struct gen_loader_fixture f; 413 bool ran; 414 int r; 415 416 if (gen_loader_fixture_init(&f) == 0) { 417 /* 418 * Correct blob but a non-exclusive metadata map: the verifier does 419 * not reject (excl_prog_sha unset), so the runtime map->excl == 1 420 * check in the loader must. 421 */ 422 r = run_gen_loader(f.gopts.insns, f.gopts.insns_sz, f.blob, 423 f.data_sz, NULL, 0, NULL, 0, true, f.ctx, 424 f.ctx_sz, &ran); 425 ASSERT_TRUE(ran, "loader ran"); 426 ASSERT_EQ(r, -EINVAL, "non-exclusive metadata map rejected"); 427 } 428 gen_loader_fixture_fini(&f); 429 } 430 431 static void metadata_hash_not_computed(void) 432 { 433 struct gen_loader_fixture f; 434 bool ran; 435 int r; 436 437 if (gen_loader_fixture_init(&f) == 0) { 438 /* 439 * Correct, exclusive, frozen map, but its hash was never computed 440 * (no OBJ_GET_INFO_BY_FD), so map->sha stays zero. The loader must 441 * fail closed rather than treat an unset hash as a match. 442 */ 443 r = run_gen_loader(f.gopts.insns, f.gopts.insns_sz, f.blob, 444 f.data_sz, f.excl, sizeof(f.excl), NULL, 0, 445 false, f.ctx, f.ctx_sz, &ran); 446 ASSERT_TRUE(ran, "loader ran"); 447 ASSERT_EQ(r, -EINVAL, "uncomputed metadata hash rejected"); 448 } 449 gen_loader_fixture_fini(&f); 450 } 451 452 static void signature_enforced(void) 453 { 454 static const __u8 junk[64] = { 0x30, 0x42, 0x13, 0x37, }; 455 struct gen_loader_fixture f; 456 int fd; 457 458 if (gen_loader_fixture_init(&f) == 0) { 459 /* 460 * A present-but-invalid signature (the cert bytes are not a 461 * PKCS#7 signature) must be rejected at load: the signature 462 * path is honored, not ignored. (The valid path is covered by 463 * the signed lskels.) 464 */ 465 fd = load_loader(f.gopts.insns, f.gopts.insns_sz, -1, junk, 466 sizeof(junk), KEY_SPEC_SESSION_KEYRING); 467 ASSERT_LT(fd, 0, "invalid signature rejected at load"); 468 } 469 gen_loader_fixture_fini(&f); 470 } 471 472 static void signature_too_large(void) 473 { 474 static const __u8 junk[64] = {}; 475 struct gen_loader_fixture f; 476 int fd; 477 478 if (gen_loader_fixture_init(&f) == 0) { 479 /* 480 * signature_size beyond the kernel's bound (KMALLOC_MAX_CACHE_SIZE) 481 * is rejected before the buffer is read. 482 */ 483 fd = load_loader(f.gopts.insns, f.gopts.insns_sz, -1, junk, 484 64 << 20, KEY_SPEC_SESSION_KEYRING); 485 ASSERT_EQ(fd, -EINVAL, "oversized signature rejected"); 486 } 487 gen_loader_fixture_fini(&f); 488 } 489 490 static void signature_bad_keyring(void) 491 { 492 static const __u8 junk[64] = {}; 493 struct gen_loader_fixture f; 494 int fd; 495 496 if (gen_loader_fixture_init(&f) == 0) { 497 /* 498 * A present signature with a keyring_id that resolves to no key is 499 * rejected up front: bpf_prog_verify_signature() fails the keyring 500 * lookup (-EINVAL) before it ever looks at the signature bytes. A 501 * large positive serial takes the user-keyring path and won't exist. 502 */ 503 fd = load_loader(f.gopts.insns, f.gopts.insns_sz, -1, junk, 504 sizeof(junk), INT_MAX); 505 ASSERT_EQ(fd, -EINVAL, "signature with bad keyring_id rejected"); 506 } 507 gen_loader_fixture_fini(&f); 508 } 509 510 /* 511 * A signed loader must ignore ctx-supplied map dimensions: the host cannot 512 * resize a signed program's maps via the loader ctx. Drive a one-map program 513 * through gen_loader, ask (via ctx) for every map to be resized to a bogus 514 * value, and confirm the created maps keep their attested size. 515 */ 516 #define GATING_BOGUS_MAX 0x4000 517 518 static void metadata_ctx_max_entries_ignored(void) 519 { 520 LIBBPF_OPTS(gen_loader_opts, gopts, .gen_hash = true); 521 struct test_signed_loader_map *skel; 522 __u8 excl[SHA256_DIGEST_LENGTH]; 523 int nr_maps = 0, nr_progs = 0, i, checked = 0, r; 524 struct bpf_program *p; 525 struct bpf_map *m; 526 struct bpf_map_desc *md; 527 unsigned char *blob; 528 __u32 ctx_sz, data_sz; 529 void *ctx; 530 bool ran; 531 532 skel = test_signed_loader_map__open(); 533 if (!ASSERT_OK_PTR(skel, "skel_open")) 534 return; 535 if (!ASSERT_OK(bpf_object__gen_loader(skel->obj, &gopts), "gen_loader")) 536 goto destroy; 537 if (!ASSERT_OK(bpf_object__load(skel->obj), "gen_load")) 538 goto destroy; 539 540 bpf_object__for_each_program(p, skel->obj) 541 nr_progs++; 542 bpf_object__for_each_map(m, skel->obj) 543 nr_maps++; 544 ctx_sz = sizeof(struct bpf_loader_ctx) + 545 nr_maps * sizeof(struct bpf_map_desc) + 546 nr_progs * sizeof(struct bpf_prog_desc); 547 ctx = calloc(1, ctx_sz); 548 if (!ASSERT_OK_PTR(ctx, "ctx_alloc")) 549 goto destroy; 550 ((struct bpf_loader_ctx *)ctx)->sz = ctx_sz; 551 552 md = (struct bpf_map_desc *)((char *)ctx + sizeof(struct bpf_loader_ctx)); 553 for (i = 0; i < nr_maps; i++) 554 md[i].max_entries = GATING_BOGUS_MAX; 555 556 libbpf_sha256(gopts.insns, gopts.insns_sz, excl); 557 data_sz = gopts.data_sz; 558 blob = malloc(data_sz); 559 if (!ASSERT_OK_PTR(blob, "blob_alloc")) 560 goto free_ctx; 561 memcpy(blob, gopts.data, data_sz); 562 563 r = run_gen_loader(gopts.insns, gopts.insns_sz, blob, data_sz, 564 excl, sizeof(excl), NULL, 0, true, ctx, ctx_sz, &ran); 565 if (!ASSERT_TRUE(ran, "loader ran") || 566 !ASSERT_EQ(r, 0, "loader retval")) 567 goto free_blob; 568 569 for (i = 0; i < nr_maps; i++) { 570 struct bpf_map_info info; 571 __u32 ilen = sizeof(info); 572 int fd = md[i].map_fd; 573 574 if (fd <= 0) 575 continue; 576 memset(&info, 0, sizeof(info)); 577 if (ASSERT_OK(bpf_map_get_info_by_fd(fd, &info, &ilen), "map_info")) { 578 ASSERT_NEQ(info.max_entries, GATING_BOGUS_MAX, 579 "ctx max_entries ignored for signed loader"); 580 checked++; 581 } 582 } 583 ASSERT_GT(checked, 0, "inspected a created map"); 584 585 free_blob: 586 free(blob); 587 free_ctx: 588 close_loader_ctx_fds(ctx, nr_maps, nr_progs); 589 free(ctx); 590 destroy: 591 test_signed_loader_map__destroy(skel); 592 } 593 594 /* 595 * A signed loader must also ignore ctx-supplied initial_value: the host cannot 596 * re-seed a signed program's map contents through the loader ctx. Drive a 597 * program with one initialized global (a .data map) through gen_loader, point 598 * every map's ctx initial_value at an adversarial buffer, and confirm the 599 * created map still holds the attested value, never the ctx bytes. 600 */ 601 #define DATA_MAGIC 0x5eed1234abad1deaULL 602 603 static void metadata_ctx_initial_value_ignored(void) 604 { 605 LIBBPF_OPTS(gen_loader_opts, gopts, .gen_hash = true); 606 struct test_signed_loader_data *skel; 607 __u8 excl[SHA256_DIGEST_LENGTH], evil[64]; 608 int nr_maps = 0, nr_progs = 0, i, found = 0, r; 609 struct bpf_program *p; 610 struct bpf_map *m; 611 struct bpf_map_desc *md; 612 unsigned char *blob; 613 __u32 ctx_sz, data_sz; 614 void *ctx; 615 bool ran; 616 617 skel = test_signed_loader_data__open(); 618 if (!ASSERT_OK_PTR(skel, "skel_open")) 619 return; 620 if (!ASSERT_OK(bpf_object__gen_loader(skel->obj, &gopts), "gen_loader")) 621 goto destroy; 622 if (!ASSERT_OK(bpf_object__load(skel->obj), "gen_load")) 623 goto destroy; 624 625 bpf_object__for_each_program(p, skel->obj) 626 nr_progs++; 627 bpf_object__for_each_map(m, skel->obj) 628 nr_maps++; 629 ctx_sz = sizeof(struct bpf_loader_ctx) + 630 nr_maps * sizeof(struct bpf_map_desc) + 631 nr_progs * sizeof(struct bpf_prog_desc); 632 ctx = calloc(1, ctx_sz); 633 if (!ASSERT_OK_PTR(ctx, "ctx_alloc")) 634 goto destroy; 635 ((struct bpf_loader_ctx *)ctx)->sz = ctx_sz; 636 637 memset(evil, 0xAA, sizeof(evil)); 638 md = (struct bpf_map_desc *)((char *)ctx + sizeof(struct bpf_loader_ctx)); 639 for (i = 0; i < nr_maps; i++) 640 md[i].initial_value = ptr_to_u64(evil); 641 642 libbpf_sha256(gopts.insns, gopts.insns_sz, excl); 643 data_sz = gopts.data_sz; 644 blob = malloc(data_sz); 645 if (!ASSERT_OK_PTR(blob, "blob_alloc")) 646 goto free_ctx; 647 memcpy(blob, gopts.data, data_sz); 648 649 r = run_gen_loader(gopts.insns, gopts.insns_sz, blob, data_sz, 650 excl, sizeof(excl), NULL, 0, true, ctx, ctx_sz, &ran); 651 if (!ASSERT_TRUE(ran, "loader ran") || 652 !ASSERT_EQ(r, 0, "loader retval")) 653 goto free_blob; 654 655 for (i = 0; i < nr_maps; i++) { 656 struct bpf_map_info info; 657 __u32 ilen = sizeof(info), key = 0; 658 __u8 value[64] = {}; 659 __u64 got; 660 int fd = md[i].map_fd; 661 662 if (fd <= 0) 663 continue; 664 memset(&info, 0, sizeof(info)); 665 if (!ASSERT_OK(bpf_map_get_info_by_fd(fd, &info, &ilen), "map_info")) 666 continue; 667 if (info.value_size <= sizeof(value) && 668 bpf_map_lookup_elem(fd, &key, value) == 0) { 669 memcpy(&got, value, sizeof(got)); 670 /* attested .data survives; ctx bytes (0xAA..) ignored */ 671 if (got == DATA_MAGIC) 672 found = 1; 673 ASSERT_NEQ(got, 0xAAAAAAAAAAAAAAAAULL, 674 "ctx initial_value ignored for signed loader"); 675 } 676 } 677 ASSERT_EQ(found, 1, "attested .data value preserved"); 678 679 free_blob: 680 free(blob); 681 free_ctx: 682 close_loader_ctx_fds(ctx, nr_maps, nr_progs); 683 free(ctx); 684 destroy: 685 test_signed_loader_data__destroy(skel); 686 } 687 688 /* 689 * The load-time signature must authenticate the loader instructions: a valid 690 * signature loads, and the very same signature over one-byte-tampered insns is 691 * rejected. Uses ./verify_sig_setup.sh + ./sign-file at runtime, like 692 * verify_pkcs7_sig, and verifies against the session keyring the key was added 693 * to. (signature_enforced/_too_large only cover a malformed signature.) 694 */ 695 static void signature_authenticates_insns(void) 696 { 697 LIBBPF_OPTS(gen_loader_opts, gopts, .gen_hash = true); 698 char dir_tmpl[] = "/tmp/signed_loaderXXXXXX", *dir; 699 struct test_signed_loader *skel = NULL; 700 __u8 excl[SHA256_DIGEST_LENGTH], sig[8192]; 701 __u32 sig_sz = sizeof(sig), insns_sz, data_sz, ctx_sz; 702 unsigned char *insns = NULL, *tampered = NULL, *blob = NULL; 703 int nr_maps = 0, nr_progs = 0, r; 704 struct bpf_program *p; 705 struct bpf_map *m; 706 void *ctx = NULL; 707 bool ran; 708 709 syscall(__NR_request_key, "keyring", "_uid.0", NULL, 710 KEY_SPEC_SESSION_KEYRING); 711 dir = mkdtemp(dir_tmpl); 712 if (!ASSERT_OK_PTR(dir, "mkdtemp")) 713 return; 714 if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) { 715 rmdir(dir); 716 return; 717 } 718 719 skel = test_signed_loader__open(); 720 if (!ASSERT_OK_PTR(skel, "skel_open")) 721 goto cleanup; 722 if (!ASSERT_OK(bpf_object__gen_loader(skel->obj, &gopts), "gen_loader")) 723 goto cleanup; 724 if (!ASSERT_OK(bpf_object__load(skel->obj), "gen_load")) 725 goto cleanup; 726 727 bpf_object__for_each_program(p, skel->obj) 728 nr_progs++; 729 bpf_object__for_each_map(m, skel->obj) 730 nr_maps++; 731 ctx_sz = sizeof(struct bpf_loader_ctx) + 732 nr_maps * sizeof(struct bpf_map_desc) + 733 nr_progs * sizeof(struct bpf_prog_desc); 734 insns_sz = gopts.insns_sz; 735 data_sz = gopts.data_sz; 736 ctx = calloc(1, ctx_sz); 737 insns = malloc(insns_sz); 738 tampered = malloc(insns_sz); 739 blob = malloc(data_sz); 740 if (!ASSERT_OK_PTR(ctx, "ctx") || 741 !ASSERT_OK_PTR(insns, "insns") || 742 !ASSERT_OK_PTR(tampered, "tampered") || 743 !ASSERT_OK_PTR(blob, "blob")) 744 goto cleanup; 745 memcpy(insns, gopts.insns, insns_sz); 746 memcpy(blob, gopts.data, data_sz); 747 libbpf_sha256(insns, insns_sz, excl); 748 749 if (!ASSERT_OK(sign_buf(dir, insns, insns_sz, sig, &sig_sz), "sign-file")) 750 goto cleanup; 751 752 memset(ctx, 0, ctx_sz); 753 ((struct bpf_loader_ctx *)ctx)->sz = ctx_sz; 754 r = run_gen_loader(insns, insns_sz, blob, data_sz, excl, sizeof(excl), 755 sig, sig_sz, true, ctx, ctx_sz, &ran); 756 ASSERT_TRUE(ran, "valid signature: loader loaded and ran"); 757 ASSERT_EQ(r, 0, "valid signature accepted"); 758 close_loader_ctx_fds(ctx, nr_maps, nr_progs); 759 760 memcpy(tampered, insns, insns_sz); 761 tampered[insns_sz / 2] ^= 0xff; 762 memset(ctx, 0, ctx_sz); 763 ((struct bpf_loader_ctx *)ctx)->sz = ctx_sz; 764 r = run_gen_loader(tampered, insns_sz, blob, data_sz, excl, sizeof(excl), 765 sig, sig_sz, true, ctx, ctx_sz, &ran); 766 ASSERT_FALSE(ran, "tampered loader rejected before run"); 767 ASSERT_EQ(r, -EKEYREJECTED, "signature is bound to the instructions"); 768 cleanup: 769 free(insns); 770 free(tampered); 771 free(blob); 772 free(ctx); 773 test_signed_loader__destroy(skel); 774 run_setup("cleanup", dir); 775 } 776 777 static int make_excl_map(__u32 flags, __u32 value_size) 778 { 779 LIBBPF_OPTS(bpf_map_create_opts, opts); 780 __u8 hash[SHA256_DIGEST_LENGTH] = { 1 }; /* any 32-byte value */ 781 782 opts.excl_prog_hash = hash; 783 opts.excl_prog_hash_size = sizeof(hash); 784 opts.map_flags = flags; 785 return bpf_map_create(BPF_MAP_TYPE_ARRAY, "md", 4, value_size, 1, &opts); 786 } 787 788 static void hash_requires_frozen(void) 789 { 790 __u8 hbuf[SHA256_DIGEST_LENGTH], val[64] = {}; 791 struct bpf_map_info info; 792 __u32 ilen, key = 0; 793 int fd; 794 795 fd = make_excl_map(0, sizeof(val)); 796 if (!ASSERT_OK_FD(fd, "excl_map")) 797 return; 798 ASSERT_OK(bpf_map_update_elem(fd, &key, val, 0), "update"); 799 800 memset(&info, 0, sizeof(info)); 801 info.hash = ptr_to_u64(hbuf); 802 info.hash_size = sizeof(hbuf); 803 ilen = sizeof(info); 804 ASSERT_EQ(bpf_map_get_info_by_fd(fd, &info, &ilen), -EPERM, 805 "hash of unfrozen map rejected"); 806 close(fd); 807 } 808 809 static void no_update_after_freeze(void) 810 { 811 __u8 val[64] = {}; 812 __u32 key = 0; 813 int fd; 814 815 fd = make_excl_map(0, sizeof(val)); 816 if (!ASSERT_OK_FD(fd, "excl_map")) 817 return; 818 ASSERT_OK(bpf_map_update_elem(fd, &key, val, 0), "update"); 819 ASSERT_OK(bpf_map_freeze(fd), "freeze"); 820 ASSERT_EQ(bpf_map_update_elem(fd, &key, val, 0), -EPERM, 821 "update after freeze rejected"); 822 close(fd); 823 } 824 825 static void freeze_writable_mmap(void) 826 { 827 void *w; 828 int fd; 829 830 fd = make_excl_map(BPF_F_MMAPABLE, 4096); 831 if (!ASSERT_OK_FD(fd, "excl_mmapable_map")) 832 return; 833 w = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 834 if (ASSERT_OK_PTR(w, "writable_mmap")) { 835 ASSERT_EQ(bpf_map_freeze(fd), -EBUSY, 836 "freeze rejected while writable mmap held"); 837 munmap(w, 4096); 838 } 839 close(fd); 840 } 841 842 static void no_writable_mmap_frozen(void) 843 { 844 void *w; 845 int fd; 846 847 fd = make_excl_map(BPF_F_MMAPABLE, 4096); 848 if (!ASSERT_OK_FD(fd, "excl_mmapable_map")) 849 return; 850 ASSERT_OK(bpf_map_freeze(fd), "freeze"); 851 w = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 852 ASSERT_EQ(w, MAP_FAILED, "writable mmap of frozen map rejected"); 853 if (w != MAP_FAILED) 854 munmap(w, 4096); 855 close(fd); 856 } 857 858 static void map_hash_matches_libbpf(void) 859 { 860 __u8 kbuf[SHA256_DIGEST_LENGTH], lbuf[SHA256_DIGEST_LENGTH], val[64] = {}; 861 struct bpf_map_info info; 862 __u32 ilen, key = 0; 863 int fd, i; 864 865 /* 866 * The signing scheme assumes the kernel's map hash equals what libbpf 867 * computes over the same bytes (gen_loader bakes libbpf_sha256(blob); 868 * the kernel recomputes via array_map_get_hash). Pin that they agree. 869 */ 870 for (i = 0; i < (int)sizeof(val); i++) 871 val[i] = i * 7 + 1; 872 fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "h", 4, sizeof(val), 1, NULL); 873 if (!ASSERT_OK_FD(fd, "array_map")) 874 return; 875 ASSERT_OK(bpf_map_update_elem(fd, &key, val, 0), "update"); 876 ASSERT_OK(bpf_map_freeze(fd), "freeze"); 877 memset(&info, 0, sizeof(info)); 878 info.hash = ptr_to_u64(kbuf); 879 info.hash_size = sizeof(kbuf); 880 ilen = sizeof(info); 881 if (ASSERT_OK(bpf_map_get_info_by_fd(fd, &info, &ilen), "get_hash")) { 882 libbpf_sha256(val, sizeof(val), lbuf); 883 ASSERT_EQ(memcmp(kbuf, lbuf, sizeof(kbuf)), 0, 884 "kernel map hash matches libbpf_sha256"); 885 } 886 close(fd); 887 } 888 889 static void map_hash_multi_element(void) 890 { 891 const __u32 nr = 8, value_size = 64; 892 __u8 kbuf[SHA256_DIGEST_LENGTH], lbuf[SHA256_DIGEST_LENGTH]; 893 struct bpf_map_info info; 894 __u32 ilen, i, j; 895 __u8 *full; 896 int fd; 897 898 /* 899 * array_map_get_hash() hashes elem_size * max_entries (the whole value 900 * area), not just element 0. With an 8-aligned value_size elem_size has 901 * no padding, so pin that a >1-entry array's kernel hash equals 902 * libbpf_sha256() over the full, concatenated element contents. 903 */ 904 fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "h", 4, value_size, nr, NULL); 905 if (!ASSERT_OK_FD(fd, "array_map")) 906 return; 907 full = calloc(nr, value_size); 908 if (!ASSERT_OK_PTR(full, "buf")) 909 goto close_fd; 910 for (i = 0; i < nr; i++) { 911 __u8 *v = full + i * value_size; 912 913 for (j = 0; j < value_size; j++) 914 v[j] = i * 31 + j * 7 + 1; 915 ASSERT_OK(bpf_map_update_elem(fd, &i, v, 0), "update"); 916 } 917 ASSERT_OK(bpf_map_freeze(fd), "freeze"); 918 memset(&info, 0, sizeof(info)); 919 info.hash = ptr_to_u64(kbuf); 920 info.hash_size = sizeof(kbuf); 921 ilen = sizeof(info); 922 if (ASSERT_OK(bpf_map_get_info_by_fd(fd, &info, &ilen), "get_hash")) { 923 libbpf_sha256(full, (size_t)nr * value_size, lbuf); 924 ASSERT_EQ(memcmp(kbuf, lbuf, sizeof(kbuf)), 0, 925 "kernel hash covers full multi-element value area"); 926 } 927 free(full); 928 close_fd: 929 close(fd); 930 } 931 932 static void map_hash_bad_size(void) 933 { 934 __u8 kbuf[SHA256_DIGEST_LENGTH], val[64] = {}; 935 struct bpf_map_info info; 936 __u32 ilen, key = 0; 937 int fd; 938 939 fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "h", 4, sizeof(val), 1, NULL); 940 if (!ASSERT_OK_FD(fd, "array_map")) 941 return; 942 ASSERT_OK(bpf_map_update_elem(fd, &key, val, 0), "update"); 943 ASSERT_OK(bpf_map_freeze(fd), "freeze"); 944 memset(&info, 0, sizeof(info)); 945 info.hash = ptr_to_u64(kbuf); 946 info.hash_size = sizeof(kbuf) / 2; 947 ilen = sizeof(info); 948 ASSERT_EQ(bpf_map_get_info_by_fd(fd, &info, &ilen), -EINVAL, 949 "wrong hash_size rejected"); 950 close(fd); 951 } 952 953 static void map_hash_unsupported_type(void) 954 { 955 __u8 kbuf[SHA256_DIGEST_LENGTH]; 956 struct bpf_map_info info; 957 __u32 ilen; 958 int fd; 959 960 /* Only arrays implement map_get_hash; a hash map must be refused. */ 961 fd = bpf_map_create(BPF_MAP_TYPE_HASH, "h", 4, 8, 4, NULL); 962 if (!ASSERT_OK_FD(fd, "hash_map")) 963 return; 964 memset(&info, 0, sizeof(info)); 965 info.hash = ptr_to_u64(kbuf); 966 info.hash_size = sizeof(kbuf); 967 ilen = sizeof(info); 968 ASSERT_EQ(bpf_map_get_info_by_fd(fd, &info, &ilen), -EINVAL, 969 "hash unsupported for non-array map"); 970 close(fd); 971 } 972 973 void test_signed_loader(void) 974 { 975 if (test__start_subtest("metadata_check_shape")) 976 metadata_check_shape(); 977 if (test__start_subtest("metadata_match")) 978 metadata_match(); 979 if (test__start_subtest("metadata_sha_mismatch")) 980 metadata_sha_mismatch(); 981 if (test__start_subtest("metadata_not_exclusive")) 982 metadata_not_exclusive(); 983 if (test__start_subtest("metadata_hash_not_computed")) 984 metadata_hash_not_computed(); 985 if (test__start_subtest("signature_enforced")) 986 signature_enforced(); 987 if (test__start_subtest("signature_too_large")) 988 signature_too_large(); 989 if (test__start_subtest("signature_bad_keyring")) 990 signature_bad_keyring(); 991 if (test__start_subtest("metadata_ctx_max_entries_ignored")) 992 metadata_ctx_max_entries_ignored(); 993 if (test__start_subtest("metadata_ctx_initial_value_ignored")) 994 metadata_ctx_initial_value_ignored(); 995 if (test__start_subtest("signature_authenticates_insns")) 996 signature_authenticates_insns(); 997 if (test__start_subtest("hash_requires_frozen")) 998 hash_requires_frozen(); 999 if (test__start_subtest("no_update_after_freeze")) 1000 no_update_after_freeze(); 1001 if (test__start_subtest("freeze_writable_mmap")) 1002 freeze_writable_mmap(); 1003 if (test__start_subtest("no_writable_mmap_frozen")) 1004 no_writable_mmap_frozen(); 1005 if (test__start_subtest("map_hash_matches_libbpf")) 1006 map_hash_matches_libbpf(); 1007 if (test__start_subtest("map_hash_multi_element")) 1008 map_hash_multi_element(); 1009 if (test__start_subtest("map_hash_bad_size")) 1010 map_hash_bad_size(); 1011 if (test__start_subtest("map_hash_unsupported_type")) 1012 map_hash_unsupported_type(); 1013 } 1014