xref: /linux/tools/testing/selftests/bpf/prog_tests/signed_loader.c (revision 9779193e871b144e34ec4a3e50109b3778a51a69)
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