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