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