1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Test the 'L' (loader substitution) flag of binfmt_misc. A matched 4 * binary runs as the MAIN image - a fully native exec - with the 5 * registered interpreter substituted for its PT_INTERP. The payload 6 * (binfmt_loader_payload) asserts the native identity from inside. 7 * 8 * The substitute is a copy of the system loader found via our own 9 * PT_INTERP; magic matching pokes a marker into the ELF header's 10 * e_ident padding, which kernel and loader ignore. 11 * 12 * Needs root for the registration; no bpf toolchain involved. 13 */ 14 #define _GNU_SOURCE 15 #include <elf.h> 16 #include <link.h> 17 #include <signal.h> 18 #include <stddef.h> 19 #include <stdio.h> 20 #include <stdlib.h> 21 #include <sys/mman.h> 22 #include <sys/ptrace.h> 23 #include <sys/syscall.h> 24 #include <sys/wait.h> 25 26 #include "binfmt_misc_common.h" 27 #include "kselftest_harness.h" 28 29 #define ENTRY "test_loader" 30 #define INTERP_PATH "/tmp/binfmt_loader_interp" 31 #define MOVED_PATH INTERP_PATH ".moved" 32 #define TARGET_PATH "/tmp/binfmt_loader_target.ldrtest" 33 #define STATIC_PATH "/tmp/binfmt_loader_static.ldrtest" 34 #define FOREIGN_PATH "/tmp/binfmt_loader_foreign.ldrtest" 35 #define SCRIPT_PATH "/tmp/binfmt_loader_script.ldrtest" 36 #define M_RULE ":" ENTRY ":M:9:" LOADER_MARKER "::" INTERP_PATH ":L" 37 #define E_RULE ":" ENTRY ":E::ldrtest::" INTERP_PATH ":L" 38 #define FL_RULE ":" ENTRY ":E::ldrtest::" INTERP_PATH ":FL" 39 40 /* Execute the binary from an inaccessible O_CLOEXEC memfd. */ 41 static int run_memfd(const char *path) 42 { 43 int status; 44 pid_t pid; 45 46 pid = fork(); 47 if (pid == 0) { 48 char *argv[] = { PAYLOAD_ARGV0, PAYLOAD_ARG1, PAYLOAD_ARG2, NULL }; 49 char buf[4096]; 50 int in, mfd; 51 ssize_t n; 52 53 mfd = memfd_create("loader-test", MFD_CLOEXEC); 54 in = open(path, O_RDONLY); 55 if (mfd < 0 || in < 0) 56 _exit(125); 57 while ((n = read(in, buf, sizeof(buf))) > 0) 58 if (write(mfd, buf, n) != n) 59 _exit(125); 60 close(in); 61 setenv("BINFMT_TEST_MEMFD", "1", 1); 62 unsetenv("BINFMT_TEST_BINARY"); 63 syscall(SYS_execveat, mfd, "", argv, environ, AT_EMPTY_PATH); 64 _exit(126); 65 } 66 if (pid < 0 || waitpid(pid, &status, 0) != pid || !WIFEXITED(status)) 67 return -1; 68 return WEXITSTATUS(status); 69 } 70 71 /* 72 * The differentiator against the transparent mode: at PTRACE_EVENT_EXEC 73 * the identity is already complete - exe, auxv and the stat code markers 74 * are mutually consistent with no window a debugger could observe. 75 */ 76 static int ptrace_probe(const char *target) 77 { 78 unsigned long auxv[2 * 64], base = 0, entry = 0, at_flags = 0; 79 unsigned long start_code = 0, end_code = 0; 80 int status, fd, execfd_seen = 0, failed = 0; 81 char path[64], buf[PATH_MAX]; 82 ssize_t n; 83 pid_t pid; 84 int i; 85 86 pid = fork(); 87 if (pid == 0) { 88 ptrace(PTRACE_TRACEME, 0, NULL, NULL); 89 raise(SIGSTOP); 90 execl(target, PAYLOAD_ARGV0, PAYLOAD_ARG1, PAYLOAD_ARG2, (char *)NULL); 91 _exit(126); 92 } 93 if (pid < 0) 94 return -1; 95 if (waitpid(pid, &status, 0) != pid || !WIFSTOPPED(status)) 96 goto fail_kill; 97 if (ptrace(PTRACE_SETOPTIONS, pid, NULL, (void *)PTRACE_O_TRACEEXEC)) 98 goto fail_kill; 99 if (ptrace(PTRACE_CONT, pid, NULL, NULL)) 100 goto fail_kill; 101 if (waitpid(pid, &status, 0) != pid || !WIFSTOPPED(status) || 102 status >> 8 != (SIGTRAP | (PTRACE_EVENT_EXEC << 8))) { 103 fprintf(stderr, "no exec stop (status %#x)\n", status); 104 goto fail_kill; 105 } 106 107 snprintf(path, sizeof(path), "/proc/%d/exe", pid); 108 n = readlink(path, buf, sizeof(buf) - 1); 109 if (n <= 0) { 110 failed = 1; 111 } else { 112 buf[n] = '\0'; 113 if (strcmp(buf, target)) { 114 fprintf(stderr, "exe at exec stop: %s\n", buf); 115 failed = 1; 116 } 117 } 118 119 snprintf(path, sizeof(path), "/proc/%d/auxv", pid); 120 fd = open(path, O_RDONLY); 121 if (fd < 0) { 122 n = -1; 123 } else { 124 n = read(fd, auxv, sizeof(auxv)); 125 close(fd); 126 } 127 if (n <= 0) { 128 failed = 1; 129 n = 0; 130 } 131 for (i = 0; i + 1 < (int)(n / sizeof(unsigned long)); i += 2) { 132 switch (auxv[i]) { 133 case AT_BASE: 134 base = auxv[i + 1]; 135 break; 136 case AT_ENTRY: 137 entry = auxv[i + 1]; 138 break; 139 case AT_FLAGS: 140 at_flags = auxv[i + 1]; 141 break; 142 case AT_EXECFD: 143 execfd_seen = 1; 144 break; 145 } 146 } 147 148 if (stat_codes(pid, &start_code, &end_code)) 149 failed = 1; 150 151 if (!base || execfd_seen || at_flags) { 152 fprintf(stderr, "auxv at exec stop not native\n"); 153 failed = 1; 154 } 155 if (!start_code || entry < start_code || entry >= end_code) { 156 fprintf(stderr, "auxv/stat inconsistent at exec stop\n"); 157 failed = 1; 158 } 159 160 if (ptrace(PTRACE_CONT, pid, NULL, NULL)) 161 goto fail_kill; 162 if (waitpid(pid, &status, 0) != pid || !WIFEXITED(status) || 163 WEXITSTATUS(status)) 164 failed = 1; 165 return failed ? -1 : 0; 166 167 fail_kill: 168 kill(pid, SIGKILL); 169 waitpid(pid, &status, 0); 170 return -1; 171 } 172 173 FIXTURE(loader) { 174 bool have_static; 175 }; 176 177 FIXTURE_SETUP(loader) 178 { 179 unsigned short foreign_machine = 0xdead; 180 char src[PATH_MAX], loader[PATH_MAX]; 181 182 if (getuid() != 0) 183 SKIP(return, "test must be run as root"); 184 if (!binfmt_misc_available()) 185 SKIP(return, "no binfmt_misc"); 186 if (find_loader(loader, sizeof(loader))) 187 SKIP(return, "cannot determine own PT_INTERP"); 188 189 ASSERT_EQ(copy_file(loader, INTERP_PATH), 0); 190 191 ASSERT_EQ(artifact_path(src, sizeof(src), "binfmt_loader_payload"), 0); 192 ASSERT_EQ(copy_file(src, TARGET_PATH), 0); 193 ASSERT_EQ(patch_file(TARGET_PATH, EI_PAD, LOADER_MARKER, 194 strlen(LOADER_MARKER)), 0); 195 196 /* The same payload with a machine type this kernel cannot load. */ 197 ASSERT_EQ(copy_file(src, FOREIGN_PATH), 0); 198 ASSERT_EQ(patch_file(FOREIGN_PATH, EI_PAD, LOADER_MARKER, 199 strlen(LOADER_MARKER)), 0); 200 ASSERT_EQ(patch_file(FOREIGN_PATH, offsetof(ElfW(Ehdr), e_machine), 201 &foreign_machine, sizeof(foreign_machine)), 0); 202 203 self->have_static = 204 artifact_path(src, sizeof(src), "binfmt_loader_payload_static") == 0 && 205 copy_file(src, STATIC_PATH) == 0; 206 207 setenv("BINFMT_TEST_BINARY", TARGET_PATH, 1); 208 setenv("BINFMT_TEST_INTERP", INTERP_PATH, 1); 209 210 /* Everything below needs the flag; find out once. */ 211 if (write_reg(E_RULE)) { 212 ASSERT_EQ(errno, EINVAL); 213 SKIP(return, "kernel without the 'L' flag"); 214 } 215 unregister(ENTRY); 216 } 217 218 FIXTURE_TEARDOWN(loader) 219 { 220 unregister(ENTRY); 221 if (access(MOVED_PATH, F_OK) == 0) 222 rename(MOVED_PATH, INTERP_PATH); 223 unlink(TARGET_PATH); 224 unlink(STATIC_PATH); 225 unlink(FOREIGN_PATH); 226 unlink(SCRIPT_PATH); 227 unlink(INTERP_PATH); 228 } 229 230 /* Grammar sanity check: the same entry without 'L' has to register. */ 231 TEST_F(loader, plain_entry_registers) 232 { 233 ASSERT_EQ(write_reg(":" ENTRY ":E::ldrtest::" INTERP_PATH ":"), 0); 234 } 235 236 /* 'L' is a native exec: every classic-dispatch flag is rejected. */ 237 TEST_F(loader, rejects_classic_flags) 238 { 239 static const char * const combos[] = { "LT", "LP", "LC", "LO" }; 240 char rule[PATH_MAX]; 241 unsigned int i; 242 243 for (i = 0; i < ARRAY_SIZE(combos); i++) { 244 int rc; 245 246 snprintf(rule, sizeof(rule), 247 ":" ENTRY ":E::ldrtest::" INTERP_PATH ":%s", combos[i]); 248 rc = write_reg(rule); 249 EXPECT_EQ(rc, -1) 250 TH_LOG("'%s' was not rejected", combos[i]); 251 if (rc == 0) { 252 unregister(ENTRY); 253 continue; 254 } 255 EXPECT_EQ(errno, EINVAL); 256 } 257 } 258 259 /* 260 * Without 'F' the interpreter is opened when the binary is executed, so a 261 * relative path would be resolved against the caller's working directory. 262 */ 263 TEST_F(loader, rejects_relative_interpreter) 264 { 265 static const char * const flags[] = { "L", "C" }; 266 char rule[PATH_MAX]; 267 unsigned int i; 268 269 for (i = 0; i < ARRAY_SIZE(flags); i++) { 270 int rc; 271 272 snprintf(rule, sizeof(rule), 273 ":" ENTRY ":E::ldrtest::binfmt_loader_interp:%s", 274 flags[i]); 275 rc = write_reg(rule); 276 EXPECT_EQ(rc, -1) 277 TH_LOG("'%s' accepted a relative interpreter", flags[i]); 278 if (rc == 0) { 279 unregister(ENTRY); 280 continue; 281 } 282 EXPECT_EQ(errno, EINVAL); 283 } 284 } 285 286 TEST_F(loader, extension_matched) 287 { 288 ASSERT_EQ(write_reg(E_RULE), 0); 289 EXPECT_EQ(run_payload(TARGET_PATH), 0); 290 } 291 292 TEST_F(loader, magic_matched) 293 { 294 ASSERT_EQ(write_reg(M_RULE), 0); 295 EXPECT_EQ(run_payload(TARGET_PATH), 0); 296 } 297 298 /* 299 * The differentiator against the transparent mode: at PTRACE_EVENT_EXEC the 300 * identity is already complete, with no window a debugger could observe. 301 */ 302 TEST_F(loader, exec_stop_consistency) 303 { 304 ASSERT_EQ(write_reg(E_RULE), 0); 305 EXPECT_EQ(ptrace_probe(TARGET_PATH), 0); 306 } 307 308 /* A binary without PT_INTERP drops the override and runs natively. */ 309 TEST_F(loader, static_binary_runs_natively) 310 { 311 if (!self->have_static) 312 SKIP(return, "no static payload built"); 313 314 ASSERT_EQ(write_reg(E_RULE), 0); 315 setenv("BINFMT_TEST_BINARY", STATIC_PATH, 1); 316 setenv("BINFMT_TEST_STATIC", "1", 1); 317 EXPECT_EQ(run_payload(STATIC_PATH), 0); 318 unsetenv("BINFMT_TEST_STATIC"); 319 setenv("BINFMT_TEST_BINARY", TARGET_PATH, 1); 320 } 321 322 /* 323 * A '#!' file that matched an 'L' entry is claimed by binfmt_script, which 324 * sits ahead of binfmt_elf. The substitute the entry staged has to be 325 * released when the interpreter replaces the file, not leaked. 326 */ 327 TEST_F(loader, script_claims_the_file) 328 { 329 static const char script[] = "#!/bin/sh\nexit 0\n"; 330 int fd; 331 332 unlink(SCRIPT_PATH); 333 fd = open(SCRIPT_PATH, O_WRONLY | O_CREAT | O_EXCL, 0755); 334 ASSERT_GE(fd, 0); 335 ASSERT_EQ(write(fd, script, sizeof(script) - 1), 336 (ssize_t)sizeof(script) - 1); 337 ASSERT_EQ(close(fd), 0); 338 339 ASSERT_EQ(write_reg(E_RULE), 0); 340 EXPECT_EQ(run_payload(SCRIPT_PATH), 0); 341 342 /* A leaked substitute keeps its write denial on the loader. */ 343 fd = open(INTERP_PATH, O_WRONLY); 344 EXPECT_GE(fd, 0) 345 TH_LOG("loader still write denied (errno %d)", errno); 346 if (fd >= 0) 347 close(fd); 348 } 349 350 /* Nothing needs the binary's path, so an inaccessible fd works. */ 351 TEST_F(loader, inaccessible_memfd) 352 { 353 ASSERT_EQ(write_reg(M_RULE), 0); 354 EXPECT_EQ(run_memfd(TARGET_PATH), 0); 355 } 356 357 /* The whole exec of a wrong-arch binary fails as if unhandled. */ 358 TEST_F(loader, foreign_arch_enoexec) 359 { 360 ASSERT_EQ(write_reg(M_RULE), 0); 361 EXPECT_EQ(run_payload(FOREIGN_PATH), RUN_ENOEXEC); 362 } 363 364 /* 'F' pre-opens the substitute, so it survives losing its path. */ 365 TEST_F(loader, fixed_interpreter_survives_rename) 366 { 367 ASSERT_EQ(write_reg(FL_RULE), 0); 368 ASSERT_EQ(rename(INTERP_PATH, MOVED_PATH), 0); 369 EXPECT_EQ(run_payload(TARGET_PATH), 0); 370 } 371 372 TEST_HARNESS_MAIN 373