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