xref: /linux/tools/testing/selftests/exec/binfmt_misc_disabled.c (revision b9cba7ebfe539f3e4bbdd03a1e0efa3b30b3f592)
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