mirror of
https://github.com/torvalds/linux.git
synced 2026-09-22 04:34:03 +02:00
selftests/exec: test the transparent binfmt_misc mode
Verify the identity a transparent dispatch constructs, from both activation paths. - binfmt_misc_transparent: registers a magic entry with the static 'T' flag and execs a matched binary with arguments. - binfmt_misc_bpf: a handler whose load program sets BPF_BINPRM_TRANSPARENT. Both dispatch to a shared asserting interpreter that runs in place of the binary and checks the contract from the inside: - AT_FLAGS carries AT_FLAGS_TRANSPARENT_INTERP - AT_EXECFD refers to the very inode of the binary - /proc/self/exe resolves to the binary - argv and /proc/self/cmdline are exactly what the caller passed with nothing spliced in - comm is the binary's basename - the binary is write-denied while it runs The static test also validates the registration. 'T' combined with 'P' must be rejected. A kernel that does not know 'T' turns the test into a skip. The asserting interpreter and the static test build without the bpf toolchain so the core transparent semantics stay covered on systems where the bpf cases are skipped. The flag support probe, the canonical payload argv with the run_payload() helper that execs it, and the identity assertions (exe link, comm, write denial) live in binfmt_misc_common.h; the loader substitution test reuses all of them. Link: https://patch.msgid.link/20260721-work-bpf-binfmt_misc-ptinterp-v2-13-e57866e4ae0f@kernel.org Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This commit is contained in:
parent
21e04378e0
commit
7baee96f83
2
tools/testing/selftests/exec/.gitignore
vendored
2
tools/testing/selftests/exec/.gitignore
vendored
|
|
@ -22,5 +22,7 @@ S_I*.test
|
|||
binfmt_misc_bpf
|
||||
binfmt_bpf_interp
|
||||
binfmt_bpf_app
|
||||
binfmt_misc_transparent
|
||||
binfmt_transparent_interp
|
||||
*.bpf.o
|
||||
vmlinux.h
|
||||
|
|
|
|||
|
|
@ -21,6 +21,11 @@ TEST_GEN_PROGS += recursion-depth
|
|||
TEST_GEN_PROGS += null-argv
|
||||
TEST_GEN_PROGS += check-exec
|
||||
|
||||
# Static ('T' flag) transparent binfmt_misc test; the asserting interpreter
|
||||
# is shared with the bpf harness's transparent case. No bpf toolchain needed.
|
||||
TEST_GEN_PROGS += binfmt_misc_transparent
|
||||
TEST_GEN_FILES += binfmt_transparent_interp
|
||||
|
||||
# binfmt_misc bpf-backed ('B') handler test: a libbpf harness plus its
|
||||
# struct_ops objects and the test interpreter/app it routes between. Only
|
||||
# built when clang, bpftool, the vmlinux BTF and libbpf are all present
|
||||
|
|
@ -35,7 +40,7 @@ HAVE_BPF_TOOLCHAIN ?= $(shell command -v $(CLANG) >/dev/null 2>&1 && \
|
|||
pkg-config --exists libbpf 2>/dev/null && echo y)
|
||||
ifeq ($(HAVE_BPF_TOOLCHAIN),y)
|
||||
TEST_GEN_PROGS += binfmt_misc_bpf
|
||||
TEST_GEN_FILES += bpf_interp.bpf.o nix_origin.bpf.o
|
||||
TEST_GEN_FILES += bpf_interp.bpf.o nix_origin.bpf.o transparent.bpf.o
|
||||
TEST_GEN_FILES += binfmt_bpf_interp binfmt_bpf_app
|
||||
else
|
||||
$(info exec selftests: skipping binfmt_misc_bpf, needs clang, bpftool, vmlinux BTF and libbpf)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
*
|
||||
* echo ':name:B::::<handler>:' > /proc/sys/fs/binfmt_misc/register
|
||||
*
|
||||
* Two self-contained cases are exercised:
|
||||
* Three self-contained cases are exercised:
|
||||
*
|
||||
* 1. bpf_interp: the match program matches a synthetic aarch64 ELF header
|
||||
* from the prefetched bprm->buf and the load program routes it to a
|
||||
|
|
@ -18,9 +18,13 @@
|
|||
* commit only to a "$ORIGIN/..."-relative PT_INTERP and the load program
|
||||
* resolves it to an interpreter co-located with the binary (the
|
||||
* relocatable-loader case the kernel ELF loader cannot express).
|
||||
* 3. transparent: the load program sets BPF_BINPRM_TRANSPARENT; the
|
||||
* asserting interpreter (binfmt_transparent_interp) verifies the
|
||||
* identity the kernel constructed (exe link, argv, cmdline, comm,
|
||||
* AT_EXECFD, write denial) from inside the process.
|
||||
*
|
||||
* Both route to a test interpreter that prints BPF_INTERP_RAN, proving the
|
||||
* program's chosen interpreter actually ran.
|
||||
* The first two route to a test interpreter that prints BPF_INTERP_RAN,
|
||||
* proving the program's chosen interpreter actually ran.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include <elf.h>
|
||||
|
|
@ -40,7 +44,10 @@
|
|||
#define INTERP_PATH "/tmp/binfmt_bpf_interp"
|
||||
#define AARCH64_PATH "/tmp/binfmt_bpf_aarch64"
|
||||
#define RELOC_TEMPLATE "/tmp/binfmt_relocXXXXXX"
|
||||
#define TRANS_INTERP "/tmp/binfmt_transparent_interp"
|
||||
#define TRANS_PATH "/tmp/binfmt_bpf_riscv"
|
||||
#define EXPECT "BPF_INTERP_RAN"
|
||||
#define TRANS_EXPECT "TRANSPARENT_OK"
|
||||
|
||||
/* A minimal 64-bit little-endian ELF header, padded to the read size. */
|
||||
static int create_fake_elf(const char *path, unsigned short machine)
|
||||
|
|
@ -208,4 +215,28 @@ TEST_F(bpf_handler, origin_relative_interpreter)
|
|||
rmdir(dir);
|
||||
}
|
||||
|
||||
/* A transparent dispatch: the process presents as the binary, not the interp. */
|
||||
TEST_F(bpf_handler, transparent_dispatch)
|
||||
{
|
||||
char src[PATH_MAX], cmd[PATH_MAX + 16];
|
||||
|
||||
/* Probe for transparent-mode support via its static counterpart. */
|
||||
if (binfmt_flag_supported('T'))
|
||||
SKIP(return, "kernel without transparent mode");
|
||||
|
||||
ASSERT_EQ(artifact_path(src, sizeof(src), "binfmt_transparent_interp"), 0);
|
||||
ASSERT_EQ(copy_file(src, TRANS_INTERP), 0);
|
||||
ASSERT_EQ(create_fake_elf(TRANS_PATH, EM_RISCV), 0);
|
||||
|
||||
setenv("BINFMT_TEST_BINARY", TRANS_PATH, 1);
|
||||
snprintf(cmd, sizeof(cmd), "%s argone argtwo", TRANS_PATH);
|
||||
ASSERT_EQ(artifact_path(self->obj, sizeof(self->obj),
|
||||
"transparent.bpf.o"), 0);
|
||||
EXPECT_EQ(run_case(self->obj, "transparent", "test_bpf_transparent",
|
||||
cmd, TRANS_EXPECT), 0);
|
||||
|
||||
unlink(TRANS_PATH);
|
||||
unlink(TRANS_INTERP);
|
||||
}
|
||||
|
||||
TEST_HARNESS_MAIN
|
||||
|
|
|
|||
|
|
@ -9,13 +9,27 @@
|
|||
#include <limits.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define BINFMT_DIR "/proc/sys/fs/binfmt_misc"
|
||||
#define BINFMT_REG BINFMT_DIR "/register"
|
||||
|
||||
/* comm holds 15 usable chars; a read of /proc/self/comm appends a newline. */
|
||||
#define TASK_COMM_LEN 16
|
||||
|
||||
/* The canonical payload argv: run_payload() passes it, the payloads assert it. */
|
||||
#define PAYLOAD_ARGV0 "payload-argv0"
|
||||
#define PAYLOAD_ARG1 "argone"
|
||||
#define PAYLOAD_ARG2 "argtwo"
|
||||
|
||||
/* Exit status run_payload() reports when the exec was refused as unhandled. */
|
||||
#define RUN_ENOEXEC 42
|
||||
|
||||
static inline int copy_file(const char *src, const char *dst)
|
||||
{
|
||||
char buf[4096];
|
||||
|
|
@ -97,4 +111,83 @@ static inline int artifact_path(char *out, size_t sz, const char *name)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* Probe kernel support for a registration flag with a throwaway entry. */
|
||||
static inline int binfmt_flag_supported(char flag)
|
||||
{
|
||||
char rule[64];
|
||||
|
||||
snprintf(rule, sizeof(rule), ":bm_flag_probe:E::bmprobe::/bin/true:%c",
|
||||
flag);
|
||||
if (write_reg(rule))
|
||||
return -1;
|
||||
unregister("bm_flag_probe");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Run @path with the canonical payload argv and return its exit status, or
|
||||
* RUN_ENOEXEC when the exec itself was refused as unhandled.
|
||||
*/
|
||||
static inline int run_payload(const char *path)
|
||||
{
|
||||
int status;
|
||||
pid_t pid;
|
||||
|
||||
pid = fork();
|
||||
if (pid == 0) {
|
||||
execl(path, PAYLOAD_ARGV0, PAYLOAD_ARG1, PAYLOAD_ARG2,
|
||||
(char *)NULL);
|
||||
_exit(errno == ENOEXEC ? RUN_ENOEXEC : 126);
|
||||
}
|
||||
if (pid < 0 || waitpid(pid, &status, 0) != pid || !WIFEXITED(status))
|
||||
return -1;
|
||||
return WEXITSTATUS(status);
|
||||
}
|
||||
|
||||
/* Does the exe link name @path? */
|
||||
static inline bool exe_is(const char *path)
|
||||
{
|
||||
char exe[PATH_MAX], real[PATH_MAX];
|
||||
ssize_t n;
|
||||
|
||||
n = readlink("/proc/self/exe", exe, sizeof(exe) - 1);
|
||||
if (n <= 0 || !realpath(path, real))
|
||||
return false;
|
||||
exe[n] = '\0';
|
||||
return !strcmp(exe, real);
|
||||
}
|
||||
|
||||
/* Is comm @name truncated to what a comm can hold? */
|
||||
static inline bool comm_is(const char *name)
|
||||
{
|
||||
char comm[TASK_COMM_LEN + 2], expect[TASK_COMM_LEN];
|
||||
ssize_t n;
|
||||
int fd;
|
||||
|
||||
fd = open("/proc/self/comm", O_RDONLY);
|
||||
if (fd < 0)
|
||||
return false;
|
||||
n = read(fd, comm, sizeof(comm) - 1);
|
||||
close(fd);
|
||||
if (n <= 0)
|
||||
return false;
|
||||
if (comm[n - 1] == '\n')
|
||||
n--;
|
||||
comm[n] = '\0';
|
||||
snprintf(expect, sizeof(expect), "%s", name);
|
||||
return !strcmp(comm, expect);
|
||||
}
|
||||
|
||||
/* Opening @path for writing has to fail with ETXTBSY. */
|
||||
static inline bool write_denied(const char *path)
|
||||
{
|
||||
int fd = open(path, O_WRONLY);
|
||||
|
||||
if (fd >= 0) {
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
return errno == ETXTBSY;
|
||||
}
|
||||
|
||||
#endif /* __SELFTESTS_EXEC_BINFMT_MISC_COMMON_H */
|
||||
|
|
|
|||
95
tools/testing/selftests/exec/binfmt_misc_transparent.c
Normal file
95
tools/testing/selftests/exec/binfmt_misc_transparent.c
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Test the static transparent flag 'T' of binfmt_misc. A magic-matched
|
||||
* binary is dispatched to an interpreter with the argument vector left
|
||||
* untouched, the binary passed through AT_EXECFD and mm->exe_file labeled
|
||||
* with the binary. The asserting interpreter (binfmt_transparent_interp)
|
||||
* verifies the constructed identity from inside the process and exits 0.
|
||||
*
|
||||
* Needs root for the registration; no bpf toolchain involved.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "binfmt_misc_common.h"
|
||||
#include "kselftest_harness.h"
|
||||
|
||||
#define MAGIC "#TRANSPARENT-SELFTEST#"
|
||||
#define TARGET_PATH "/tmp/binfmt_transparent_target"
|
||||
#define INTERP_PATH "/tmp/binfmt_transparent_interp"
|
||||
#define ENTRY "test_transparent"
|
||||
#define RULE(flags) ":" ENTRY ":M:0:" MAGIC "::" INTERP_PATH ":" flags
|
||||
|
||||
/* The target only has to carry the magic; it is never actually loaded. */
|
||||
static int create_target(void)
|
||||
{
|
||||
char buf[128] = MAGIC "\n";
|
||||
int fd;
|
||||
|
||||
unlink(TARGET_PATH);
|
||||
fd = open(TARGET_PATH, O_WRONLY | O_CREAT | O_EXCL, 0755);
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
if (write(fd, buf, sizeof(buf)) != (ssize_t)sizeof(buf)) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
FIXTURE(transparent) {
|
||||
};
|
||||
|
||||
FIXTURE_SETUP(transparent)
|
||||
{
|
||||
char src[PATH_MAX];
|
||||
|
||||
if (getuid() != 0)
|
||||
SKIP(return, "test must be run as root");
|
||||
if (!binfmt_misc_available())
|
||||
SKIP(return, "no binfmt_misc");
|
||||
|
||||
ASSERT_EQ(artifact_path(src, sizeof(src), "binfmt_transparent_interp"), 0);
|
||||
ASSERT_EQ(copy_file(src, INTERP_PATH), 0);
|
||||
ASSERT_EQ(create_target(), 0);
|
||||
|
||||
/* Skip the whole suite on a kernel that does not know 'T'. */
|
||||
if (binfmt_flag_supported('T')) {
|
||||
ASSERT_EQ(errno, EINVAL);
|
||||
SKIP(return, "kernel without the 'T' flag");
|
||||
}
|
||||
}
|
||||
|
||||
FIXTURE_TEARDOWN(transparent)
|
||||
{
|
||||
unregister(ENTRY);
|
||||
unlink(TARGET_PATH);
|
||||
unlink(INTERP_PATH);
|
||||
}
|
||||
|
||||
/* Grammar sanity check: the same entry without 'T' has to register. */
|
||||
TEST_F(transparent, plain_entry_registers)
|
||||
{
|
||||
ASSERT_EQ(write_reg(RULE("")), 0);
|
||||
}
|
||||
|
||||
/* 'T' preserves the whole argv, so combining it with 'P' is rejected. */
|
||||
TEST_F(transparent, rejects_preserve_argv0)
|
||||
{
|
||||
ASSERT_NE(write_reg(RULE("TP")), 0);
|
||||
EXPECT_EQ(errno, EINVAL);
|
||||
}
|
||||
|
||||
/* The interpreter asserts the identity the kernel built for it. */
|
||||
TEST_F(transparent, dispatch)
|
||||
{
|
||||
ASSERT_EQ(write_reg(RULE("T")), 0);
|
||||
|
||||
setenv("BINFMT_TEST_BINARY", TARGET_PATH, 1);
|
||||
setenv("BINFMT_TEST_ARGV0", PAYLOAD_ARGV0, 1);
|
||||
EXPECT_EQ(run_payload(TARGET_PATH), 0);
|
||||
}
|
||||
|
||||
TEST_HARNESS_MAIN
|
||||
112
tools/testing/selftests/exec/binfmt_transparent_interp.c
Normal file
112
tools/testing/selftests/exec/binfmt_transparent_interp.c
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Asserting interpreter for the transparent binfmt_misc mode. It runs in
|
||||
* place of the dispatched binary and verifies the identity the kernel
|
||||
* constructed: the aux vector contract, the exe link, argv, cmdline, comm
|
||||
* and the write denial on the binary. BINFMT_TEST_BINARY names the binary;
|
||||
* the harness execs it with the arguments "argone argtwo". Prints
|
||||
* TRANSPARENT_OK and exits 0 when every check holds.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/auxv.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "binfmt_misc_common.h"
|
||||
#include "kselftest.h"
|
||||
|
||||
#ifndef AT_FLAGS_TRANSPARENT_INTERP
|
||||
#define AT_FLAGS_TRANSPARENT_INTERP (1 << 1)
|
||||
#endif
|
||||
|
||||
static int fail;
|
||||
|
||||
static void ok(int cond, const char *what)
|
||||
{
|
||||
if (!cond) {
|
||||
fprintf(stderr, "TRANSPARENT_FAIL: %s (errno %d)\n", what, errno);
|
||||
fail = 1;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *binary = getenv("BINFMT_TEST_BINARY");
|
||||
const char *argv0 = getenv("BINFMT_TEST_ARGV0");
|
||||
char expect[PATH_MAX + 32], buf[PATH_MAX];
|
||||
unsigned long execfd;
|
||||
struct stat stb, stfd;
|
||||
const char *want[3];
|
||||
const char *base;
|
||||
size_t expect_len, i;
|
||||
int fd, have_stb, have_stfd;
|
||||
ssize_t n;
|
||||
|
||||
if (!binary) {
|
||||
fprintf(stderr, "TRANSPARENT_FAIL: BINFMT_TEST_BINARY unset\n");
|
||||
return 1;
|
||||
}
|
||||
/* Distinct from the binary path, so a classic argv splice is caught. */
|
||||
want[0] = argv0 ? argv0 : binary;
|
||||
want[1] = PAYLOAD_ARG1;
|
||||
want[2] = PAYLOAD_ARG2;
|
||||
|
||||
/* The aux vector announces the transparent contract. */
|
||||
ok(getauxval(AT_FLAGS) & AT_FLAGS_TRANSPARENT_INTERP,
|
||||
"AT_FLAGS lacks AT_FLAGS_TRANSPARENT_INTERP");
|
||||
|
||||
/* AT_EXECFD refers to the very file that was executed. */
|
||||
execfd = getauxval(AT_EXECFD);
|
||||
ok(execfd > 2, "no AT_EXECFD");
|
||||
have_stb = !stat(binary, &stb);
|
||||
ok(have_stb, "cannot stat the binary");
|
||||
have_stfd = !fstat((int)execfd, &stfd);
|
||||
ok(have_stfd, "cannot fstat AT_EXECFD");
|
||||
ok(have_stb && have_stfd && stb.st_dev == stfd.st_dev &&
|
||||
stb.st_ino == stfd.st_ino, "AT_EXECFD is not the binary");
|
||||
|
||||
/* The exe link names the binary, not this interpreter. */
|
||||
ok(exe_is(binary), "/proc/self/exe is not the binary");
|
||||
|
||||
/* argv arrived unspliced. */
|
||||
ok(argc == (int)ARRAY_SIZE(want), "argv was rewritten");
|
||||
for (i = 0; i < ARRAY_SIZE(want) && i < (size_t)argc; i++)
|
||||
ok(!strcmp(argv[i], want[i]), "argv was rewritten");
|
||||
|
||||
/* And so did the kernel's copy of it: the same strings, NUL separated. */
|
||||
for (i = 0, expect_len = 0; i < ARRAY_SIZE(want); i++) {
|
||||
size_t len = strlen(want[i]) + 1;
|
||||
|
||||
if (expect_len + len > sizeof(expect)) {
|
||||
ok(0, "argv does not fit the expectation buffer");
|
||||
break;
|
||||
}
|
||||
memcpy(expect + expect_len, want[i], len);
|
||||
expect_len += len;
|
||||
}
|
||||
fd = open("/proc/self/cmdline", O_RDONLY);
|
||||
n = fd >= 0 ? read(fd, buf, sizeof(buf)) : -1;
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
ok(n == (ssize_t)expect_len && !memcmp(buf, expect, expect_len),
|
||||
"/proc/self/cmdline was rewritten");
|
||||
|
||||
/* comm is the binary's basename. */
|
||||
base = strrchr(binary, '/');
|
||||
base = base ? base + 1 : binary;
|
||||
ok(comm_is(base), "comm is not the binary's basename");
|
||||
|
||||
/* The binary is write-denied while it runs, like a direct exec. */
|
||||
ok(write_denied(binary), "binary is writable while running");
|
||||
ok(write_denied("/proc/self/exe"), "exe link is writable while running");
|
||||
|
||||
if (!fail)
|
||||
printf("TRANSPARENT_OK\n");
|
||||
return fail;
|
||||
}
|
||||
57
tools/testing/selftests/exec/transparent.bpf.c
Normal file
57
tools/testing/selftests/exec/transparent.bpf.c
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* binfmt_misc_ops handler for the transparent-mode case: match a synthetic
|
||||
* riscv ELF header and run the asserting interpreter transparently - the
|
||||
* argument vector untouched, the binary in AT_EXECFD and mm->exe_file
|
||||
* labeled with the binary.
|
||||
*/
|
||||
#include "vmlinux.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
|
||||
#define EI_CLASS 4
|
||||
#define ELFCLASS64 2
|
||||
#define EM_RISCV 243
|
||||
|
||||
extern int bpf_binprm_set_interp(struct linux_binprm *bprm, const char *path,
|
||||
size_t path__sz) __ksym;
|
||||
extern int bpf_binprm_set_flags(struct linux_binprm *bprm,
|
||||
enum bpf_binprm_flags flags) __ksym;
|
||||
|
||||
SEC("struct_ops.s/match")
|
||||
bool BPF_PROG(transparent_match, struct linux_binprm *bprm)
|
||||
{
|
||||
__u16 machine;
|
||||
|
||||
if (bprm->buf[0] != 0x7f || bprm->buf[1] != 'E' ||
|
||||
bprm->buf[2] != 'L' || bprm->buf[3] != 'F' ||
|
||||
bprm->buf[EI_CLASS] != ELFCLASS64)
|
||||
return false;
|
||||
|
||||
/* e_machine is a 16-bit little-endian field at offset 18. */
|
||||
machine = (__u8)bprm->buf[18] | ((__u16)(__u8)bprm->buf[19] << 8);
|
||||
return machine == EM_RISCV;
|
||||
}
|
||||
|
||||
SEC("struct_ops.s/load")
|
||||
int BPF_PROG(transparent_load, struct linux_binprm *bprm)
|
||||
{
|
||||
char interp[] = "/tmp/binfmt_transparent_interp";
|
||||
int err;
|
||||
|
||||
err = bpf_binprm_set_flags(bprm, BPF_BINPRM_TRANSPARENT);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* @path__sz includes the terminating NUL; 0 commits the selection. */
|
||||
return bpf_binprm_set_interp(bprm, interp, sizeof(interp));
|
||||
}
|
||||
|
||||
SEC(".struct_ops.link")
|
||||
struct binfmt_misc_ops transparent = {
|
||||
.match = (void *)transparent_match,
|
||||
.load = (void *)transparent_load,
|
||||
.name = "transparent",
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user