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/btf.h>
15
16 #include "bpf/libbpf_internal.h" /* for libbpf_sha256() */
17 #include "bpf/skel_internal.h" /* for loader ctx layout (bpf_loader_ctx etc) */
18
19 #include "test_signed_loader.skel.h"
20 #include "test_signed_loader_map.skel.h"
21 #include "test_signed_loader_data.skel.h"
22 #include "test_signed_loader_lsm.skel.h"
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
load_loader(const void * insns,__u32 insns_sz,int map_fd,const void * sig,__u32 sig_sz,__s32 keyring_id,__u32 fd_array_cnt)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 __u32 fd_array_cnt)
40 {
41 union bpf_attr attr;
42 int fd;
43
44 memset(&attr, 0, sizeof(attr));
45 attr.prog_type = BPF_PROG_TYPE_SYSCALL;
46 attr.insns = ptr_to_u64(insns);
47 attr.insn_cnt = insns_sz / sizeof(struct bpf_insn);
48 attr.license = ptr_to_u64("Dual BSD/GPL");
49 attr.prog_flags = BPF_F_SLEEPABLE;
50 attr.fd_array = ptr_to_u64(&map_fd);
51 if (sig) {
52 attr.signature = ptr_to_u64(sig);
53 attr.signature_size = sig_sz;
54 attr.keyring_id = keyring_id;
55 }
56 attr.fd_array_cnt = fd_array_cnt;
57 memcpy(attr.prog_name, "__loader.prog", sizeof("__loader.prog"));
58 fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
59 offsetofend(union bpf_attr, keyring_id));
60 return fd < 0 ? -errno : fd;
61 }
62
run_gen_loader(const void * insns,__u32 insns_sz,const void * data,__u32 data_sz,const void * excl,__u32 excl_sz,const void * sig,__u32 sig_sz,void * ctx,__u32 ctx_sz,bool * loader_ran)63 static int run_gen_loader(const void *insns, __u32 insns_sz,
64 const void *data, __u32 data_sz,
65 const void *excl, __u32 excl_sz,
66 const void *sig, __u32 sig_sz,
67 void *ctx, __u32 ctx_sz, bool *loader_ran)
68 {
69 LIBBPF_OPTS(bpf_map_create_opts, mopts,
70 .excl_prog_hash = excl,
71 .excl_prog_hash_size = excl_sz);
72 __u32 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
91 memset(&attr, 0, sizeof(attr));
92 attr.prog_type = BPF_PROG_TYPE_SYSCALL;
93 attr.insns = ptr_to_u64(insns);
94 attr.insn_cnt = insns_sz / sizeof(struct bpf_insn);
95 attr.license = ptr_to_u64("Dual BSD/GPL");
96 attr.prog_flags = BPF_F_SLEEPABLE;
97 attr.fd_array = ptr_to_u64(&map_fd);
98 if (sig) {
99 attr.signature = ptr_to_u64(sig);
100 attr.signature_size = sig_sz;
101 attr.keyring_id = KEY_SPEC_SESSION_KEYRING;
102 attr.fd_array_cnt = 1;
103 }
104 memcpy(attr.prog_name, "__loader.prog", sizeof("__loader.prog"));
105 prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
106 offsetofend(union bpf_attr, keyring_id));
107 if (prog_fd < 0) {
108 ret = -errno;
109 goto out_map;
110 }
111
112 memset(&attr, 0, sizeof(attr));
113 attr.test.prog_fd = prog_fd;
114 attr.test.ctx_in = ptr_to_u64(ctx);
115 attr.test.ctx_size_in = ctx_sz;
116 if (syscall(__NR_bpf, BPF_PROG_RUN, &attr,
117 offsetofend(union bpf_attr, test)) < 0) {
118 ret = -errno;
119 goto out_prog;
120 }
121 *loader_ran = true;
122 ret = (int)attr.test.retval;
123 out_prog:
124 close(prog_fd);
125 out_map:
126 close(map_fd);
127 return ret;
128 }
129
close_loader_ctx_fds(void * ctx,int nr_maps,int nr_progs)130 static void close_loader_ctx_fds(void *ctx, int nr_maps, int nr_progs)
131 {
132 struct bpf_map_desc *md = (struct bpf_map_desc *)((char *)ctx +
133 sizeof(struct bpf_loader_ctx));
134 struct bpf_prog_desc *pd = (struct bpf_prog_desc *)(md + nr_maps);
135 int i;
136
137 for (i = 0; i < nr_maps; i++)
138 if (md[i].map_fd > 0)
139 close(md[i].map_fd);
140 for (i = 0; i < nr_progs; i++)
141 if (pd[i].prog_fd > 0)
142 close(pd[i].prog_fd);
143 }
144
run_setup(const char * cmd,const char * dir)145 static int run_setup(const char *cmd, const char *dir)
146 {
147 int pid, status;
148
149 pid = fork();
150 if (pid < 0)
151 return -errno;
152 if (pid == 0) {
153 execlp("./verify_sig_setup.sh", "./verify_sig_setup.sh",
154 cmd, dir, NULL);
155 exit(1);
156 }
157 if (waitpid(pid, &status, 0) < 0)
158 return -errno;
159 return (WIFEXITED(status) &&
160 WEXITSTATUS(status) == 0) ? 0 : -EINVAL;
161 }
162
sign_buf(const char * dir,const void * buf,__u32 len,void * sig,__u32 * sig_sz)163 static int sign_buf(const char *dir, const void *buf, __u32 len,
164 void *sig, __u32 *sig_sz)
165 {
166 char data_tmpl[PATH_MAX], key[PATH_MAX];
167 char sigpath[PATH_MAX + sizeof(".p7s")];
168 int fd, pid, status, ret;
169 struct stat st;
170
171 ret = snprintf(data_tmpl, sizeof(data_tmpl), "%s/dataXXXXXX", dir);
172 if (ret < 0 || ret >= (int)sizeof(data_tmpl))
173 return -ENAMETOOLONG;
174 ret = 0;
175
176 fd = mkstemp(data_tmpl);
177 if (fd < 0)
178 return -errno;
179 if (write(fd, buf, len) != (ssize_t)len) {
180 close(fd);
181 ret = -EIO;
182 goto out;
183 }
184 close(fd);
185
186 pid = fork();
187 if (pid < 0) {
188 ret = -errno;
189 goto out;
190 }
191 if (pid == 0) {
192 snprintf(key, sizeof(key), "%s/signing_key.pem", dir);
193 execlp("./sign-file", "./sign-file", "-d", "sha256",
194 key, key, data_tmpl, NULL);
195 exit(1);
196 }
197 if (waitpid(pid, &status, 0) < 0 ||
198 !WIFEXITED(status) || WEXITSTATUS(status)) {
199 ret = -EINVAL;
200 goto out;
201 }
202
203 snprintf(sigpath, sizeof(sigpath), "%s.p7s", data_tmpl);
204 if (stat(sigpath, &st) < 0) {
205 ret = -errno;
206 goto out;
207 }
208 if (st.st_size > (off_t)*sig_sz) {
209 ret = -E2BIG;
210 goto out_sig;
211 }
212 fd = open(sigpath, O_RDONLY);
213 if (fd < 0) {
214 ret = -errno;
215 goto out_sig;
216 }
217 if (read(fd, sig, st.st_size) != st.st_size) {
218 close(fd);
219 ret = -EIO;
220 goto out_sig;
221 }
222 close(fd);
223 *sig_sz = st.st_size;
224 out_sig:
225 unlink(sigpath);
226 out:
227 unlink(data_tmpl);
228 return ret;
229 }
230
231 struct gen_loader_fixture {
232 struct test_signed_loader *skel;
233 struct gen_loader_opts gopts;
234 unsigned char *blob;
235 void *ctx;
236 __u32 data_sz;
237 __u32 ctx_sz;
238 int nr_maps;
239 int nr_progs;
240 __u8 excl[SHA256_DIGEST_LENGTH];
241 };
242
gen_loader_fixture_init(struct gen_loader_fixture * f)243 static int gen_loader_fixture_init(struct gen_loader_fixture *f)
244 {
245 LIBBPF_OPTS(gen_loader_opts, gopts, .gen_hash = true);
246 int nr_maps = 0, nr_progs = 0;
247 struct bpf_program *p;
248 struct bpf_map *m;
249
250 memset(f, 0, sizeof(*f));
251 f->skel = test_signed_loader__open();
252 if (!ASSERT_OK_PTR(f->skel, "skel_open"))
253 return -1;
254 if (!ASSERT_OK(bpf_object__gen_loader(f->skel->obj, &gopts), "gen_loader"))
255 return -1;
256 if (!ASSERT_OK(bpf_object__load(f->skel->obj), "gen_load"))
257 return -1;
258 f->gopts = gopts;
259
260 bpf_object__for_each_program(p, f->skel->obj)
261 nr_progs++;
262 bpf_object__for_each_map(m, f->skel->obj)
263 nr_maps++;
264 f->nr_maps = nr_maps;
265 f->nr_progs = nr_progs;
266 f->ctx_sz = sizeof(struct bpf_loader_ctx) +
267 nr_maps * sizeof(struct bpf_map_desc) +
268 nr_progs * sizeof(struct bpf_prog_desc);
269 f->ctx = calloc(1, f->ctx_sz);
270 if (!ASSERT_OK_PTR(f->ctx, "ctx_alloc"))
271 return -1;
272 ((struct bpf_loader_ctx *)f->ctx)->sz = f->ctx_sz;
273
274 f->data_sz = gopts.data_sz;
275 f->blob = malloc(f->data_sz);
276 if (!ASSERT_OK_PTR(f->blob, "blob_alloc"))
277 return -1;
278 memcpy(f->blob, gopts.data, f->data_sz);
279
280 /* excl_prog_hash = SHA256(loader insns) == the loader's prog->digest. */
281 libbpf_sha256(gopts.insns, gopts.insns_sz, f->excl);
282 return 0;
283 }
284
gen_loader_fixture_fini(struct gen_loader_fixture * f)285 static void gen_loader_fixture_fini(struct gen_loader_fixture *f)
286 {
287 if (f->ctx)
288 close_loader_ctx_fds(f->ctx, f->nr_maps, f->nr_progs);
289 free(f->blob);
290 free(f->ctx);
291 test_signed_loader__destroy(f->skel);
292 }
293
metadata_match(void)294 static void metadata_match(void)
295 {
296 struct gen_loader_fixture f;
297 bool ran;
298 int r;
299
300 if (gen_loader_fixture_init(&f) == 0) {
301 r = run_gen_loader(f.gopts.insns, f.gopts.insns_sz, f.blob,
302 f.data_sz, f.excl, sizeof(f.excl), NULL, 0,
303 f.ctx, f.ctx_sz, &ran);
304 ASSERT_TRUE(ran, "loader ran");
305 ASSERT_EQ(r, 0, "honest loader retval");
306 }
307 gen_loader_fixture_fini(&f);
308 }
309
signature_enforced(void)310 static void signature_enforced(void)
311 {
312 static const __u8 junk[64] = { 0x30, 0x42, 0x13, 0x37, };
313 struct gen_loader_fixture f;
314 int fd;
315
316 if (gen_loader_fixture_init(&f) == 0) {
317 /*
318 * A present-but-invalid signature (the cert bytes are not a
319 * PKCS#7 signature) must be rejected at load: the signature
320 * path is honored, not ignored. (The valid path is covered by
321 * the signed lskels.) Pin -EBADMSG, the PKCS#7 parse failure:
322 * a looser fd < 0 check could also be satisfied by the sparse
323 * fd_array rejection (-EACCES) that the loader's map reference
324 * would trip even if the signature were silently ignored.
325 */
326 fd = load_loader(f.gopts.insns, f.gopts.insns_sz, -1, junk,
327 sizeof(junk), KEY_SPEC_SESSION_KEYRING, 0);
328 ASSERT_EQ(fd, -EBADMSG, "invalid signature rejected at load");
329 if (fd >= 0)
330 close(fd);
331 }
332 gen_loader_fixture_fini(&f);
333 }
334
signed_nonexcl_fd_array_rejected(void)335 static void signed_nonexcl_fd_array_rejected(void)
336 {
337 static const __u8 junk[64] = { 0x30, 0x42, 0x13, 0x37, };
338 struct gen_loader_fixture f;
339 int map_fd, fd;
340
341 if (gen_loader_fixture_init(&f) == 0) {
342 /*
343 * A signed program may only bind exclusive maps through fd_array
344 * (their contents are folded into the signature). Binding a
345 * non-exclusive map is rejected, before the signature is even
346 * examined.
347 */
348 map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "nonexcl", 4,
349 f.data_sz, 1, NULL);
350 if (ASSERT_OK_FD(map_fd, "nonexcl_map")) {
351 if (ASSERT_OK(bpf_map_freeze(map_fd), "freeze")) {
352 fd = load_loader(f.gopts.insns, f.gopts.insns_sz,
353 map_fd, junk, sizeof(junk),
354 KEY_SPEC_SESSION_KEYRING, 1);
355 ASSERT_EQ(fd, -EPERM,
356 "non-exclusive map in signed fd_array rejected");
357 if (fd >= 0)
358 close(fd);
359 }
360 close(map_fd);
361 }
362 }
363 gen_loader_fixture_fini(&f);
364 }
365
signed_unfrozen_fd_array_rejected(void)366 static void signed_unfrozen_fd_array_rejected(void)
367 {
368 static const __u8 junk[64] = { 0x30, 0x42, 0x13, 0x37, };
369 LIBBPF_OPTS(bpf_map_create_opts, mopts);
370 struct gen_loader_fixture f;
371 __u32 key = 0;
372 int map_fd, fd;
373
374 if (gen_loader_fixture_init(&f) == 0) {
375 /*
376 * The metadata map must be frozen before a signed load so the
377 * folded bytes cannot change afterwards. Bind an exclusive map
378 * with matching contents but skip the freeze: the load must be
379 * rejected by the frozen check with -EPERM. The exclusivity
380 * check right after it would pass, so the errno uniquely pins
381 * the freeze requirement.
382 */
383 mopts.excl_prog_hash = f.excl;
384 mopts.excl_prog_hash_size = sizeof(f.excl);
385 map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "unfrozen", 4,
386 f.data_sz, 1, &mopts);
387 if (ASSERT_OK_FD(map_fd, "unfrozen_map")) {
388 if (ASSERT_OK(bpf_map_update_elem(map_fd, &key, f.blob, 0),
389 "update")) {
390 fd = load_loader(f.gopts.insns, f.gopts.insns_sz,
391 map_fd, junk, sizeof(junk),
392 KEY_SPEC_SESSION_KEYRING, 1);
393 ASSERT_EQ(fd, -EPERM,
394 "unfrozen map in signed fd_array rejected");
395 if (fd >= 0)
396 close(fd);
397 }
398 close(map_fd);
399 }
400 }
401 gen_loader_fixture_fini(&f);
402 }
403
signed_nonarray_fd_array_rejected(void)404 static void signed_nonarray_fd_array_rejected(void)
405 {
406 static const __u8 junk[64] = { 0x30, 0x42, 0x13, 0x37, };
407 LIBBPF_OPTS(bpf_map_create_opts, mopts);
408 struct gen_loader_fixture f;
409 int map_fd, fd;
410
411 if (gen_loader_fixture_init(&f) == 0) {
412 /*
413 * Only a plain BPF_MAP_TYPE_ARRAY may be folded into the
414 * signature. An exclusive map of any other type is rejected
415 * (-EINVAL) rather than folded - this is the type gate that
416 * keeps arena maps (map_direct_value_addr() returns a user
417 * address) and insn-array maps (buffer smaller than value_size)
418 * out of the hashed region, where the old code would have
419 * memcpy()'d from them. A hash map stands in here: it is
420 * exclusive (bound to the loader digest) but not an array.
421 */
422 mopts.excl_prog_hash = f.excl;
423 mopts.excl_prog_hash_size = sizeof(f.excl);
424 map_fd = bpf_map_create(BPF_MAP_TYPE_HASH, "excl_hash", 4, 4, 1,
425 &mopts);
426 if (ASSERT_OK_FD(map_fd, "excl_hash_map")) {
427 fd = load_loader(f.gopts.insns, f.gopts.insns_sz, map_fd,
428 junk, sizeof(junk),
429 KEY_SPEC_SESSION_KEYRING, 1);
430 ASSERT_EQ(fd, -EINVAL,
431 "non-array map in signed fd_array rejected");
432 if (fd >= 0)
433 close(fd);
434 close(map_fd);
435 }
436 }
437 gen_loader_fixture_fini(&f);
438 }
439
440 static int setup_meta_map(const struct gen_loader_fixture *f);
441
signed_btf_fd_array_rejected(void)442 static void signed_btf_fd_array_rejected(void)
443 {
444 char dir_tmpl[] = "/tmp/signed_loader_btfXXXXXX", *dir = NULL;
445 __u32 sig_sz = 8192;
446 int map_fd = -1, prog_fd = -1;
447 unsigned char *buf = NULL;
448 struct gen_loader_fixture f;
449 bool have_fixture = false;
450 struct btf *btf = NULL;
451 union bpf_attr attr;
452 int fds[2];
453 __u8 sig[8192];
454
455 syscall(__NR_request_key, "keyring", "_uid.0", NULL,
456 KEY_SPEC_SESSION_KEYRING);
457 dir = mkdtemp(dir_tmpl);
458 if (!ASSERT_OK_PTR(dir, "mkdtemp"))
459 return;
460 if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
461 rmdir(dir);
462 return;
463 }
464 have_fixture = true;
465 if (gen_loader_fixture_init(&f) != 0)
466 goto out;
467
468 /*
469 * fd_array binds maps and BTFs alike, but only exclusive array maps are
470 * folded into the signature. Build an otherwise genuinely signed load -
471 * insns || metadata, exclusive frozen map at fd_array[0] - then smuggle
472 * an extra BTF into fd_array[1]. A signed program may not bind any BTF,
473 * so resolving the fd_array entries rejects the BTF with -EACCES (in
474 * __add_used_btf(), before the signature is even verified).
475 */
476 buf = malloc((size_t)f.gopts.insns_sz + f.data_sz);
477 if (!ASSERT_OK_PTR(buf, "signbuf"))
478 goto out;
479 memcpy(buf, f.gopts.insns, f.gopts.insns_sz);
480 memcpy(buf + f.gopts.insns_sz, f.blob, f.data_sz);
481 if (!ASSERT_OK(sign_buf(dir, buf, f.gopts.insns_sz + f.data_sz, sig,
482 &sig_sz), "sign insns||metadata"))
483 goto out;
484
485 map_fd = setup_meta_map(&f);
486 if (!ASSERT_OK_FD(map_fd, "meta_map"))
487 goto out;
488 btf = btf__new_empty();
489 if (!ASSERT_OK_PTR(btf, "btf_new_empty"))
490 goto out;
491 btf__add_int(btf, "int", 4, BTF_INT_SIGNED);
492 if (!ASSERT_OK(btf__load_into_kernel(btf), "btf_load"))
493 goto out;
494
495 fds[0] = map_fd;
496 fds[1] = btf__fd(btf);
497 memset(&attr, 0, sizeof(attr));
498 attr.prog_type = BPF_PROG_TYPE_SYSCALL;
499 attr.insns = ptr_to_u64(f.gopts.insns);
500 attr.insn_cnt = f.gopts.insns_sz / sizeof(struct bpf_insn);
501 attr.license = ptr_to_u64("Dual BSD/GPL");
502 attr.prog_flags = BPF_F_SLEEPABLE;
503 attr.fd_array = ptr_to_u64(fds);
504 attr.fd_array_cnt = 2;
505 attr.signature = ptr_to_u64(sig);
506 attr.signature_size = sig_sz;
507 attr.keyring_id = KEY_SPEC_SESSION_KEYRING;
508 memcpy(attr.prog_name, "__loader.prog", sizeof("__loader.prog"));
509 prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
510 offsetofend(union bpf_attr, keyring_id));
511 ASSERT_EQ(prog_fd < 0 ? -errno : prog_fd, -EACCES,
512 "BTF in signed fd_array rejected");
513 if (prog_fd >= 0)
514 close(prog_fd);
515 out:
516 if (btf)
517 btf__free(btf);
518 if (map_fd >= 0)
519 close(map_fd);
520 if (have_fixture)
521 gen_loader_fixture_fini(&f);
522 if (dir)
523 run_setup("cleanup", dir);
524 free(buf);
525 }
526
signature_failure_logs(void)527 static void signature_failure_logs(void)
528 {
529 static const __u8 junk[64] = { 0x30, 0x42, 0x13, 0x37, };
530 char log_buf[1024] = {};
531 struct gen_loader_fixture f;
532 union bpf_attr attr;
533 int fd;
534
535 if (gen_loader_fixture_init(&f) == 0) {
536 /*
537 * Signature verification now runs inside bpf_check(), so a
538 * failure is reported through the verifier log. A present-but-
539 * invalid signature is rejected and the log says why.
540 */
541 memset(&attr, 0, sizeof(attr));
542 attr.prog_type = BPF_PROG_TYPE_SYSCALL;
543 attr.insns = ptr_to_u64(f.gopts.insns);
544 attr.insn_cnt = f.gopts.insns_sz / sizeof(struct bpf_insn);
545 attr.license = ptr_to_u64("Dual BSD/GPL");
546 attr.prog_flags = BPF_F_SLEEPABLE;
547 attr.signature = ptr_to_u64(junk);
548 attr.signature_size = sizeof(junk);
549 attr.keyring_id = KEY_SPEC_SESSION_KEYRING;
550 attr.log_level = 1;
551 attr.log_buf = ptr_to_u64(log_buf);
552 attr.log_size = sizeof(log_buf);
553 memcpy(attr.prog_name, "__loader.prog", sizeof("__loader.prog"));
554
555 fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
556 offsetofend(union bpf_attr, keyring_id));
557 ASSERT_LT(fd, 0, "invalid signature rejected at load");
558 if (fd >= 0)
559 close(fd);
560 ASSERT_HAS_SUBSTR(log_buf, "signature verification failed",
561 "verifier logs signature failure");
562 }
563 gen_loader_fixture_fini(&f);
564 }
565
signature_too_large(void)566 static void signature_too_large(void)
567 {
568 static const __u8 junk[64] = {};
569 struct gen_loader_fixture f;
570 int fd;
571
572 if (gen_loader_fixture_init(&f) == 0) {
573 /*
574 * signature_size beyond the kernel's bound (KMALLOC_MAX_CACHE_SIZE)
575 * is rejected before the buffer is read.
576 */
577 fd = load_loader(f.gopts.insns, f.gopts.insns_sz, -1, junk,
578 64 << 20, KEY_SPEC_SESSION_KEYRING, 0);
579 ASSERT_EQ(fd, -EINVAL, "oversized signature rejected");
580 if (fd >= 0)
581 close(fd);
582 }
583 gen_loader_fixture_fini(&f);
584 }
585
signature_zero_size(void)586 static void signature_zero_size(void)
587 {
588 static const __u8 junk[64] = {};
589 struct gen_loader_fixture f;
590 int fd;
591
592 if (gen_loader_fixture_init(&f) == 0) {
593 /*
594 * A present signature with signature_size == 0 is rejected
595 * up front, before the keyring is resolved or the signature
596 * buffer is read.
597 */
598 fd = load_loader(f.gopts.insns, f.gopts.insns_sz, -1, junk,
599 0, KEY_SPEC_SESSION_KEYRING, 0);
600 ASSERT_EQ(fd, -EINVAL, "zero-size signature rejected");
601 if (fd >= 0)
602 close(fd);
603 }
604 gen_loader_fixture_fini(&f);
605 }
606
signature_bad_keyring(void)607 static void signature_bad_keyring(void)
608 {
609 static const __u8 junk[64] = {};
610 struct gen_loader_fixture f;
611 int fd;
612
613 if (gen_loader_fixture_init(&f) == 0) {
614 /*
615 * A present signature with a keyring_id that resolves to no key is
616 * rejected up front: bpf_prog_verify_signature() fails the keyring
617 * lookup (-EINVAL) before it ever looks at the signature bytes. A
618 * large positive serial takes the user-keyring path and won't exist.
619 */
620 fd = load_loader(f.gopts.insns, f.gopts.insns_sz, -1, junk,
621 sizeof(junk), INT_MAX, 0);
622 ASSERT_EQ(fd, -EINVAL, "signature with bad keyring_id rejected");
623 if (fd >= 0)
624 close(fd);
625 }
626 gen_loader_fixture_fini(&f);
627 }
628
629 /*
630 * A signed loader must ignore ctx-supplied map dimensions: the host cannot
631 * resize a signed program's maps via the loader ctx. Drive a one-map program
632 * through gen_loader, ask (via ctx) for every map to be resized to a bogus
633 * value, and confirm the created maps keep their attested size.
634 */
635 #define GATING_BOGUS_MAX 0x4000
636
metadata_ctx_max_entries_ignored(void)637 static void metadata_ctx_max_entries_ignored(void)
638 {
639 LIBBPF_OPTS(gen_loader_opts, gopts, .gen_hash = true);
640 struct test_signed_loader_map *skel;
641 __u8 excl[SHA256_DIGEST_LENGTH];
642 int nr_maps = 0, nr_progs = 0, i, checked = 0, r;
643 struct bpf_program *p;
644 struct bpf_map *m;
645 struct bpf_map_desc *md;
646 unsigned char *blob;
647 __u32 ctx_sz, data_sz;
648 void *ctx;
649 bool ran;
650
651 skel = test_signed_loader_map__open();
652 if (!ASSERT_OK_PTR(skel, "skel_open"))
653 return;
654 if (!ASSERT_OK(bpf_object__gen_loader(skel->obj, &gopts), "gen_loader"))
655 goto destroy;
656 if (!ASSERT_OK(bpf_object__load(skel->obj), "gen_load"))
657 goto destroy;
658
659 bpf_object__for_each_program(p, skel->obj)
660 nr_progs++;
661 bpf_object__for_each_map(m, skel->obj)
662 nr_maps++;
663 ctx_sz = sizeof(struct bpf_loader_ctx) +
664 nr_maps * sizeof(struct bpf_map_desc) +
665 nr_progs * sizeof(struct bpf_prog_desc);
666 ctx = calloc(1, ctx_sz);
667 if (!ASSERT_OK_PTR(ctx, "ctx_alloc"))
668 goto destroy;
669 ((struct bpf_loader_ctx *)ctx)->sz = ctx_sz;
670
671 md = (struct bpf_map_desc *)((char *)ctx + sizeof(struct bpf_loader_ctx));
672 for (i = 0; i < nr_maps; i++)
673 md[i].max_entries = GATING_BOGUS_MAX;
674
675 libbpf_sha256(gopts.insns, gopts.insns_sz, excl);
676 data_sz = gopts.data_sz;
677 blob = malloc(data_sz);
678 if (!ASSERT_OK_PTR(blob, "blob_alloc"))
679 goto free_ctx;
680 memcpy(blob, gopts.data, data_sz);
681
682 r = run_gen_loader(gopts.insns, gopts.insns_sz, blob, data_sz,
683 excl, sizeof(excl), NULL, 0, ctx, ctx_sz, &ran);
684 if (!ASSERT_TRUE(ran, "loader ran") ||
685 !ASSERT_EQ(r, 0, "loader retval"))
686 goto free_blob;
687
688 for (i = 0; i < nr_maps; i++) {
689 struct bpf_map_info info;
690 __u32 ilen = sizeof(info);
691 int fd = md[i].map_fd;
692
693 if (fd <= 0)
694 continue;
695 memset(&info, 0, sizeof(info));
696 if (ASSERT_OK(bpf_map_get_info_by_fd(fd, &info, &ilen), "map_info")) {
697 ASSERT_NEQ(info.max_entries, GATING_BOGUS_MAX,
698 "ctx max_entries ignored for signed loader");
699 checked++;
700 }
701 }
702 ASSERT_GT(checked, 0, "inspected a created map");
703
704 free_blob:
705 free(blob);
706 free_ctx:
707 close_loader_ctx_fds(ctx, nr_maps, nr_progs);
708 free(ctx);
709 destroy:
710 test_signed_loader_map__destroy(skel);
711 }
712
713 /*
714 * A signed loader must also ignore ctx-supplied initial_value: the host cannot
715 * re-seed a signed program's map contents through the loader ctx. Drive a
716 * program with one initialized global (a .data map) through gen_loader, point
717 * every map's ctx initial_value at an adversarial buffer, and confirm the
718 * created map still holds the attested value, never the ctx bytes.
719 */
720 #define DATA_MAGIC 0x5eed1234abad1deaULL
721
metadata_ctx_initial_value_ignored(void)722 static void metadata_ctx_initial_value_ignored(void)
723 {
724 LIBBPF_OPTS(gen_loader_opts, gopts, .gen_hash = true);
725 struct test_signed_loader_data *skel;
726 __u8 excl[SHA256_DIGEST_LENGTH], evil[64];
727 int nr_maps = 0, nr_progs = 0, i, found = 0, r;
728 struct bpf_program *p;
729 struct bpf_map *m;
730 struct bpf_map_desc *md;
731 unsigned char *blob;
732 __u32 ctx_sz, data_sz;
733 void *ctx;
734 bool ran;
735
736 skel = test_signed_loader_data__open();
737 if (!ASSERT_OK_PTR(skel, "skel_open"))
738 return;
739 if (!ASSERT_OK(bpf_object__gen_loader(skel->obj, &gopts), "gen_loader"))
740 goto destroy;
741 if (!ASSERT_OK(bpf_object__load(skel->obj), "gen_load"))
742 goto destroy;
743
744 bpf_object__for_each_program(p, skel->obj)
745 nr_progs++;
746 bpf_object__for_each_map(m, skel->obj)
747 nr_maps++;
748 ctx_sz = sizeof(struct bpf_loader_ctx) +
749 nr_maps * sizeof(struct bpf_map_desc) +
750 nr_progs * sizeof(struct bpf_prog_desc);
751 ctx = calloc(1, ctx_sz);
752 if (!ASSERT_OK_PTR(ctx, "ctx_alloc"))
753 goto destroy;
754 ((struct bpf_loader_ctx *)ctx)->sz = ctx_sz;
755
756 memset(evil, 0xAA, sizeof(evil));
757 md = (struct bpf_map_desc *)((char *)ctx + sizeof(struct bpf_loader_ctx));
758 for (i = 0; i < nr_maps; i++)
759 md[i].initial_value = ptr_to_u64(evil);
760
761 libbpf_sha256(gopts.insns, gopts.insns_sz, excl);
762 data_sz = gopts.data_sz;
763 blob = malloc(data_sz);
764 if (!ASSERT_OK_PTR(blob, "blob_alloc"))
765 goto free_ctx;
766 memcpy(blob, gopts.data, data_sz);
767
768 r = run_gen_loader(gopts.insns, gopts.insns_sz, blob, data_sz,
769 excl, sizeof(excl), NULL, 0, ctx, ctx_sz, &ran);
770 if (!ASSERT_TRUE(ran, "loader ran") ||
771 !ASSERT_EQ(r, 0, "loader retval"))
772 goto free_blob;
773
774 for (i = 0; i < nr_maps; i++) {
775 struct bpf_map_info info;
776 __u32 ilen = sizeof(info), key = 0;
777 __u8 value[64] = {};
778 __u64 got;
779 int fd = md[i].map_fd;
780
781 if (fd <= 0)
782 continue;
783 memset(&info, 0, sizeof(info));
784 if (!ASSERT_OK(bpf_map_get_info_by_fd(fd, &info, &ilen), "map_info"))
785 continue;
786 if (info.value_size <= sizeof(value) &&
787 bpf_map_lookup_elem(fd, &key, value) == 0) {
788 memcpy(&got, value, sizeof(got));
789 /* attested .data survives; ctx bytes (0xAA..) ignored */
790 if (got == DATA_MAGIC)
791 found = 1;
792 ASSERT_NEQ(got, 0xAAAAAAAAAAAAAAAAULL,
793 "ctx initial_value ignored for signed loader");
794 }
795 }
796 ASSERT_EQ(found, 1, "attested .data value preserved");
797
798 free_blob:
799 free(blob);
800 free_ctx:
801 close_loader_ctx_fds(ctx, nr_maps, nr_progs);
802 free(ctx);
803 destroy:
804 test_signed_loader_data__destroy(skel);
805 }
806
807 /*
808 * The load-time signature must authenticate the loader instructions: a valid
809 * signature loads, and the very same signature over one-byte-tampered insns is
810 * rejected. Uses ./verify_sig_setup.sh + ./sign-file at runtime, like
811 * verify_pkcs7_sig, and verifies against the session keyring the key was added
812 * to. (signature_enforced/_too_large only cover a malformed signature.)
813 */
signature_authenticates_insns(void)814 static void signature_authenticates_insns(void)
815 {
816 LIBBPF_OPTS(gen_loader_opts, gopts, .gen_hash = true);
817 char dir_tmpl[] = "/tmp/signed_loaderXXXXXX", *dir;
818 struct test_signed_loader *skel = NULL;
819 __u8 excl[SHA256_DIGEST_LENGTH], sig[8192];
820 __u32 sig_sz = sizeof(sig), insns_sz, data_sz, ctx_sz;
821 unsigned char *insns = NULL, *tampered = NULL, *blob = NULL;
822 unsigned char *signbuf = NULL;
823 int nr_maps = 0, nr_progs = 0, r;
824 struct bpf_program *p;
825 struct bpf_map *m;
826 void *ctx = NULL;
827 bool ran;
828
829 syscall(__NR_request_key, "keyring", "_uid.0", NULL,
830 KEY_SPEC_SESSION_KEYRING);
831 dir = mkdtemp(dir_tmpl);
832 if (!ASSERT_OK_PTR(dir, "mkdtemp"))
833 return;
834 if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
835 rmdir(dir);
836 return;
837 }
838
839 skel = test_signed_loader__open();
840 if (!ASSERT_OK_PTR(skel, "skel_open"))
841 goto cleanup;
842 if (!ASSERT_OK(bpf_object__gen_loader(skel->obj, &gopts), "gen_loader"))
843 goto cleanup;
844 if (!ASSERT_OK(bpf_object__load(skel->obj), "gen_load"))
845 goto cleanup;
846
847 bpf_object__for_each_program(p, skel->obj)
848 nr_progs++;
849 bpf_object__for_each_map(m, skel->obj)
850 nr_maps++;
851 ctx_sz = sizeof(struct bpf_loader_ctx) +
852 nr_maps * sizeof(struct bpf_map_desc) +
853 nr_progs * sizeof(struct bpf_prog_desc);
854 insns_sz = gopts.insns_sz;
855 data_sz = gopts.data_sz;
856 ctx = calloc(1, ctx_sz);
857 insns = malloc(insns_sz);
858 tampered = malloc(insns_sz);
859 blob = malloc(data_sz);
860 if (!ASSERT_OK_PTR(ctx, "ctx") ||
861 !ASSERT_OK_PTR(insns, "insns") ||
862 !ASSERT_OK_PTR(tampered, "tampered") ||
863 !ASSERT_OK_PTR(blob, "blob"))
864 goto cleanup;
865 memcpy(insns, gopts.insns, insns_sz);
866 memcpy(blob, gopts.data, data_sz);
867 libbpf_sha256(insns, insns_sz, excl);
868
869 signbuf = malloc((size_t)insns_sz + data_sz);
870 if (!ASSERT_OK_PTR(signbuf, "signbuf"))
871 goto cleanup;
872 memcpy(signbuf, insns, insns_sz);
873 memcpy(signbuf + insns_sz, blob, data_sz);
874 if (!ASSERT_OK(sign_buf(dir, signbuf, insns_sz + data_sz, sig, &sig_sz),
875 "sign-file"))
876 goto cleanup;
877
878 memset(ctx, 0, ctx_sz);
879 ((struct bpf_loader_ctx *)ctx)->sz = ctx_sz;
880 r = run_gen_loader(insns, insns_sz, blob, data_sz, excl, sizeof(excl),
881 sig, sig_sz, ctx, ctx_sz, &ran);
882 ASSERT_TRUE(ran, "valid signature: loader loaded and ran");
883 ASSERT_EQ(r, 0, "valid signature accepted");
884 close_loader_ctx_fds(ctx, nr_maps, nr_progs);
885
886 memcpy(tampered, insns, insns_sz);
887 tampered[insns_sz / 2] ^= 0xff;
888 /*
889 * Bind the metadata map to the tampered loader's own digest, so the
890 * verifier's exclusive-map check (excl_prog_sha == prog->digest) passes
891 * and the signature - verified after the maps are resolved - is what
892 * rejects the load. This is the attacker's best case: even after
893 * re-binding the exclusive map to their tampered loader, the signature
894 * over the original insns || metadata still fails. (Leaving the map
895 * bound to the original digest would instead trip the excl check first.)
896 */
897 libbpf_sha256(tampered, insns_sz, excl);
898 memset(ctx, 0, ctx_sz);
899 ((struct bpf_loader_ctx *)ctx)->sz = ctx_sz;
900 r = run_gen_loader(tampered, insns_sz, blob, data_sz, excl, sizeof(excl),
901 sig, sig_sz, ctx, ctx_sz, &ran);
902 ASSERT_FALSE(ran, "tampered loader rejected before run");
903 ASSERT_EQ(r, -EKEYREJECTED, "signature is bound to the instructions");
904 cleanup:
905 free(insns);
906 free(tampered);
907 free(blob);
908 free(signbuf);
909 free(ctx);
910 test_signed_loader__destroy(skel);
911 run_setup("cleanup", dir);
912 }
913
signature_authenticates_metadata(void)914 static void signature_authenticates_metadata(void)
915 {
916 LIBBPF_OPTS(gen_loader_opts, gopts, .gen_hash = true);
917 char dir_tmpl[] = "/tmp/signed_loaderXXXXXX", *dir;
918 struct test_signed_loader *skel = NULL;
919 __u8 excl[SHA256_DIGEST_LENGTH], sig[8192];
920 __u32 sig_sz = sizeof(sig), insns_sz, data_sz, ctx_sz;
921 unsigned char *insns = NULL, *blob = NULL;
922 unsigned char *signbuf = NULL;
923 int nr_maps = 0, nr_progs = 0, r;
924 struct bpf_program *p;
925 struct bpf_map *m;
926 void *ctx = NULL;
927 bool ran;
928
929 syscall(__NR_request_key, "keyring", "_uid.0", NULL,
930 KEY_SPEC_SESSION_KEYRING);
931 dir = mkdtemp(dir_tmpl);
932 if (!ASSERT_OK_PTR(dir, "mkdtemp"))
933 return;
934 if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
935 rmdir(dir);
936 return;
937 }
938
939 skel = test_signed_loader__open();
940 if (!ASSERT_OK_PTR(skel, "skel_open"))
941 goto cleanup;
942 if (!ASSERT_OK(bpf_object__gen_loader(skel->obj, &gopts), "gen_loader"))
943 goto cleanup;
944 if (!ASSERT_OK(bpf_object__load(skel->obj), "gen_load"))
945 goto cleanup;
946
947 bpf_object__for_each_program(p, skel->obj)
948 nr_progs++;
949 bpf_object__for_each_map(m, skel->obj)
950 nr_maps++;
951 ctx_sz = sizeof(struct bpf_loader_ctx) +
952 nr_maps * sizeof(struct bpf_map_desc) +
953 nr_progs * sizeof(struct bpf_prog_desc);
954 insns_sz = gopts.insns_sz;
955 data_sz = gopts.data_sz;
956 ctx = calloc(1, ctx_sz);
957 insns = malloc(insns_sz);
958 blob = malloc(data_sz);
959 if (!ASSERT_OK_PTR(ctx, "ctx") ||
960 !ASSERT_OK_PTR(insns, "insns") ||
961 !ASSERT_OK_PTR(blob, "blob"))
962 goto cleanup;
963 memcpy(insns, gopts.insns, insns_sz);
964 memcpy(blob, gopts.data, data_sz);
965 libbpf_sha256(insns, insns_sz, excl);
966
967 signbuf = malloc((size_t)insns_sz + data_sz);
968 if (!ASSERT_OK_PTR(signbuf, "signbuf"))
969 goto cleanup;
970 memcpy(signbuf, insns, insns_sz);
971 memcpy(signbuf + insns_sz, blob, data_sz);
972 if (!ASSERT_OK(sign_buf(dir, signbuf, insns_sz + data_sz, sig, &sig_sz),
973 "sign-file"))
974 goto cleanup;
975
976 memset(ctx, 0, ctx_sz);
977 ((struct bpf_loader_ctx *)ctx)->sz = ctx_sz;
978 r = run_gen_loader(insns, insns_sz, blob, data_sz, excl, sizeof(excl),
979 sig, sig_sz, ctx, ctx_sz, &ran);
980 ASSERT_TRUE(ran, "valid signature: loader loaded and ran");
981 ASSERT_EQ(r, 0, "valid signature accepted");
982 close_loader_ctx_fds(ctx, nr_maps, nr_progs);
983
984 /*
985 * Tamper the metadata after signing while leaving the instructions
986 * and thus the exclusive hash binding untouched: the map freezes
987 * fine and excl_prog_sha still matches the loader's digest, so the
988 * load reaches signature verification, which folds the live frozen
989 * map bytes into the checked payload and must reject the modified
990 * blob. A kernel folding anything but the map contents themselves
991 * would wrongly accept this load.
992 */
993 blob[data_sz / 2] ^= 0xff;
994 memset(ctx, 0, ctx_sz);
995 ((struct bpf_loader_ctx *)ctx)->sz = ctx_sz;
996 r = run_gen_loader(insns, insns_sz, blob, data_sz, excl, sizeof(excl),
997 sig, sig_sz, ctx, ctx_sz, &ran);
998 ASSERT_FALSE(ran, "tampered metadata rejected before run");
999 ASSERT_EQ(r, -EKEYREJECTED, "signature is bound to the metadata");
1000 cleanup:
1001 free(insns);
1002 free(blob);
1003 free(signbuf);
1004 free(ctx);
1005 test_signed_loader__destroy(skel);
1006 run_setup("cleanup", dir);
1007 }
1008
make_excl_map(__u32 flags,__u32 value_size)1009 static int make_excl_map(__u32 flags, __u32 value_size)
1010 {
1011 LIBBPF_OPTS(bpf_map_create_opts, opts);
1012 __u8 hash[SHA256_DIGEST_LENGTH] = { 1 }; /* any 32-byte value */
1013
1014 opts.excl_prog_hash = hash;
1015 opts.excl_prog_hash_size = sizeof(hash);
1016 opts.map_flags = flags;
1017 return bpf_map_create(BPF_MAP_TYPE_ARRAY, "md", 4, value_size, 1, &opts);
1018 }
1019
hash_requires_frozen(void)1020 static void hash_requires_frozen(void)
1021 {
1022 __u8 hbuf[SHA256_DIGEST_LENGTH], val[64] = {};
1023 struct bpf_map_info info;
1024 __u32 ilen, key = 0;
1025 int fd;
1026
1027 fd = make_excl_map(0, sizeof(val));
1028 if (!ASSERT_OK_FD(fd, "excl_map"))
1029 return;
1030 ASSERT_OK(bpf_map_update_elem(fd, &key, val, 0), "update");
1031
1032 memset(&info, 0, sizeof(info));
1033 info.hash = ptr_to_u64(hbuf);
1034 info.hash_size = sizeof(hbuf);
1035 ilen = sizeof(info);
1036 ASSERT_EQ(bpf_map_get_info_by_fd(fd, &info, &ilen), -EPERM,
1037 "hash of unfrozen map rejected");
1038 close(fd);
1039 }
1040
no_update_after_freeze(void)1041 static void no_update_after_freeze(void)
1042 {
1043 __u8 val[64] = {};
1044 __u32 key = 0;
1045 int fd;
1046
1047 fd = make_excl_map(0, sizeof(val));
1048 if (!ASSERT_OK_FD(fd, "excl_map"))
1049 return;
1050 ASSERT_OK(bpf_map_update_elem(fd, &key, val, 0), "update");
1051 ASSERT_OK(bpf_map_freeze(fd), "freeze");
1052 ASSERT_EQ(bpf_map_update_elem(fd, &key, val, 0), -EPERM,
1053 "update after freeze rejected");
1054 close(fd);
1055 }
1056
freeze_writable_mmap(void)1057 static void freeze_writable_mmap(void)
1058 {
1059 void *w;
1060 int fd;
1061
1062 fd = make_excl_map(BPF_F_MMAPABLE, 4096);
1063 if (!ASSERT_OK_FD(fd, "excl_mmapable_map"))
1064 return;
1065 w = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
1066 if (ASSERT_OK_PTR(w, "writable_mmap")) {
1067 ASSERT_EQ(bpf_map_freeze(fd), -EBUSY,
1068 "freeze rejected while writable mmap held");
1069 munmap(w, 4096);
1070 }
1071 close(fd);
1072 }
1073
no_writable_mmap_frozen(void)1074 static void no_writable_mmap_frozen(void)
1075 {
1076 void *w;
1077 int fd;
1078
1079 fd = make_excl_map(BPF_F_MMAPABLE, 4096);
1080 if (!ASSERT_OK_FD(fd, "excl_mmapable_map"))
1081 return;
1082 ASSERT_OK(bpf_map_freeze(fd), "freeze");
1083 w = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
1084 ASSERT_EQ(w, MAP_FAILED, "writable mmap of frozen map rejected");
1085 if (w != MAP_FAILED)
1086 munmap(w, 4096);
1087 close(fd);
1088 }
1089
map_hash_matches_libbpf(void)1090 static void map_hash_matches_libbpf(void)
1091 {
1092 __u8 kbuf[SHA256_DIGEST_LENGTH], lbuf[SHA256_DIGEST_LENGTH], val[64] = {};
1093 struct bpf_map_info info;
1094 __u32 ilen, key = 0;
1095 int fd, i;
1096
1097 /*
1098 * The signing scheme assumes the kernel's map hash equals what libbpf
1099 * computes over the same bytes (gen_loader bakes libbpf_sha256(blob);
1100 * the kernel recomputes via array_map_get_hash). Pin that they agree.
1101 */
1102 for (i = 0; i < (int)sizeof(val); i++)
1103 val[i] = i * 7 + 1;
1104 fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "h", 4, sizeof(val), 1, NULL);
1105 if (!ASSERT_OK_FD(fd, "array_map"))
1106 return;
1107 ASSERT_OK(bpf_map_update_elem(fd, &key, val, 0), "update");
1108 ASSERT_OK(bpf_map_freeze(fd), "freeze");
1109 memset(&info, 0, sizeof(info));
1110 info.hash = ptr_to_u64(kbuf);
1111 info.hash_size = sizeof(kbuf);
1112 ilen = sizeof(info);
1113 if (ASSERT_OK(bpf_map_get_info_by_fd(fd, &info, &ilen), "get_hash")) {
1114 libbpf_sha256(val, sizeof(val), lbuf);
1115 ASSERT_EQ(memcmp(kbuf, lbuf, sizeof(kbuf)), 0,
1116 "kernel map hash matches libbpf_sha256");
1117 }
1118 close(fd);
1119 }
1120
map_hash_multi_element(void)1121 static void map_hash_multi_element(void)
1122 {
1123 const __u32 nr = 8, value_size = 64;
1124 __u8 kbuf[SHA256_DIGEST_LENGTH], lbuf[SHA256_DIGEST_LENGTH];
1125 struct bpf_map_info info;
1126 __u32 ilen, i, j;
1127 __u8 *full;
1128 int fd;
1129
1130 /*
1131 * array_map_get_hash() hashes elem_size * max_entries (the whole value
1132 * area), not just element 0. With an 8-aligned value_size elem_size has
1133 * no padding, so pin that a >1-entry array's kernel hash equals
1134 * libbpf_sha256() over the full, concatenated element contents.
1135 */
1136 fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "h", 4, value_size, nr, NULL);
1137 if (!ASSERT_OK_FD(fd, "array_map"))
1138 return;
1139 full = calloc(nr, value_size);
1140 if (!ASSERT_OK_PTR(full, "buf"))
1141 goto close_fd;
1142 for (i = 0; i < nr; i++) {
1143 __u8 *v = full + i * value_size;
1144
1145 for (j = 0; j < value_size; j++)
1146 v[j] = i * 31 + j * 7 + 1;
1147 ASSERT_OK(bpf_map_update_elem(fd, &i, v, 0), "update");
1148 }
1149 ASSERT_OK(bpf_map_freeze(fd), "freeze");
1150 memset(&info, 0, sizeof(info));
1151 info.hash = ptr_to_u64(kbuf);
1152 info.hash_size = sizeof(kbuf);
1153 ilen = sizeof(info);
1154 if (ASSERT_OK(bpf_map_get_info_by_fd(fd, &info, &ilen), "get_hash")) {
1155 libbpf_sha256(full, (size_t)nr * value_size, lbuf);
1156 ASSERT_EQ(memcmp(kbuf, lbuf, sizeof(kbuf)), 0,
1157 "kernel hash covers full multi-element value area");
1158 }
1159 free(full);
1160 close_fd:
1161 close(fd);
1162 }
1163
map_hash_bad_size(void)1164 static void map_hash_bad_size(void)
1165 {
1166 __u8 kbuf[SHA256_DIGEST_LENGTH], val[64] = {};
1167 struct bpf_map_info info;
1168 __u32 ilen, key = 0;
1169 int fd;
1170
1171 fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "h", 4, sizeof(val), 1, NULL);
1172 if (!ASSERT_OK_FD(fd, "array_map"))
1173 return;
1174 ASSERT_OK(bpf_map_update_elem(fd, &key, val, 0), "update");
1175 ASSERT_OK(bpf_map_freeze(fd), "freeze");
1176 memset(&info, 0, sizeof(info));
1177 info.hash = ptr_to_u64(kbuf);
1178 info.hash_size = sizeof(kbuf) / 2;
1179 ilen = sizeof(info);
1180 ASSERT_EQ(bpf_map_get_info_by_fd(fd, &info, &ilen), -EINVAL,
1181 "wrong hash_size rejected");
1182 close(fd);
1183 }
1184
map_hash_unsupported_type(void)1185 static void map_hash_unsupported_type(void)
1186 {
1187 __u8 kbuf[SHA256_DIGEST_LENGTH];
1188 struct bpf_map_info info;
1189 __u32 ilen;
1190 int fd;
1191
1192 /* Only arrays implement map_get_hash; a hash map must be refused. */
1193 fd = bpf_map_create(BPF_MAP_TYPE_HASH, "h", 4, 8, 4, NULL);
1194 if (!ASSERT_OK_FD(fd, "hash_map"))
1195 return;
1196 memset(&info, 0, sizeof(info));
1197 info.hash = ptr_to_u64(kbuf);
1198 info.hash_size = sizeof(kbuf);
1199 ilen = sizeof(info);
1200 ASSERT_EQ(bpf_map_get_info_by_fd(fd, &info, &ilen), -EINVAL,
1201 "hash unsupported for non-array map");
1202 close(fd);
1203 }
1204
setup_meta_map(const struct gen_loader_fixture * f)1205 static int setup_meta_map(const struct gen_loader_fixture *f)
1206 {
1207 LIBBPF_OPTS(bpf_map_create_opts, mopts,
1208 .excl_prog_hash = f->excl,
1209 .excl_prog_hash_size = sizeof(f->excl));
1210 __u32 key = 0;
1211 int fd;
1212
1213 fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "__loader.map", 4,
1214 f->data_sz, 1, &mopts);
1215 if (fd < 0)
1216 return -errno;
1217 if (bpf_map_update_elem(fd, &key, f->blob, 0) || bpf_map_freeze(fd)) {
1218 close(fd);
1219 return -errno;
1220 }
1221 return fd;
1222 }
1223
lsm_signature_verdict(void)1224 static void lsm_signature_verdict(void)
1225 {
1226 char dir_tmpl[] = "/tmp/signed_loader_lsmXXXXXX", *dir = NULL;
1227 struct test_signed_loader_lsm *lsm = NULL;
1228 __u32 sig_sz = 8192, msig_sz = 8192;
1229 int map_fd = -1, prog_fd = -1;
1230 bool have_fixture = false;
1231 struct gen_loader_fixture f;
1232 unsigned char *buf;
1233 __s32 ses_serial;
1234 __u8 sig[8192];
1235
1236 lsm = test_signed_loader_lsm__open_and_load();
1237 if (!ASSERT_OK_PTR(lsm, "lsm_skel_load"))
1238 return;
1239 lsm->bss->monitored_tid = sys_gettid();
1240 if (!ASSERT_OK(test_signed_loader_lsm__attach(lsm), "lsm_attach"))
1241 goto out;
1242
1243 have_fixture = true;
1244 if (gen_loader_fixture_init(&f) != 0)
1245 goto out;
1246
1247 map_fd = setup_meta_map(&f);
1248 if (!ASSERT_OK_FD(map_fd, "meta_map_unsigned"))
1249 goto out;
1250 lsm->bss->seen = 0;
1251 prog_fd = load_loader(f.gopts.insns, f.gopts.insns_sz, map_fd, NULL, 0, 0, 0);
1252 close(map_fd);
1253 map_fd = -1;
1254 if (!ASSERT_OK_FD(prog_fd, "unsigned loader load"))
1255 goto out;
1256 close(prog_fd);
1257 prog_fd = -1;
1258 if (!ASSERT_NEQ(lsm->bss->seen, 0, "bpf LSM in the active LSM set"))
1259 goto out;
1260 ASSERT_EQ(lsm->bss->seen, 1, "unsigned: one observed load");
1261 ASSERT_EQ(lsm->bss->sig_verdict, BPF_SIG_UNSIGNED, "unsigned verdict");
1262 ASSERT_EQ(lsm->bss->sig_keyring_type, BPF_SIG_KEYRING_NONE, "unsigned keyring type");
1263 ASSERT_EQ(lsm->bss->sig_keyring_serial, 0, "unsigned: no keyring serial");
1264
1265 syscall(__NR_request_key, "keyring", "_uid.0", NULL,
1266 KEY_SPEC_SESSION_KEYRING);
1267 dir = mkdtemp(dir_tmpl);
1268 if (!ASSERT_OK_PTR(dir, "mkdtemp"))
1269 goto out;
1270 if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
1271 rmdir(dir);
1272 dir = NULL;
1273 goto out;
1274 }
1275 if (!ASSERT_OK(sign_buf(dir, f.gopts.insns, f.gopts.insns_sz, sig,
1276 &sig_sz), "sign-file"))
1277 goto out;
1278
1279 map_fd = setup_meta_map(&f);
1280 if (!ASSERT_OK_FD(map_fd, "meta_map_signed"))
1281 goto out;
1282 lsm->bss->seen = 0;
1283 prog_fd = load_loader(f.gopts.insns, f.gopts.insns_sz, map_fd, sig,
1284 sig_sz, KEY_SPEC_SESSION_KEYRING, 0);
1285 close(map_fd);
1286 map_fd = -1;
1287 ASSERT_EQ(prog_fd, -EACCES, "unfolded metadata rejected");
1288 if (prog_fd >= 0)
1289 close(prog_fd);
1290 prog_fd = -1;
1291
1292 ses_serial = syscall(__NR_keyctl, KEYCTL_GET_KEYRING_ID,
1293 KEY_SPEC_SESSION_KEYRING, 0);
1294 ASSERT_EQ(lsm->bss->seen, 1, "signed: one observed load");
1295 ASSERT_EQ(lsm->bss->sig_verdict, BPF_SIG_VERIFIED,
1296 "admission saw a valid signature");
1297 ASSERT_EQ(lsm->bss->sig_keyring_type, BPF_SIG_KEYRING_USER, "signed keyring type");
1298 ASSERT_GT(ses_serial, 0, "session keyring serial resolved");
1299 ASSERT_EQ(lsm->bss->sig_keyring_serial, ses_serial,
1300 "signed: validated against session keyring");
1301
1302 buf = malloc((size_t)f.gopts.insns_sz + f.data_sz);
1303 if (!ASSERT_OK_PTR(buf, "meta_signbuf"))
1304 goto out;
1305 memcpy(buf, f.gopts.insns, f.gopts.insns_sz);
1306 memcpy(buf + f.gopts.insns_sz, f.blob, f.data_sz);
1307 if (!ASSERT_OK(sign_buf(dir, buf, f.gopts.insns_sz + f.data_sz,
1308 sig, &msig_sz), "sign insns||metadata")) {
1309 free(buf);
1310 goto out;
1311 }
1312 free(buf);
1313
1314 map_fd = setup_meta_map(&f);
1315 if (!ASSERT_OK_FD(map_fd, "meta_map_bound"))
1316 goto out;
1317 lsm->bss->seen = 0;
1318 prog_fd = load_loader(f.gopts.insns, f.gopts.insns_sz, map_fd, sig,
1319 msig_sz, KEY_SPEC_SESSION_KEYRING, 1);
1320 close(map_fd);
1321 map_fd = -1;
1322 if (!ASSERT_OK_FD(prog_fd, "metadata-bound loader load"))
1323 goto out;
1324 close(prog_fd);
1325 prog_fd = -1;
1326 ASSERT_EQ(lsm->bss->seen, 1, "metadata: one observed load");
1327 ASSERT_EQ(lsm->bss->sig_verdict, BPF_SIG_VERIFIED,
1328 "metadata-bound verdict");
1329 out:
1330 if (map_fd >= 0)
1331 close(map_fd);
1332 if (prog_fd >= 0)
1333 close(prog_fd);
1334 if (have_fixture)
1335 gen_loader_fixture_fini(&f);
1336 if (dir)
1337 run_setup("cleanup", dir);
1338 test_signed_loader_lsm__destroy(lsm);
1339 }
1340
1341 /*
1342 * Load-time metadata verification: the kernel folds the frozen metadata map
1343 * into the signature (insns || metadata) and checks it at BPF_PROG_LOAD via
1344 * fd_array_cnt, rather than the loader checking from within BPF. Sign that
1345 * concatenation, hand the kernel the map, and confirm the signed loader loads,
1346 * runs, and installs its target.
1347 */
loadtime_drive(const char * dir,const void * insns,__u32 insns_sz,const void * data,__u32 data_sz,const __u8 * excl,void * ctx,__u32 ctx_sz,int * load_ret,bool * ran)1348 static int loadtime_drive(const char *dir, const void *insns, __u32 insns_sz,
1349 const void *data, __u32 data_sz, const __u8 *excl,
1350 void *ctx, __u32 ctx_sz, int *load_ret, bool *ran)
1351 {
1352 LIBBPF_OPTS(bpf_map_create_opts, mopts,
1353 .excl_prog_hash = excl,
1354 .excl_prog_hash_size = SHA256_DIGEST_LENGTH);
1355 __u32 sig_sz = 8192, key = 0;
1356 unsigned char *buf = NULL;
1357 int map_fd, prog_fd, ret = 0;
1358 union bpf_attr attr;
1359 __u8 sig[8192];
1360
1361 *ran = false;
1362 *load_ret = 0;
1363
1364 /*
1365 * Metadata map, bound to the loader digest and frozen, exactly as
1366 * skel_internal.h's bpf_load_and_run() sets it up.
1367 */
1368 map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "__loader.map", 4,
1369 data_sz, 1, &mopts);
1370 if (map_fd < 0) {
1371 ret = -errno;
1372 goto out_load;
1373 }
1374 if (bpf_map_update_elem(map_fd, &key, data, 0) || bpf_map_freeze(map_fd)) {
1375 ret = -errno;
1376 goto out_load;
1377 }
1378
1379 /* Sign insns || metadata, the same bytes the kernel reconstructs. */
1380 buf = malloc((size_t)insns_sz + data_sz);
1381 if (!buf) {
1382 ret = -ENOMEM;
1383 goto out_load;
1384 }
1385 memcpy(buf, insns, insns_sz);
1386 memcpy(buf + insns_sz, data, data_sz);
1387 ret = sign_buf(dir, buf, insns_sz + data_sz, sig, &sig_sz);
1388 if (ret)
1389 goto out_load;
1390
1391 memset(&attr, 0, sizeof(attr));
1392 attr.prog_type = BPF_PROG_TYPE_SYSCALL;
1393 attr.insns = ptr_to_u64(insns);
1394 attr.insn_cnt = insns_sz / sizeof(struct bpf_insn);
1395 attr.license = ptr_to_u64("Dual BSD/GPL");
1396 attr.prog_flags = BPF_F_SLEEPABLE;
1397 attr.fd_array = ptr_to_u64(&map_fd);
1398 attr.signature = ptr_to_u64(sig);
1399 attr.signature_size = sig_sz;
1400 attr.keyring_id = KEY_SPEC_SESSION_KEYRING;
1401 attr.fd_array_cnt = 1;
1402 memcpy(attr.prog_name, "__loader.prog", sizeof("__loader.prog"));
1403 prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
1404 offsetofend(union bpf_attr, keyring_id));
1405 if (prog_fd < 0) {
1406 ret = -errno;
1407 goto out_load;
1408 }
1409
1410 memset(&attr, 0, sizeof(attr));
1411 attr.test.prog_fd = prog_fd;
1412 attr.test.ctx_in = ptr_to_u64(ctx);
1413 attr.test.ctx_size_in = ctx_sz;
1414 if (syscall(__NR_bpf, BPF_PROG_RUN, &attr,
1415 offsetofend(union bpf_attr, test)) < 0) {
1416 ret = -errno;
1417 goto out_prog;
1418 }
1419 *ran = true;
1420 ret = (int)attr.test.retval;
1421 out_prog:
1422 close(prog_fd);
1423 goto out_map;
1424 out_load:
1425 *load_ret = ret;
1426 out_map:
1427 free(buf);
1428 if (map_fd >= 0)
1429 close(map_fd);
1430 return ret;
1431 }
1432
loadtime_verify(struct bpf_object * obj,int expect_maps)1433 static void loadtime_verify(struct bpf_object *obj, int expect_maps)
1434 {
1435 LIBBPF_OPTS(gen_loader_opts, gopts, .gen_hash = true);
1436 char dir_tmpl[] = "/tmp/signed_loader_ltXXXXXX", *dir = NULL;
1437 int nr_maps = 0, nr_progs = 0, load_ret = 0, r;
1438 __u8 excl[SHA256_DIGEST_LENGTH];
1439 struct bpf_prog_desc *pd;
1440 struct bpf_map_desc *md;
1441 unsigned char *blob = NULL;
1442 struct bpf_program *p;
1443 struct bpf_map *m;
1444 __u32 ctx_sz, data_sz;
1445 void *ctx = NULL;
1446 bool ran = false;
1447
1448 syscall(__NR_request_key, "keyring", "_uid.0", NULL,
1449 KEY_SPEC_SESSION_KEYRING);
1450 dir = mkdtemp(dir_tmpl);
1451 if (!ASSERT_OK_PTR(dir, "mkdtemp"))
1452 return;
1453 if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
1454 rmdir(dir);
1455 return;
1456 }
1457
1458 if (!ASSERT_OK(bpf_object__gen_loader(obj, &gopts), "gen_loader"))
1459 goto out;
1460 if (!ASSERT_OK(bpf_object__load(obj), "gen_load"))
1461 goto out;
1462
1463 bpf_object__for_each_program(p, obj)
1464 nr_progs++;
1465 bpf_object__for_each_map(m, obj)
1466 nr_maps++;
1467 if (!ASSERT_EQ(nr_maps, expect_maps, "fixture map count"))
1468 goto out;
1469
1470 ctx_sz = sizeof(struct bpf_loader_ctx) +
1471 nr_maps * sizeof(struct bpf_map_desc) +
1472 nr_progs * sizeof(struct bpf_prog_desc);
1473 ctx = calloc(1, ctx_sz);
1474 if (!ASSERT_OK_PTR(ctx, "ctx_alloc"))
1475 goto out;
1476 ((struct bpf_loader_ctx *)ctx)->sz = ctx_sz;
1477
1478 data_sz = gopts.data_sz;
1479 blob = malloc(data_sz);
1480 if (!ASSERT_OK_PTR(blob, "blob_alloc"))
1481 goto out;
1482 memcpy(blob, gopts.data, data_sz);
1483
1484 /* excl_prog_hash = SHA256(loader insns) == the loader's prog->digest. */
1485 libbpf_sha256(gopts.insns, gopts.insns_sz, excl);
1486
1487 r = loadtime_drive(dir, gopts.insns, gopts.insns_sz, blob, data_sz,
1488 excl, ctx, ctx_sz, &load_ret, &ran);
1489 ASSERT_OK(load_ret, "signed loader loaded (insns || metadata)");
1490 ASSERT_TRUE(ran, "loader ran");
1491 ASSERT_EQ(r, 0, "loader installed its target");
1492
1493 md = (struct bpf_map_desc *)((char *)ctx + sizeof(struct bpf_loader_ctx));
1494 pd = (struct bpf_prog_desc *)(md + nr_maps);
1495 ASSERT_GT(pd[0].prog_fd, 0, "target program installed");
1496 if (nr_maps)
1497 ASSERT_GT(md[0].map_fd, 0, "target map installed");
1498
1499 close_loader_ctx_fds(ctx, nr_maps, nr_progs);
1500 out:
1501 free(blob);
1502 free(ctx);
1503 if (dir)
1504 run_setup("cleanup", dir);
1505 }
1506
loadtime_no_map(void)1507 static void loadtime_no_map(void)
1508 {
1509 struct test_signed_loader *skel = test_signed_loader__open();
1510
1511 if (!ASSERT_OK_PTR(skel, "skel_open"))
1512 return;
1513 loadtime_verify(skel->obj, 0);
1514 test_signed_loader__destroy(skel);
1515 }
1516
loadtime_with_map(void)1517 static void loadtime_with_map(void)
1518 {
1519 struct test_signed_loader_map *skel = test_signed_loader_map__open();
1520
1521 if (!ASSERT_OK_PTR(skel, "skel_open"))
1522 return;
1523 loadtime_verify(skel->obj, 1);
1524 test_signed_loader_map__destroy(skel);
1525 }
1526
1527 /*
1528 * A signed program need not bind any map. A plain BPF_PROG_TYPE_SYSCALL
1529 * program with no fd_array is signed over its instructions alone: the kernel
1530 * verifies the signature, folds no metadata, and the program loads. Exercise
1531 * the fd_array == NULL / fd_array_cnt == 0 path, and confirm the signature
1532 * still authenticates the instructions (a tampered copy is rejected).
1533 */
signed_no_fd_array(void)1534 static void signed_no_fd_array(void)
1535 {
1536 struct bpf_insn insns[] = {
1537 BPF_MOV64_IMM(BPF_REG_0, 0),
1538 BPF_EXIT_INSN(),
1539 };
1540 char dir_tmpl[] = "/tmp/signed_loaderXXXXXX", *dir;
1541 __u32 sig_sz = 8192;
1542 union bpf_attr attr;
1543 __u8 sig[8192];
1544 int prog_fd, err;
1545
1546 syscall(__NR_request_key, "keyring", "_uid.0", NULL,
1547 KEY_SPEC_SESSION_KEYRING);
1548 dir = mkdtemp(dir_tmpl);
1549 if (!ASSERT_OK_PTR(dir, "mkdtemp"))
1550 return;
1551 if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
1552 rmdir(dir);
1553 return;
1554 }
1555
1556 /* No metadata map: the signed payload is the instructions alone. */
1557 if (!ASSERT_OK(sign_buf(dir, insns, sizeof(insns), sig, &sig_sz),
1558 "sign-file"))
1559 goto cleanup;
1560
1561 memset(&attr, 0, sizeof(attr));
1562 attr.prog_type = BPF_PROG_TYPE_SYSCALL;
1563 attr.insns = ptr_to_u64(insns);
1564 attr.insn_cnt = ARRAY_SIZE(insns);
1565 attr.license = ptr_to_u64("Dual BSD/GPL");
1566 attr.prog_flags = BPF_F_SLEEPABLE;
1567 attr.signature = ptr_to_u64(sig);
1568 attr.signature_size = sig_sz;
1569 attr.keyring_id = KEY_SPEC_SESSION_KEYRING;
1570 /* fd_array and fd_array_cnt deliberately left NULL/0. */
1571 memcpy(attr.prog_name, "signed_nomap", sizeof("signed_nomap"));
1572
1573 prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
1574 offsetofend(union bpf_attr, keyring_id));
1575 if (!ASSERT_GE(prog_fd, 0, "map-less signed program loaded")) {
1576 if (prog_fd >= 0)
1577 close(prog_fd);
1578 goto cleanup;
1579 }
1580 close(prog_fd);
1581
1582 /* The signature covers the instructions, so tampering must be rejected. */
1583 insns[0].imm = 1;
1584 prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
1585 offsetofend(union bpf_attr, keyring_id));
1586 err = prog_fd < 0 ? -errno : prog_fd;
1587 ASSERT_EQ(err, -EKEYREJECTED, "tampered map-less program rejected");
1588 if (prog_fd >= 0)
1589 close(prog_fd);
1590 cleanup:
1591 run_setup("cleanup", dir);
1592 }
1593
1594 /*
1595 * A signed program may reach maps only through fd_array indices, so the kernel
1596 * folds (and thus attests) them. A direct BPF_PSEUDO_MAP_FD reference - a raw,
1597 * unfolded fd baked into the signed instructions - is rejected by the verifier.
1598 */
signed_map_by_fd_rejected(void)1599 static void signed_map_by_fd_rejected(void)
1600 {
1601 struct bpf_insn insns[] = {
1602 BPF_LD_MAP_FD(BPF_REG_1, 0),
1603 BPF_MOV64_IMM(BPF_REG_0, 0),
1604 BPF_EXIT_INSN(),
1605 };
1606 char dir_tmpl[] = "/tmp/signed_loaderXXXXXX", *dir;
1607 __u32 sig_sz = 8192;
1608 union bpf_attr attr;
1609 __u8 sig[8192];
1610 int map_fd, prog_fd, err;
1611
1612 map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "sig_mapfd", 4, 4, 1, NULL);
1613 if (!ASSERT_GE(map_fd, 0, "map_create"))
1614 return;
1615 insns[0].imm = map_fd; /* bake the raw map fd into the ld_imm64 */
1616
1617 syscall(__NR_request_key, "keyring", "_uid.0", NULL,
1618 KEY_SPEC_SESSION_KEYRING);
1619 dir = mkdtemp(dir_tmpl);
1620 if (!ASSERT_OK_PTR(dir, "mkdtemp"))
1621 goto out_map;
1622 if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
1623 rmdir(dir);
1624 goto out_map;
1625 }
1626
1627 /* Sign the instructions, raw map fd and all. */
1628 if (!ASSERT_OK(sign_buf(dir, insns, sizeof(insns), sig, &sig_sz),
1629 "sign-file"))
1630 goto cleanup;
1631
1632 memset(&attr, 0, sizeof(attr));
1633 attr.prog_type = BPF_PROG_TYPE_SYSCALL;
1634 attr.insns = ptr_to_u64(insns);
1635 attr.insn_cnt = ARRAY_SIZE(insns);
1636 attr.license = ptr_to_u64("Dual BSD/GPL");
1637 attr.prog_flags = BPF_F_SLEEPABLE;
1638 attr.signature = ptr_to_u64(sig);
1639 attr.signature_size = sig_sz;
1640 attr.keyring_id = KEY_SPEC_SESSION_KEYRING;
1641 /* No fd_array: the map is reached by a raw fd in the instructions. */
1642 memcpy(attr.prog_name, "signed_mapfd", sizeof("signed_mapfd"));
1643
1644 prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
1645 offsetofend(union bpf_attr, keyring_id));
1646 err = prog_fd < 0 ? -errno : prog_fd;
1647 ASSERT_EQ(err, -EINVAL, "signed program referencing a map by fd rejected");
1648 if (prog_fd >= 0)
1649 close(prog_fd);
1650 cleanup:
1651 run_setup("cleanup", dir);
1652 out_map:
1653 close(map_fd);
1654 }
1655
1656 /*
1657 * A signed program may reach maps only through the continuous fd_array, so the
1658 * kernel folds (and thus attests) them. Referencing a map by fd_array *index*
1659 * while leaving fd_array_cnt at 0 selects the sparse path, which resolves a map
1660 * the signature never covered; the verifier rejects it up front with -EACCES.
1661 */
signed_sparse_fd_array_rejected(void)1662 static void signed_sparse_fd_array_rejected(void)
1663 {
1664 struct bpf_insn insns[] = {
1665 BPF_LD_IMM64_RAW(BPF_REG_1, BPF_PSEUDO_MAP_IDX, 0),
1666 BPF_MOV64_IMM(BPF_REG_0, 0),
1667 BPF_EXIT_INSN(),
1668 };
1669 char dir_tmpl[] = "/tmp/signed_loader_spXXXXXX", *dir;
1670 __u32 sig_sz = 8192;
1671 union bpf_attr attr;
1672 __u8 sig[8192];
1673 int map_fd, prog_fd, err;
1674
1675 map_fd = bpf_map_create(BPF_MAP_TYPE_ARRAY, "sig_sparse", 4, 4, 1, NULL);
1676 if (!ASSERT_GE(map_fd, 0, "map_create"))
1677 return;
1678
1679 syscall(__NR_request_key, "keyring", "_uid.0", NULL,
1680 KEY_SPEC_SESSION_KEYRING);
1681 dir = mkdtemp(dir_tmpl);
1682 if (!ASSERT_OK_PTR(dir, "mkdtemp"))
1683 goto out_map;
1684 if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
1685 rmdir(dir);
1686 goto out_map;
1687 }
1688
1689 /* Sign the instructions alone; the sparse map is not folded. */
1690 if (!ASSERT_OK(sign_buf(dir, insns, sizeof(insns), sig, &sig_sz),
1691 "sign-file"))
1692 goto cleanup;
1693
1694 memset(&attr, 0, sizeof(attr));
1695 attr.prog_type = BPF_PROG_TYPE_SYSCALL;
1696 attr.insns = ptr_to_u64(insns);
1697 attr.insn_cnt = ARRAY_SIZE(insns);
1698 attr.license = ptr_to_u64("Dual BSD/GPL");
1699 attr.prog_flags = BPF_F_SLEEPABLE;
1700 attr.fd_array = ptr_to_u64(&map_fd);
1701 attr.fd_array_cnt = 0; /* sparse: force lazy map resolution */
1702 attr.signature = ptr_to_u64(sig);
1703 attr.signature_size = sig_sz;
1704 attr.keyring_id = KEY_SPEC_SESSION_KEYRING;
1705 memcpy(attr.prog_name, "signed_sparse", sizeof("signed_sparse"));
1706
1707 prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
1708 offsetofend(union bpf_attr, keyring_id));
1709 err = prog_fd < 0 ? -errno : prog_fd;
1710 ASSERT_EQ(err, -EACCES, "signed program binding a sparse fd_array map rejected");
1711 if (prog_fd >= 0)
1712 close(prog_fd);
1713 cleanup:
1714 run_setup("cleanup", dir);
1715 out_map:
1716 close(map_fd);
1717 }
1718
signed_module_kfunc_rejected(void)1719 static void signed_module_kfunc_rejected(void)
1720 {
1721 struct bpf_insn insns[] = {
1722 BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, BPF_PSEUDO_KFUNC_CALL, 1, 1),
1723 BPF_MOV64_IMM(BPF_REG_0, 0),
1724 BPF_EXIT_INSN(),
1725 };
1726 char dir_tmpl[] = "/tmp/signed_loader_kfnXXXXXX", *dir;
1727 int prog_fd, err, fds[2];
1728 struct btf *btf = NULL;
1729 __u32 sig_sz = 8192;
1730 union bpf_attr attr;
1731 __u8 sig[8192];
1732
1733 syscall(__NR_request_key, "keyring", "_uid.0", NULL,
1734 KEY_SPEC_SESSION_KEYRING);
1735 dir = mkdtemp(dir_tmpl);
1736 if (!ASSERT_OK_PTR(dir, "mkdtemp"))
1737 return;
1738 if (!ASSERT_OK(run_setup("setup", dir), "verify_sig_setup")) {
1739 rmdir(dir);
1740 return;
1741 }
1742 if (!ASSERT_OK(sign_buf(dir, insns, sizeof(insns), sig, &sig_sz),
1743 "sign-file"))
1744 goto cleanup;
1745 btf = btf__new_empty();
1746 if (!ASSERT_OK_PTR(btf, "btf_new_empty"))
1747 goto cleanup;
1748 btf__add_int(btf, "int", 4, BTF_INT_SIGNED);
1749 if (!ASSERT_OK(btf__load_into_kernel(btf), "btf_load"))
1750 goto cleanup;
1751 fds[0] = -1;
1752 fds[1] = btf__fd(btf);
1753
1754 memset(&attr, 0, sizeof(attr));
1755 attr.prog_type = BPF_PROG_TYPE_SYSCALL;
1756 attr.insns = ptr_to_u64(insns);
1757 attr.insn_cnt = ARRAY_SIZE(insns);
1758 attr.license = ptr_to_u64("Dual BSD/GPL");
1759 attr.prog_flags = BPF_F_SLEEPABLE;
1760 attr.fd_array = ptr_to_u64(fds);
1761 attr.fd_array_cnt = 0; /* sparse: force lazy kfunc BTF resolution */
1762 attr.signature = ptr_to_u64(sig);
1763 attr.signature_size = sig_sz;
1764 attr.keyring_id = KEY_SPEC_SESSION_KEYRING;
1765 memcpy(attr.prog_name, "signed_kfunc", sizeof("signed_kfunc"));
1766
1767 prog_fd = syscall(__NR_bpf, BPF_PROG_LOAD, &attr,
1768 offsetofend(union bpf_attr, keyring_id));
1769 err = prog_fd < 0 ? -errno : prog_fd;
1770 if (prog_fd >= 0)
1771 close(prog_fd);
1772
1773 ASSERT_EQ(err, -EACCES, "module kfunc BTF in signed program rejected");
1774 cleanup:
1775 if (btf)
1776 btf__free(btf);
1777 run_setup("cleanup", dir);
1778 }
1779
test_signed_loader(void)1780 void test_signed_loader(void)
1781 {
1782 if (test__start_subtest("loadtime_no_map"))
1783 loadtime_no_map();
1784 if (test__start_subtest("loadtime_with_map"))
1785 loadtime_with_map();
1786 if (test__start_subtest("metadata_match"))
1787 metadata_match();
1788 if (test__start_subtest("signature_enforced"))
1789 signature_enforced();
1790 if (test__start_subtest("signed_nonexcl_fd_array_rejected"))
1791 signed_nonexcl_fd_array_rejected();
1792 if (test__start_subtest("signed_unfrozen_fd_array_rejected"))
1793 signed_unfrozen_fd_array_rejected();
1794 if (test__start_subtest("signed_nonarray_fd_array_rejected"))
1795 signed_nonarray_fd_array_rejected();
1796 if (test__start_subtest("signed_btf_fd_array_rejected"))
1797 signed_btf_fd_array_rejected();
1798 if (test__start_subtest("signed_module_kfunc_rejected"))
1799 signed_module_kfunc_rejected();
1800 if (test__start_subtest("signature_failure_logs"))
1801 signature_failure_logs();
1802 if (test__start_subtest("signature_too_large"))
1803 signature_too_large();
1804 if (test__start_subtest("signature_zero_size"))
1805 signature_zero_size();
1806 if (test__start_subtest("signature_bad_keyring"))
1807 signature_bad_keyring();
1808 if (test__start_subtest("metadata_ctx_max_entries_ignored"))
1809 metadata_ctx_max_entries_ignored();
1810 if (test__start_subtest("metadata_ctx_initial_value_ignored"))
1811 metadata_ctx_initial_value_ignored();
1812 if (test__start_subtest("signature_authenticates_insns"))
1813 signature_authenticates_insns();
1814 if (test__start_subtest("signature_authenticates_metadata"))
1815 signature_authenticates_metadata();
1816 if (test__start_subtest("hash_requires_frozen"))
1817 hash_requires_frozen();
1818 if (test__start_subtest("no_update_after_freeze"))
1819 no_update_after_freeze();
1820 if (test__start_subtest("freeze_writable_mmap"))
1821 freeze_writable_mmap();
1822 if (test__start_subtest("no_writable_mmap_frozen"))
1823 no_writable_mmap_frozen();
1824 if (test__start_subtest("map_hash_matches_libbpf"))
1825 map_hash_matches_libbpf();
1826 if (test__start_subtest("map_hash_multi_element"))
1827 map_hash_multi_element();
1828 if (test__start_subtest("map_hash_bad_size"))
1829 map_hash_bad_size();
1830 if (test__start_subtest("map_hash_unsupported_type"))
1831 map_hash_unsupported_type();
1832 if (test__start_subtest("lsm_signature_verdict"))
1833 lsm_signature_verdict();
1834 if (test__start_subtest("signed_no_fd_array"))
1835 signed_no_fd_array();
1836 if (test__start_subtest("signed_map_by_fd_rejected"))
1837 signed_map_by_fd_rejected();
1838 if (test__start_subtest("signed_sparse_fd_array_rejected"))
1839 signed_sparse_fd_array_rejected();
1840 }
1841