1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Test the 'D' (register disabled) flag of binfmt_misc. An entry 4 * registered with it exists but cannot be matched until userspace enables 5 * it, which splits a registration into create and activate. 6 * 7 * Needs root for the registration; no bpf toolchain involved. 8 */ 9 #define _GNU_SOURCE 10 #include <stdio.h> 11 #include <stdlib.h> 12 13 #include "binfmt_misc_common.h" 14 #include "kselftest_harness.h" 15 16 #define MAGIC "#DISABLED-SELFTEST#" 17 #define TARGET_PATH "/tmp/binfmt_disabled_target" 18 #define INTERP_PATH "/tmp/binfmt_disabled_interp.sh" 19 #define ENTRY "test_disabled" 20 #define RULE(flags) ":" ENTRY ":M:0:" MAGIC "::" INTERP_PATH ":" flags 21 22 /* The interpreter exits with a code the harness can recognise. */ 23 #define EXIT_INTERP 7 24 25 /* The target only has to carry the magic; it is never actually loaded. */ 26 static int create_target(void) 27 { 28 char buf[128] = MAGIC "\n"; 29 int fd; 30 31 unlink(TARGET_PATH); 32 fd = open(TARGET_PATH, O_WRONLY | O_CREAT | O_EXCL, 0755); 33 if (fd < 0) 34 return -1; 35 if (write(fd, buf, sizeof(buf)) != (ssize_t)sizeof(buf)) { 36 close(fd); 37 return -1; 38 } 39 close(fd); 40 return 0; 41 } 42 43 static int create_interp(void) 44 { 45 char buf[64]; 46 int fd; 47 48 unlink(INTERP_PATH); 49 fd = open(INTERP_PATH, O_WRONLY | O_CREAT | O_EXCL, 0755); 50 if (fd < 0) 51 return -1; 52 snprintf(buf, sizeof(buf), "#!/bin/sh\nexit %d\n", EXIT_INTERP); 53 if (write(fd, buf, strlen(buf)) != (ssize_t)strlen(buf)) { 54 close(fd); 55 return -1; 56 } 57 return close(fd); 58 } 59 60 FIXTURE(disabled) { 61 }; 62 63 FIXTURE_SETUP(disabled) 64 { 65 if (getuid() != 0) 66 SKIP(return, "test must be run as root"); 67 if (!binfmt_misc_available()) 68 SKIP(return, "no binfmt_misc"); 69 70 /* Skip the whole suite on a kernel that does not know 'D'. */ 71 if (!binfmt_flag_supported('D')) { 72 ASSERT_EQ(errno, EINVAL); 73 SKIP(return, "kernel without the 'D' flag"); 74 } 75 76 ASSERT_EQ(create_interp(), 0); 77 ASSERT_EQ(create_target(), 0); 78 } 79 80 FIXTURE_TEARDOWN(disabled) 81 { 82 unregister(ENTRY); 83 unlink(TARGET_PATH); 84 unlink(INTERP_PATH); 85 } 86 87 /* The entry exists but does not dispatch until it is enabled. */ 88 TEST_F(disabled, inert_until_enabled) 89 { 90 ASSERT_EQ(write_reg(RULE("D")), 0); 91 EXPECT_TRUE(entry_shows(ENTRY, "disabled")); 92 93 /* Nothing matches it, so no binary format claims the target. */ 94 EXPECT_EQ(run_payload(TARGET_PATH), RUN_ENOEXEC); 95 96 ASSERT_EQ(entry_command(ENTRY, "1\n"), 0); 97 EXPECT_TRUE(entry_shows(ENTRY, "enabled")); 98 EXPECT_EQ(run_payload(TARGET_PATH), EXIT_INTERP); 99 } 100 101 /* Without 'D' an entry is matchable the moment it is registered. */ 102 TEST_F(disabled, enabled_without_the_flag) 103 { 104 ASSERT_EQ(write_reg(RULE("")), 0); 105 EXPECT_TRUE(entry_shows(ENTRY, "enabled")); 106 EXPECT_EQ(run_payload(TARGET_PATH), EXIT_INTERP); 107 } 108 109 /* 'D' is spent on the registration: the entry does not report it back. */ 110 TEST_F(disabled, flag_not_reported) 111 { 112 ASSERT_EQ(write_reg(RULE("D")), 0); 113 EXPECT_FALSE(entry_shows(ENTRY, "flags: D")); 114 EXPECT_TRUE(entry_shows(ENTRY, "flags: ")); 115 } 116 117 /* A disabled entry can be disabled and enabled like any other. */ 118 TEST_F(disabled, toggles_like_any_entry) 119 { 120 ASSERT_EQ(write_reg(RULE("D")), 0); 121 122 ASSERT_EQ(entry_command(ENTRY, "1\n"), 0); 123 ASSERT_EQ(run_payload(TARGET_PATH), EXIT_INTERP); 124 ASSERT_EQ(entry_command(ENTRY, "0\n"), 0); 125 EXPECT_EQ(run_payload(TARGET_PATH), RUN_ENOEXEC); 126 ASSERT_EQ(entry_command(ENTRY, "1\n"), 0); 127 EXPECT_EQ(run_payload(TARGET_PATH), EXIT_INTERP); 128 } 129 130 /* 'D' composes with the invocation flags a static entry can carry. */ 131 TEST_F(disabled, composes_with_invocation_flags) 132 { 133 ASSERT_EQ(write_reg(RULE("PD")), 0); 134 EXPECT_TRUE(entry_shows(ENTRY, "disabled")); 135 EXPECT_TRUE(entry_shows(ENTRY, "flags: P")); 136 } 137 138 /* '-1' to the status file sweeps a staged entry with everything else. */ 139 TEST_F(disabled, removed_by_remove_all) 140 { 141 int fd; 142 143 ASSERT_EQ(write_reg(RULE("D")), 0); 144 EXPECT_TRUE(entry_shows(ENTRY, "disabled")); 145 146 fd = open(BINFMT_DIR "/status", O_WRONLY | O_CLOEXEC); 147 ASSERT_GE(fd, 0); 148 ASSERT_EQ(write(fd, "-1", 2), 2); 149 close(fd); 150 151 EXPECT_NE(access(BINFMT_DIR "/" ENTRY, F_OK), 0); 152 } 153 154 /* A file handle held across a removal cannot resurrect the entry. */ 155 TEST_F(disabled, no_resurrection_after_remove) 156 { 157 int fd; 158 159 ASSERT_EQ(write_reg(RULE("D")), 0); 160 fd = open(BINFMT_DIR "/" ENTRY, O_WRONLY | O_CLOEXEC); 161 ASSERT_GE(fd, 0); 162 163 ASSERT_EQ(write(fd, "-1", 2), 2); 164 EXPECT_NE(access(BINFMT_DIR "/" ENTRY, F_OK), 0); 165 166 /* Accepted like any toggle of a removed entry, but publishes nothing. */ 167 EXPECT_EQ(write(fd, "1", 1), 1); 168 EXPECT_EQ(run_payload(TARGET_PATH), RUN_ENOEXEC); 169 close(fd); 170 } 171 172 TEST_HARNESS_MAIN 173