summaryrefslogtreecommitdiff
path: root/tools/testing/selftests/exec/binfmt_transparent_interp.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/testing/selftests/exec/binfmt_transparent_interp.c')
-rw-r--r--tools/testing/selftests/exec/binfmt_transparent_interp.c112
1 files changed, 112 insertions, 0 deletions
diff --git a/tools/testing/selftests/exec/binfmt_transparent_interp.c b/tools/testing/selftests/exec/binfmt_transparent_interp.c
new file mode 100644
index 000000000000..d4c4a538c9aa
--- /dev/null
+++ b/tools/testing/selftests/exec/binfmt_transparent_interp.c
@@ -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;
+}