projects/linux·blob master · refs

172 lines · 4.4 KiB

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. */
26static 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
43static 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
60FIXTURE(disabled) {
61};
62
63FIXTURE_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
80FIXTURE_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. */
88TEST_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. */
102TEST_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. */
110TEST_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. */
118TEST_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. */
131TEST_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. */
139TEST_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. */
155TEST_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
172TEST_HARNESS_MAIN