| // 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; |
| } |