summaryrefslogtreecommitdiff
path: root/tools/testing/selftests/exec/binfmt_misc_disabled.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/testing/selftests/exec/binfmt_misc_disabled.c')
-rw-r--r--tools/testing/selftests/exec/binfmt_misc_disabled.c172
1 files changed, 172 insertions, 0 deletions
diff --git a/tools/testing/selftests/exec/binfmt_misc_disabled.c b/tools/testing/selftests/exec/binfmt_misc_disabled.c
new file mode 100644
index 000000000000..47c9e8a4ee42
--- /dev/null
+++ b/tools/testing/selftests/exec/binfmt_misc_disabled.c
@@ -0,0 +1,172 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Test the 'D' (register disabled) flag of binfmt_misc. An entry
+ * registered with it exists but cannot be matched until userspace enables
+ * it, which splits a registration into create and activate.
+ *
+ * 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 "#DISABLED-SELFTEST#"
+#define TARGET_PATH "/tmp/binfmt_disabled_target"
+#define INTERP_PATH "/tmp/binfmt_disabled_interp.sh"
+#define ENTRY "test_disabled"
+#define RULE(flags) ":" ENTRY ":M:0:" MAGIC "::" INTERP_PATH ":" flags
+
+/* The interpreter exits with a code the harness can recognise. */
+#define EXIT_INTERP 7
+
+/* 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;
+}
+
+static int create_interp(void)
+{
+ char buf[64];
+ int fd;
+
+ unlink(INTERP_PATH);
+ fd = open(INTERP_PATH, O_WRONLY | O_CREAT | O_EXCL, 0755);
+ if (fd < 0)
+ return -1;
+ snprintf(buf, sizeof(buf), "#!/bin/sh\nexit %d\n", EXIT_INTERP);
+ if (write(fd, buf, strlen(buf)) != (ssize_t)strlen(buf)) {
+ close(fd);
+ return -1;
+ }
+ return close(fd);
+}
+
+FIXTURE(disabled) {
+};
+
+FIXTURE_SETUP(disabled)
+{
+ if (getuid() != 0)
+ SKIP(return, "test must be run as root");
+ if (!binfmt_misc_available())
+ SKIP(return, "no binfmt_misc");
+
+ /* Skip the whole suite on a kernel that does not know 'D'. */
+ if (!binfmt_flag_supported('D')) {
+ ASSERT_EQ(errno, EINVAL);
+ SKIP(return, "kernel without the 'D' flag");
+ }
+
+ ASSERT_EQ(create_interp(), 0);
+ ASSERT_EQ(create_target(), 0);
+}
+
+FIXTURE_TEARDOWN(disabled)
+{
+ unregister(ENTRY);
+ unlink(TARGET_PATH);
+ unlink(INTERP_PATH);
+}
+
+/* The entry exists but does not dispatch until it is enabled. */
+TEST_F(disabled, inert_until_enabled)
+{
+ ASSERT_EQ(write_reg(RULE("D")), 0);
+ EXPECT_TRUE(entry_shows(ENTRY, "disabled"));
+
+ /* Nothing matches it, so no binary format claims the target. */
+ EXPECT_EQ(run_payload(TARGET_PATH), RUN_ENOEXEC);
+
+ ASSERT_EQ(entry_command(ENTRY, "1\n"), 0);
+ EXPECT_TRUE(entry_shows(ENTRY, "enabled"));
+ EXPECT_EQ(run_payload(TARGET_PATH), EXIT_INTERP);
+}
+
+/* Without 'D' an entry is matchable the moment it is registered. */
+TEST_F(disabled, enabled_without_the_flag)
+{
+ ASSERT_EQ(write_reg(RULE("")), 0);
+ EXPECT_TRUE(entry_shows(ENTRY, "enabled"));
+ EXPECT_EQ(run_payload(TARGET_PATH), EXIT_INTERP);
+}
+
+/* 'D' is spent on the registration: the entry does not report it back. */
+TEST_F(disabled, flag_not_reported)
+{
+ ASSERT_EQ(write_reg(RULE("D")), 0);
+ EXPECT_FALSE(entry_shows(ENTRY, "flags: D"));
+ EXPECT_TRUE(entry_shows(ENTRY, "flags: "));
+}
+
+/* A disabled entry can be disabled and enabled like any other. */
+TEST_F(disabled, toggles_like_any_entry)
+{
+ ASSERT_EQ(write_reg(RULE("D")), 0);
+
+ ASSERT_EQ(entry_command(ENTRY, "1\n"), 0);
+ ASSERT_EQ(run_payload(TARGET_PATH), EXIT_INTERP);
+ ASSERT_EQ(entry_command(ENTRY, "0\n"), 0);
+ EXPECT_EQ(run_payload(TARGET_PATH), RUN_ENOEXEC);
+ ASSERT_EQ(entry_command(ENTRY, "1\n"), 0);
+ EXPECT_EQ(run_payload(TARGET_PATH), EXIT_INTERP);
+}
+
+/* 'D' composes with the invocation flags a static entry can carry. */
+TEST_F(disabled, composes_with_invocation_flags)
+{
+ ASSERT_EQ(write_reg(RULE("PD")), 0);
+ EXPECT_TRUE(entry_shows(ENTRY, "disabled"));
+ EXPECT_TRUE(entry_shows(ENTRY, "flags: P"));
+}
+
+/* '-1' to the status file sweeps a staged entry with everything else. */
+TEST_F(disabled, removed_by_remove_all)
+{
+ int fd;
+
+ ASSERT_EQ(write_reg(RULE("D")), 0);
+ EXPECT_TRUE(entry_shows(ENTRY, "disabled"));
+
+ fd = open(BINFMT_DIR "/status", O_WRONLY | O_CLOEXEC);
+ ASSERT_GE(fd, 0);
+ ASSERT_EQ(write(fd, "-1", 2), 2);
+ close(fd);
+
+ EXPECT_NE(access(BINFMT_DIR "/" ENTRY, F_OK), 0);
+}
+
+/* A file handle held across a removal cannot resurrect the entry. */
+TEST_F(disabled, no_resurrection_after_remove)
+{
+ int fd;
+
+ ASSERT_EQ(write_reg(RULE("D")), 0);
+ fd = open(BINFMT_DIR "/" ENTRY, O_WRONLY | O_CLOEXEC);
+ ASSERT_GE(fd, 0);
+
+ ASSERT_EQ(write(fd, "-1", 2), 2);
+ EXPECT_NE(access(BINFMT_DIR "/" ENTRY, F_OK), 0);
+
+ /* Accepted like any toggle of a removed entry, but publishes nothing. */
+ EXPECT_EQ(write(fd, "1", 1), 1);
+ EXPECT_EQ(run_payload(TARGET_PATH), RUN_ENOEXEC);
+ close(fd);
+}
+
+TEST_HARNESS_MAIN