1
2
3
4
5
6
7
8
9#define _GNU_SOURCE
10#include <errno.h>
11#include <fcntl.h>
12#include <linux/landlock.h>
13#include <signal.h>
14#include <sys/prctl.h>
15#include <sys/ptrace.h>
16#include <sys/types.h>
17#include <sys/wait.h>
18#include <unistd.h>
19
20#include "common.h"
21
22static void create_domain(struct __test_metadata *const _metadata)
23{
24 int ruleset_fd;
25 struct landlock_ruleset_attr ruleset_attr = {
26 .handled_access_fs = LANDLOCK_ACCESS_FS_MAKE_BLOCK,
27 };
28
29 ruleset_fd = landlock_create_ruleset(&ruleset_attr,
30 sizeof(ruleset_attr), 0);
31 EXPECT_LE(0, ruleset_fd) {
32 TH_LOG("Failed to create a ruleset: %s", strerror(errno));
33 }
34 EXPECT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
35 EXPECT_EQ(0, landlock_restrict_self(ruleset_fd, 0));
36 EXPECT_EQ(0, close(ruleset_fd));
37}
38
39static int test_ptrace_read(const pid_t pid)
40{
41 static const char path_template[] = "/proc/%d/environ";
42 char procenv_path[sizeof(path_template) + 10];
43 int procenv_path_size, fd;
44
45 procenv_path_size = snprintf(procenv_path, sizeof(procenv_path),
46 path_template, pid);
47 if (procenv_path_size >= sizeof(procenv_path))
48 return E2BIG;
49
50 fd = open(procenv_path, O_RDONLY | O_CLOEXEC);
51 if (fd < 0)
52 return errno;
53
54
55
56
57 if (close(fd) != 0)
58 return errno;
59 return 0;
60}
61
62FIXTURE(hierarchy) { };
63
64FIXTURE_VARIANT(hierarchy) {
65 const bool domain_both;
66 const bool domain_parent;
67 const bool domain_child;
68};
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86FIXTURE_VARIANT_ADD(hierarchy, allow_without_domain) {
87 .domain_both = false,
88 .domain_parent = false,
89 .domain_child = false,
90};
91
92
93
94
95
96
97
98
99
100
101FIXTURE_VARIANT_ADD(hierarchy, allow_with_one_domain) {
102 .domain_both = false,
103 .domain_parent = false,
104 .domain_child = true,
105};
106
107
108
109
110
111
112
113
114
115FIXTURE_VARIANT_ADD(hierarchy, deny_with_parent_domain) {
116 .domain_both = false,
117 .domain_parent = true,
118 .domain_child = false,
119};
120
121
122
123
124
125
126
127
128
129
130FIXTURE_VARIANT_ADD(hierarchy, deny_with_sibling_domain) {
131 .domain_both = false,
132 .domain_parent = true,
133 .domain_child = true,
134};
135
136
137
138
139
140
141
142
143
144
145FIXTURE_VARIANT_ADD(hierarchy, allow_sibling_domain) {
146 .domain_both = true,
147 .domain_parent = false,
148 .domain_child = false,
149};
150
151
152
153
154
155
156
157
158
159
160
161FIXTURE_VARIANT_ADD(hierarchy, allow_with_nested_domain) {
162 .domain_both = true,
163 .domain_parent = false,
164 .domain_child = true,
165};
166
167
168
169
170
171
172
173
174
175
176
177FIXTURE_VARIANT_ADD(hierarchy, deny_with_nested_and_parent_domain) {
178 .domain_both = true,
179 .domain_parent = true,
180 .domain_child = false,
181};
182
183
184
185
186
187
188
189
190
191
192
193
194
195FIXTURE_VARIANT_ADD(hierarchy, deny_with_forked_domain) {
196 .domain_both = true,
197 .domain_parent = true,
198 .domain_child = true,
199};
200
201FIXTURE_SETUP(hierarchy)
202{ }
203
204FIXTURE_TEARDOWN(hierarchy)
205{ }
206
207
208TEST_F(hierarchy, trace)
209{
210 pid_t child, parent;
211 int status, err_proc_read;
212 int pipe_child[2], pipe_parent[2];
213 char buf_parent;
214 long ret;
215
216
217
218
219
220 drop_caps(_metadata);
221
222 parent = getpid();
223 ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC));
224 ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC));
225 if (variant->domain_both) {
226 create_domain(_metadata);
227 if (!_metadata->passed)
228
229 return;
230 }
231
232 child = fork();
233 ASSERT_LE(0, child);
234 if (child == 0) {
235 char buf_child;
236
237 ASSERT_EQ(0, close(pipe_parent[1]));
238 ASSERT_EQ(0, close(pipe_child[0]));
239 if (variant->domain_child)
240 create_domain(_metadata);
241
242
243 ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
244
245
246 err_proc_read = test_ptrace_read(parent);
247 ret = ptrace(PTRACE_ATTACH, parent, NULL, 0);
248 if (variant->domain_child) {
249 EXPECT_EQ(-1, ret);
250 EXPECT_EQ(EPERM, errno);
251 EXPECT_EQ(EACCES, err_proc_read);
252 } else {
253 EXPECT_EQ(0, ret);
254 EXPECT_EQ(0, err_proc_read);
255 }
256 if (ret == 0) {
257 ASSERT_EQ(parent, waitpid(parent, &status, 0));
258 ASSERT_EQ(1, WIFSTOPPED(status));
259 ASSERT_EQ(0, ptrace(PTRACE_DETACH, parent, NULL, 0));
260 }
261
262
263 ret = ptrace(PTRACE_TRACEME);
264 if (variant->domain_parent) {
265 EXPECT_EQ(-1, ret);
266 EXPECT_EQ(EPERM, errno);
267 } else {
268 EXPECT_EQ(0, ret);
269 }
270
271
272
273
274
275 ASSERT_EQ(1, write(pipe_child[1], ".", 1));
276
277 if (!variant->domain_parent) {
278 ASSERT_EQ(0, raise(SIGSTOP));
279 }
280
281
282 ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1));
283 _exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE);
284 return;
285 }
286
287 ASSERT_EQ(0, close(pipe_child[1]));
288 ASSERT_EQ(0, close(pipe_parent[0]));
289 if (variant->domain_parent)
290 create_domain(_metadata);
291
292
293 ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
294
295
296
297
298
299 ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1));
300
301
302 if (!variant->domain_parent) {
303 ASSERT_EQ(child, waitpid(child, &status, 0));
304 ASSERT_EQ(1, WIFSTOPPED(status));
305 ASSERT_EQ(0, ptrace(PTRACE_DETACH, child, NULL, 0));
306 } else {
307
308 EXPECT_EQ(-1, ptrace(PTRACE_DETACH, child, NULL, 0));
309 EXPECT_EQ(ESRCH, errno);
310 }
311
312
313 err_proc_read = test_ptrace_read(child);
314 ret = ptrace(PTRACE_ATTACH, child, NULL, 0);
315 if (variant->domain_parent) {
316 EXPECT_EQ(-1, ret);
317 EXPECT_EQ(EPERM, errno);
318 EXPECT_EQ(EACCES, err_proc_read);
319 } else {
320 EXPECT_EQ(0, ret);
321 EXPECT_EQ(0, err_proc_read);
322 }
323 if (ret == 0) {
324 ASSERT_EQ(child, waitpid(child, &status, 0));
325 ASSERT_EQ(1, WIFSTOPPED(status));
326 ASSERT_EQ(0, ptrace(PTRACE_DETACH, child, NULL, 0));
327 }
328
329
330 ASSERT_EQ(1, write(pipe_parent[1], ".", 1));
331 ASSERT_EQ(child, waitpid(child, &status, 0));
332 if (WIFSIGNALED(status) || !WIFEXITED(status) ||
333 WEXITSTATUS(status) != EXIT_SUCCESS)
334 _metadata->passed = 0;
335}
336
337TEST_HARNESS_MAIN
338