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