projects/linux·blob master · refs

638 lines · 18.5 KiB

1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Selftest for binfmt_misc bpf-backed ('B') handlers.
4 *
5 * A handler is a struct binfmt_misc_ops struct_ops map with a sleepable match
6 * and a sleepable load program. Attaching it publishes it by name in the
7 * caller's user namespace; a 'B' entry referencing it by name in the
8 * interpreter field activates it:
9 *
10 * echo ':name:B::::<handler>:' > /proc/sys/fs/binfmt_misc/register
11 *
12 * Five self-contained cases are exercised:
13 *
14 * 1. bpf_interp: the match program matches a synthetic aarch64 ELF header
15 * from the prefetched bprm->buf and the load program routes it to a
16 * fixed interpreter of its choosing.
17 * 2. nix_origin: the match program reads the binary's program headers to
18 * commit only to a "$ORIGIN/..."-relative PT_INTERP and the load program
19 * resolves it to an interpreter co-located with the binary (the
20 * relocatable-loader case the kernel ELF loader cannot express).
21 * 3. transparent: the load program sets BPF_BINPRM_TRANSPARENT; the
22 * asserting interpreter (binfmt_transparent_interp) verifies the
23 * identity the kernel constructed (exe link, argv, cmdline, comm,
24 * AT_EXECFD, write denial) from inside the process.
25 * 4. loader: the load program sets BPF_BINPRM_LOADER; the payload
26 * (binfmt_loader_payload) runs as the main image with the selected
27 * interpreter substituted for its PT_INTERP and asserts the native
28 * identity from inside.
29 * 5. interp_bind: an entry registered disabled with 'D' is given its
30 * interpreters one write at a time, and the load program picks one by
31 * name per exec. Replacing what the path holds afterwards changes
32 * nothing, which is the point of binding a file rather than resolving
33 * a name at exec time. Enabling the entry seals it.
34 *
35 * The first two route to a test interpreter that prints BPF_INTERP_RAN,
36 * proving the program's chosen interpreter actually ran.
37 */
38#define _GNU_SOURCE
39#include <elf.h>
40#include <limits.h>
41#include <sched.h>
42#include <stdio.h>
43#include <stdlib.h>
44#include <string.h>
45#include <unistd.h>
46#include <fcntl.h>
47
48#include <bpf/btf.h>
49#include <bpf/libbpf.h>
50
51#include "binfmt_misc_common.h"
52#include "kselftest_harness.h"
53
54#define INTERP_PATH "/tmp/binfmt_bpf_interp"
55#define AARCH64_PATH "/tmp/binfmt_bpf_aarch64"
56#define RELOC_TEMPLATE "/tmp/binfmt_relocXXXXXX"
57#define TRANS_INTERP "/tmp/binfmt_transparent_interp"
58#define TRANS_PATH "/tmp/binfmt_bpf_riscv"
59#define EXPECT "BPF_INTERP_RAN"
60#define TRANS_EXPECT "TRANSPARENT_OK"
61#define LOADER_INTERP "/tmp/binfmt_loader_interp"
62#define LOADER_PATH "/tmp/binfmt_bpf_loader.ldrtest"
63#define BIND_FIRST "/tmp/binfmt_bind_first"
64#define BIND_SECOND "/tmp/binfmt_bind_second"
65#define BIND_ARM_PATH "/tmp/binfmt_bind_arm"
66#define BIND_RISCV_PATH "/tmp/binfmt_bind_riscv"
67#define BIND_EXPECT "BIND_RAN "
68#define BIND_MAX 100
69#define INTERP_LIMIT "/proc/sys/user/max_binfmt_misc_interpreters"
70/* Exit status of the binding child when it cannot set up a budget of its own. */
71#define BIND_NO_BUDGET 200
72
73/* A minimal 64-bit little-endian ELF header, padded to the read size. */
74static int create_fake_elf(const char *path, unsigned short machine)
75{
76 unsigned char hdr[256] = {0};
77 int fd;
78
79 hdr[0] = 0x7f; hdr[1] = 'E'; hdr[2] = 'L'; hdr[3] = 'F';
80 hdr[4] = ELFCLASS64;
81 hdr[5] = ELFDATA2LSB;
82 hdr[6] = EV_CURRENT;
83 hdr[16] = ET_EXEC;
84 hdr[18] = machine & 0xff; /* e_machine, little-endian */
85 hdr[19] = machine >> 8;
86 hdr[20] = EV_CURRENT;
87
88 unlink(path);
89 fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0755);
90 if (fd < 0)
91 return -1;
92 if (write(fd, hdr, sizeof(hdr)) != (ssize_t)sizeof(hdr)) {
93 close(fd);
94 return -1;
95 }
96 close(fd);
97 return 0;
98}
99
100/*
101 * Register a 'B' entry for @handler. With @flags "D" the entry is created
102 * disabled, which is what leaves it open to being given interpreters.
103 */
104static int register_entry(const char *name, const char *handler,
105 const char *flags)
106{
107 char rule[PATH_MAX];
108
109 snprintf(rule, sizeof(rule), ":%s:B::::%s:%s", name, handler,
110 flags ? flags : "");
111 return write_reg(rule);
112}
113
114static int check_output(const char *cmd, const char *expected)
115{
116 char buf[128];
117 FILE *fp;
118
119 fp = popen(cmd, "r");
120 if (!fp)
121 return -1;
122 if (!fgets(buf, sizeof(buf), fp)) {
123 pclose(fp);
124 return -1;
125 }
126 pclose(fp);
127 return strncmp(buf, expected, strlen(expected)) ? -1 : 0;
128}
129
130/* Does the kernel BTF know struct binfmt_misc_ops (CONFIG_BINFMT_MISC_BPF)? */
131static bool have_binfmt_misc_ops(void)
132{
133 struct btf *btf = btf__load_vmlinux_btf();
134 bool have;
135
136 have = btf && btf__find_by_name_kind(btf, "binfmt_misc_ops",
137 BTF_KIND_STRUCT) >= 0;
138 btf__free(btf);
139 return have;
140}
141
142/* The reason bpf handler cases cannot run here, NULL if they can. */
143static const char *bpf_handler_unsupported(void)
144{
145 if (getuid() != 0)
146 return "test must be run as root";
147 if (!have_binfmt_misc_ops())
148 return "no struct binfmt_misc_ops in the kernel BTF (CONFIG_BINFMT_MISC_BPF)";
149 if (!binfmt_misc_available())
150 return "no binfmt_misc";
151 return NULL;
152}
153
154/* An attached handler with its 'B' entry activated. */
155struct bpf_case {
156 struct bpf_object *obj;
157 struct bpf_link *link;
158 const char *entry;
159};
160
161/*
162 * Load @objfile, attach its struct_ops map @handler (which publishes the
163 * handler) and register a 'B' entry named @entry that references it, with
164 * @flags as the entry's register-string flags.
165 */
166static int bpf_case_start_flags(struct bpf_case *c, const char *objfile,
167 const char *handler, const char *entry,
168 const char *flags)
169{
170 struct bpf_map *map;
171
172 c->obj = NULL;
173 c->link = NULL;
174 c->entry = entry;
175
176 c->obj = bpf_object__open_file(objfile, NULL);
177 if (!c->obj || libbpf_get_error(c->obj)) {
178 fprintf(stderr, "open %s failed\n", objfile);
179 c->obj = NULL;
180 return -1;
181 }
182 if (bpf_object__load(c->obj)) {
183 fprintf(stderr, "load %s failed (check dmesg for the verifier log)\n",
184 objfile);
185 goto fail;
186 }
187 map = bpf_object__find_map_by_name(c->obj, handler);
188 if (!map) {
189 fprintf(stderr, "no struct_ops map '%s' in %s\n", handler, objfile);
190 goto fail;
191 }
192 c->link = bpf_map__attach_struct_ops(map);
193 if (!c->link || libbpf_get_error(c->link)) {
194 fprintf(stderr, "attach struct_ops '%s' failed\n", handler);
195 c->link = NULL;
196 goto fail;
197 }
198 if (register_entry(entry, handler, flags)) {
199 fprintf(stderr, "register 'B' entry '%s' failed\n", entry);
200 goto fail;
201 }
202 return 0;
203
204fail:
205 bpf_link__destroy(c->link);
206 bpf_object__close(c->obj);
207 c->obj = NULL;
208 c->link = NULL;
209 return -1;
210}
211
212static int bpf_case_start(struct bpf_case *c, const char *objfile,
213 const char *handler, const char *entry)
214{
215 return bpf_case_start_flags(c, objfile, handler, entry, NULL);
216}
217
218static void bpf_case_stop(struct bpf_case *c)
219{
220 unregister(c->entry);
221 bpf_link__destroy(c->link);
222 bpf_object__close(c->obj);
223}
224
225/* Activate @handler, run @target and check it produced @expect. */
226static int run_case(const char *objfile, const char *handler,
227 const char *entry, const char *target, const char *expect)
228{
229 struct bpf_case c;
230 int ret;
231
232 if (bpf_case_start(&c, objfile, handler, entry))
233 return -1;
234 ret = check_output(target, expect);
235 bpf_case_stop(&c);
236 return ret;
237}
238
239FIXTURE(bpf_handler) {
240 char obj[PATH_MAX]; /* struct_ops object of the case under test */
241};
242
243FIXTURE_SETUP(bpf_handler)
244{
245 char src[PATH_MAX];
246 const char *why = bpf_handler_unsupported();
247
248 if (why)
249 SKIP(return, "%s", why);
250
251 /* Shared test interpreter. */
252 ASSERT_EQ(artifact_path(src, sizeof(src), "binfmt_bpf_interp"), 0);
253 ASSERT_EQ(copy_file(src, INTERP_PATH), 0);
254}
255
256FIXTURE_TEARDOWN(bpf_handler)
257{
258 unlink(INTERP_PATH);
259}
260
261/* The match program matches a synthetic header, the load program routes it. */
262TEST_F(bpf_handler, fixed_interpreter)
263{
264 ASSERT_EQ(create_fake_elf(AARCH64_PATH, EM_AARCH64), 0);
265 ASSERT_EQ(artifact_path(self->obj, sizeof(self->obj),
266 "bpf_interp.bpf.o"), 0);
267 EXPECT_EQ(run_case(self->obj, "bpf_interp", "test_bpf_interp",
268 AARCH64_PATH, EXPECT), 0);
269 unlink(AARCH64_PATH);
270}
271
272/* A "$ORIGIN/..." PT_INTERP resolved to an interpreter next to the binary. */
273TEST_F(bpf_handler, origin_relative_interpreter)
274{
275 char src[PATH_MAX], app[PATH_MAX], interp[PATH_MAX];
276 char dir[] = RELOC_TEMPLATE;
277
278 ASSERT_NE(mkdtemp(dir), NULL);
279 snprintf(app, sizeof(app), "%s/app", dir);
280 snprintf(interp, sizeof(interp), "%s/binfmt_bpf_interp", dir);
281 ASSERT_EQ(artifact_path(src, sizeof(src), "binfmt_bpf_app"), 0);
282 ASSERT_EQ(copy_file(src, app), 0);
283 ASSERT_EQ(copy_file(INTERP_PATH, interp), 0);
284
285 ASSERT_EQ(artifact_path(self->obj, sizeof(self->obj),
286 "nix_origin.bpf.o"), 0);
287 EXPECT_EQ(run_case(self->obj, "nix_origin", "test_bpf_origin",
288 app, EXPECT), 0);
289
290 unlink(app);
291 unlink(interp);
292 rmdir(dir);
293}
294
295/* A transparent dispatch: the process presents as the binary, not the interp. */
296TEST_F(bpf_handler, transparent_dispatch)
297{
298 char src[PATH_MAX], cmd[PATH_MAX + 16];
299
300 /* Probe for transparent-mode support via its static counterpart. */
301 if (!binfmt_flag_supported('T'))
302 SKIP(return, "kernel without transparent mode");
303
304 ASSERT_EQ(artifact_path(src, sizeof(src), "binfmt_transparent_interp"), 0);
305 ASSERT_EQ(copy_file(src, TRANS_INTERP), 0);
306 ASSERT_EQ(create_fake_elf(TRANS_PATH, EM_RISCV), 0);
307
308 setenv("BINFMT_TEST_BINARY", TRANS_PATH, 1);
309 snprintf(cmd, sizeof(cmd), "%s argone argtwo", TRANS_PATH);
310 ASSERT_EQ(artifact_path(self->obj, sizeof(self->obj),
311 "transparent.bpf.o"), 0);
312 EXPECT_EQ(run_case(self->obj, "transparent", "test_bpf_transparent",
313 cmd, TRANS_EXPECT), 0);
314
315 unlink(TRANS_PATH);
316 unlink(TRANS_INTERP);
317}
318
319/* A per-exec loader substitution: the payload runs as a native exec. */
320TEST_F(bpf_handler, loader_substitution)
321{
322 char src[PATH_MAX], loader[PATH_MAX];
323 struct bpf_case c;
324 int status;
325
326 if (find_loader(loader, sizeof(loader)))
327 SKIP(return, "cannot determine own PT_INTERP");
328
329 ASSERT_EQ(copy_file(loader, LOADER_INTERP), 0);
330 ASSERT_EQ(artifact_path(src, sizeof(src), "binfmt_loader_payload"), 0);
331 ASSERT_EQ(copy_file(src, LOADER_PATH), 0);
332 ASSERT_EQ(patch_file(LOADER_PATH, EI_PAD, LOADER_MARKER,
333 strlen(LOADER_MARKER)), 0);
334 ASSERT_EQ(artifact_path(self->obj, sizeof(self->obj),
335 "loader.bpf.o"), 0);
336
337 setenv("BINFMT_TEST_BINARY", LOADER_PATH, 1);
338 setenv("BINFMT_TEST_INTERP", LOADER_INTERP, 1);
339
340 ASSERT_EQ(bpf_case_start(&c, self->obj, "loader", "test_bpf_loader"), 0);
341 status = run_payload(LOADER_PATH);
342 bpf_case_stop(&c);
343 EXPECT_EQ(status, 0);
344
345 unsetenv("BINFMT_TEST_INTERP");
346 unlink(LOADER_PATH);
347 unlink(LOADER_INTERP);
348}
349
350/* The errno an exec of @path fails with, 0 if it succeeded. */
351static int exec_errno(const char *path)
352{
353 int status;
354 pid_t pid;
355
356 pid = fork();
357 if (pid == 0) {
358 execl(path, path, (char *)NULL);
359 _exit(errno);
360 }
361 if (pid < 0 || waitpid(pid, &status, 0) != pid || !WIFEXITED(status))
362 return -1;
363 return WEXITSTATUS(status);
364}
365
366/* Install a copy of the bound-interpreter test binary at @path. */
367static int install_interp(const char *path)
368{
369 char src[PATH_MAX];
370
371 if (artifact_path(src, sizeof(src), "binfmt_bind_interp"))
372 return -1;
373 return copy_file(src, path);
374}
375
376/* Bind @path to @entry under @name, the '+' command of a disabled entry. */
377static int entry_bind(const char *entry, const char *name, const char *path)
378{
379 char cmd[PATH_MAX];
380
381 snprintf(cmd, sizeof(cmd), "+%s %s\n", name, path);
382 return entry_command(entry, cmd);
383}
384
385/* Set the interpreter budget of this namespace. */
386static int write_interp_limit(const char *val)
387{
388 ssize_t n;
389 int fd;
390
391 fd = open(INTERP_LIMIT, O_WRONLY | O_CLOEXEC);
392 if (fd < 0)
393 return -1;
394 n = write(fd, val, strlen(val));
395 close(fd);
396 return n < 0 ? -1 : 0;
397}
398
399/*
400 * The errno a bind is refused with when the writer is a child that has spent
401 * the budget of a user namespace of its own, 0 if it succeeded and -1 if the
402 * child could not set itself up. The fd is opened here and inherited, so the
403 * interpreter is still opened with this process's credentials.
404 */
405static int bind_out_of_budget(const char *entry, const char *name,
406 const char *path)
407{
408 char cmd[PATH_MAX], file[PATH_MAX];
409 int fd, status, retval;
410 pid_t pid;
411
412 snprintf(file, sizeof(file), BINFMT_DIR "/%s", entry);
413 snprintf(cmd, sizeof(cmd), "+%s %s\n", name, path);
414
415 fd = open(file, O_WRONLY | O_CLOEXEC);
416 if (fd < 0)
417 return -1;
418
419 pid = fork();
420 if (pid == 0) {
421 ssize_t n;
422
423 /* A namespace of its own, with nothing left in it to spend. */
424 if (unshare(CLONE_NEWUSER) || write_interp_limit("0"))
425 _exit(BIND_NO_BUDGET);
426 n = write(fd, cmd, strlen(cmd));
427 _exit(n < 0 ? errno : 0);
428 }
429 close(fd);
430 if (pid < 0 || waitpid(pid, &status, 0) != pid || !WIFEXITED(status))
431 return -1;
432 retval = WEXITSTATUS(status);
433 return retval == BIND_NO_BUDGET ? -1 : retval;
434}
435
436FIXTURE(bound_interp) {
437 char obj[PATH_MAX];
438 struct bpf_case c;
439 bool started;
440};
441
442FIXTURE_SETUP(bound_interp)
443{
444 const char *why = bpf_handler_unsupported();
445
446 if (why)
447 SKIP(return, "%s", why);
448 if (!binfmt_flag_supported('D')) {
449 ASSERT_EQ(errno, EINVAL);
450 SKIP(return, "kernel without the 'D' flag");
451 }
452
453 ASSERT_EQ(install_interp(BIND_FIRST), 0);
454 ASSERT_EQ(install_interp(BIND_SECOND), 0);
455
456 ASSERT_EQ(artifact_path(self->obj, sizeof(self->obj),
457 "interp_bind.bpf.o"), 0);
458
459 /*
460 * Registered disabled, so it cannot be matched yet and can still be
461 * given interpreters. Each path is resolved once, by its write(2);
462 * from here on the entry holds the files themselves.
463 */
464 ASSERT_EQ(bpf_case_start_flags(&self->c, self->obj, "interp_bind",
465 "test_interp_bind", "D"), 0);
466 self->started = true;
467
468 ASSERT_EQ(entry_bind("test_interp_bind", "first", BIND_FIRST), 0);
469 ASSERT_EQ(entry_bind("test_interp_bind", "second", BIND_SECOND), 0);
470}
471
472FIXTURE_TEARDOWN(bound_interp)
473{
474 if (self->started)
475 bpf_case_stop(&self->c);
476 unlink(BIND_FIRST);
477 unlink(BIND_SECOND);
478 unlink(AARCH64_PATH);
479 unlink(BIND_RISCV_PATH);
480 unlink(BIND_ARM_PATH);
481}
482
483/* Enabling is what makes the configured entry matchable. */
484static int activate(const char *entry)
485{
486 return entry_command(entry, "1\n");
487}
488
489/* One entry, one interpreter per guest architecture, picked per exec. */
490TEST_F(bound_interp, selects_by_name)
491{
492 ASSERT_EQ(create_fake_elf(AARCH64_PATH, EM_AARCH64), 0);
493 ASSERT_EQ(create_fake_elf(BIND_RISCV_PATH, EM_RISCV), 0);
494
495 /* Disabled, so it does not match and no format claims the binary. */
496 EXPECT_EQ(exec_errno(AARCH64_PATH), ENOEXEC);
497
498 ASSERT_EQ(activate("test_interp_bind"), 0);
499 EXPECT_EQ(check_output(AARCH64_PATH, BIND_EXPECT BIND_FIRST), 0);
500 EXPECT_EQ(check_output(BIND_RISCV_PATH, BIND_EXPECT BIND_SECOND), 0);
501}
502
503/* What was bound is what runs, whatever the path holds afterwards. */
504TEST_F(bound_interp, path_no_longer_decides)
505{
506 char other[PATH_MAX];
507
508 ASSERT_EQ(create_fake_elf(AARCH64_PATH, EM_AARCH64), 0);
509 ASSERT_EQ(activate("test_interp_bind"), 0);
510
511 /* Bound interpreters are pinned against writes, exactly like 'F'. */
512 EXPECT_TRUE(write_denied(BIND_FIRST));
513
514 /* Replace the path with a different binary: a new file, new inode. */
515 ASSERT_EQ(artifact_path(other, sizeof(other), "binfmt_bpf_interp"), 0);
516 ASSERT_EQ(unlink(BIND_FIRST), 0);
517 ASSERT_EQ(copy_file(other, BIND_FIRST), 0);
518
519 EXPECT_EQ(check_output(AARCH64_PATH, BIND_EXPECT BIND_FIRST), 0);
520}
521
522/* The entry reports what it bound, under the names it bound them as. */
523TEST_F(bound_interp, entry_reports_bindings)
524{
525 EXPECT_TRUE(entry_shows("test_interp_bind",
526 "bpf-interpreter first " BIND_FIRST));
527 EXPECT_TRUE(entry_shows("test_interp_bind",
528 "bpf-interpreter second " BIND_SECOND));
529}
530
531/* Selecting a name the entry did not bind fails the exec. */
532TEST_F(bound_interp, unbound_name_fails)
533{
534 ASSERT_EQ(create_fake_elf(BIND_ARM_PATH, EM_ARM), 0);
535 ASSERT_EQ(activate("test_interp_bind"), 0);
536
537 EXPECT_EQ(exec_errno(BIND_ARM_PATH), ENOENT);
538}
539
540/* Activating seals it: what can be matched cannot be changed. */
541TEST_F(bound_interp, sealed_once_active)
542{
543 ASSERT_EQ(activate("test_interp_bind"), 0);
544
545 EXPECT_EQ(entry_bind("test_interp_bind", "third", BIND_SECOND), -EBUSY);
546 EXPECT_FALSE(entry_shows("test_interp_bind",
547 "bpf-interpreter third " BIND_SECOND));
548}
549
550/* The seal is for good: disabling the entry again reopens nothing. */
551TEST_F(bound_interp, disable_does_not_unseal)
552{
553 ASSERT_EQ(activate("test_interp_bind"), 0);
554 ASSERT_EQ(entry_command("test_interp_bind", "0\n"), 0);
555
556 EXPECT_EQ(entry_bind("test_interp_bind", "third", BIND_SECOND), -EBUSY);
557}
558
559/* An entry registered without 'D' is sealed from the start. */
560TEST_F(bound_interp, born_sealed)
561{
562 /* A second entry for the handler the fixture already published. */
563 ASSERT_EQ(register_entry("test_born_sealed", "interp_bind", NULL), 0);
564
565 EXPECT_EQ(entry_bind("test_born_sealed", "first", BIND_FIRST), -EBUSY);
566 unregister("test_born_sealed");
567}
568
569/* A name is bound once; a second use of it is refused. */
570TEST_F(bound_interp, duplicate_name_refused)
571{
572 EXPECT_EQ(entry_bind("test_interp_bind", "first", BIND_SECOND), -EEXIST);
573}
574
575/* A name is a printable word: the entry file reports 'name path' lines. */
576TEST_F(bound_interp, name_must_be_printable)
577{
578 /* A control character would forge a line into the entry file. */
579 EXPECT_EQ(entry_bind("test_interp_bind", "a\tb", BIND_FIRST), -EINVAL);
580 EXPECT_EQ(entry_bind("test_interp_bind", "a\nb", BIND_FIRST), -EINVAL);
581
582 /* A space cannot even be spelled: the path starts after the first one. */
583 EXPECT_EQ(entry_bind("test_interp_bind", "a b", BIND_FIRST), -EINVAL);
584}
585
586/* The command ends at the write: bytes past an embedded nul are refused. */
587TEST_F(bound_interp, trailing_bytes_refused)
588{
589 char cmd[PATH_MAX];
590 size_t len;
591 int fd;
592
593 /* entry_command() cannot spell a nul, so write the buffer raw. */
594 snprintf(cmd, sizeof(cmd), "+nul %s", BIND_FIRST);
595 len = strlen(cmd) + 1;
596 memcpy(cmd + len, "junk", sizeof("junk"));
597 len += sizeof("junk");
598
599 fd = open(BINFMT_DIR "/test_interp_bind", O_WRONLY | O_CLOEXEC);
600 ASSERT_GE(fd, 0);
601 EXPECT_EQ(write(fd, cmd, len), -1);
602 EXPECT_EQ(errno, EINVAL);
603 close(fd);
604
605 EXPECT_FALSE(entry_shows("test_interp_bind",
606 "bpf-interpreter nul " BIND_FIRST));
607}
608
609/* An entry binds at most BIND_MAX interpreters. */
610TEST_F(bound_interp, capped_bindings)
611{
612 char name[16];
613 int i;
614
615 /* The fixture bound "first" and "second" already. */
616 for (i = 2; i < BIND_MAX; i++) {
617 snprintf(name, sizeof(name), "n%d", i);
618 ASSERT_EQ(entry_bind("test_interp_bind", name, BIND_FIRST), 0);
619 }
620 EXPECT_EQ(entry_bind("test_interp_bind", "over", BIND_FIRST), -ENOSPC);
621}
622
623/* A binding pins a file: it is charged, and refused once the budget is out. */
624TEST_F(bound_interp, bindings_are_charged)
625{
626 int err = bind_out_of_budget("test_interp_bind", "third", BIND_FIRST);
627
628 if (err < 0)
629 SKIP(return, "no user namespaces or no " INTERP_LIMIT);
630
631 /* The charge follows the writer, not the entry file it writes to. */
632 EXPECT_EQ(err, ENOSPC);
633
634 /* The budget was the only thing in the way. */
635 EXPECT_EQ(entry_bind("test_interp_bind", "third", BIND_FIRST), 0);
636}
637
638TEST_HARNESS_MAIN