1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Asserting interpreter for the transparent binfmt_misc mode. It runs in 4 * place of the dispatched binary and verifies the identity the kernel 5 * constructed: the aux vector contract, the exe link, argv, cmdline, comm 6 * and the write denial on the binary. BINFMT_TEST_BINARY names the binary; 7 * the harness execs it with the arguments "argone argtwo". Prints 8 * TRANSPARENT_OK and exits 0 when every check holds. 9 */ 10 #define _GNU_SOURCE 11 #include <errno.h> 12 #include <fcntl.h> 13 #include <limits.h> 14 #include <stdio.h> 15 #include <stdlib.h> 16 #include <string.h> 17 #include <sys/auxv.h> 18 #include <sys/stat.h> 19 #include <unistd.h> 20 21 #include "binfmt_misc_common.h" 22 #include "kselftest.h" 23 24 #ifndef AT_FLAGS_TRANSPARENT_INTERP 25 #define AT_FLAGS_TRANSPARENT_INTERP (1 << 1) 26 #endif 27 28 static int fail; 29 30 static void ok(int cond, const char *what) 31 { 32 if (!cond) { 33 fprintf(stderr, "TRANSPARENT_FAIL: %s (errno %d)\n", what, errno); 34 fail = 1; 35 } 36 } 37 38 int main(int argc, char **argv) 39 { 40 const char *binary = getenv("BINFMT_TEST_BINARY"); 41 const char *argv0 = getenv("BINFMT_TEST_ARGV0"); 42 char expect[PATH_MAX + 32], buf[PATH_MAX]; 43 unsigned long execfd; 44 struct stat stb, stfd; 45 const char *want[3]; 46 const char *base; 47 size_t expect_len, i; 48 int fd, have_stb, have_stfd; 49 ssize_t n; 50 51 if (!binary) { 52 fprintf(stderr, "TRANSPARENT_FAIL: BINFMT_TEST_BINARY unset\n"); 53 return 1; 54 } 55 /* Distinct from the binary path, so a classic argv splice is caught. */ 56 want[0] = argv0 ? argv0 : binary; 57 want[1] = PAYLOAD_ARG1; 58 want[2] = PAYLOAD_ARG2; 59 60 /* The aux vector announces the transparent contract. */ 61 ok(getauxval(AT_FLAGS) & AT_FLAGS_TRANSPARENT_INTERP, 62 "AT_FLAGS lacks AT_FLAGS_TRANSPARENT_INTERP"); 63 64 /* AT_EXECFD refers to the very file that was executed. */ 65 execfd = getauxval(AT_EXECFD); 66 ok(execfd > 2, "no AT_EXECFD"); 67 have_stb = !stat(binary, &stb); 68 ok(have_stb, "cannot stat the binary"); 69 have_stfd = !fstat((int)execfd, &stfd); 70 ok(have_stfd, "cannot fstat AT_EXECFD"); 71 ok(have_stb && have_stfd && stb.st_dev == stfd.st_dev && 72 stb.st_ino == stfd.st_ino, "AT_EXECFD is not the binary"); 73 74 /* The exe link names the binary, not this interpreter. */ 75 ok(exe_is(binary), "/proc/self/exe is not the binary"); 76 77 /* argv arrived unspliced. */ 78 ok(argc == (int)ARRAY_SIZE(want), "argv was rewritten"); 79 for (i = 0; i < ARRAY_SIZE(want) && i < (size_t)argc; i++) 80 ok(!strcmp(argv[i], want[i]), "argv was rewritten"); 81 82 /* And so did the kernel's copy of it: the same strings, NUL separated. */ 83 for (i = 0, expect_len = 0; i < ARRAY_SIZE(want); i++) { 84 size_t len = strlen(want[i]) + 1; 85 86 if (expect_len + len > sizeof(expect)) { 87 ok(0, "argv does not fit the expectation buffer"); 88 break; 89 } 90 memcpy(expect + expect_len, want[i], len); 91 expect_len += len; 92 } 93 fd = open("/proc/self/cmdline", O_RDONLY); 94 n = fd >= 0 ? read(fd, buf, sizeof(buf)) : -1; 95 if (fd >= 0) 96 close(fd); 97 ok(n == (ssize_t)expect_len && !memcmp(buf, expect, expect_len), 98 "/proc/self/cmdline was rewritten"); 99 100 /* comm is the binary's basename. */ 101 base = strrchr(binary, '/'); 102 base = base ? base + 1 : binary; 103 ok(comm_is(base), "comm is not the binary's basename"); 104 105 /* The binary is write-denied while it runs, like a direct exec. */ 106 ok(write_denied(binary), "binary is writable while running"); 107 ok(write_denied("/proc/self/exe"), "exe link is writable while running"); 108 109 if (!fail) 110 printf("TRANSPARENT_OK\n"); 111 return fail; 112 } 113