1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Test execveat(2) with AT_EXECVE_CHECK, and prctl(2) with
4 * SECBIT_EXEC_RESTRICT_FILE, SECBIT_EXEC_DENY_INTERACTIVE, and their locked
5 * counterparts.
6 *
7 * Copyright © 2018-2020 ANSSI
8 * Copyright © 2024 Microsoft Corporation
9 *
10 * Author: Mickaël Salaün <[email protected]>
11 */
12
13 #include <asm-generic/unistd.h>
14 #include <errno.h>
15 #include <fcntl.h>
16 #include <linux/prctl.h>
17 #include <linux/securebits.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <sys/capability.h>
21 #include <sys/mount.h>
22 #include <sys/prctl.h>
23 #include <sys/socket.h>
24 #include <sys/stat.h>
25 #include <sys/syscall.h>
26 #include <sys/sysmacros.h>
27 #include <unistd.h>
28
29 /* Defines AT_EXECVE_CHECK without type conflicts. */
30 #define _ASM_GENERIC_FCNTL_H
31 #include <linux/fcntl.h>
32
33 #include "../kselftest_harness.h"
34
sys_execveat(int dirfd,const char * pathname,char * const argv[],char * const envp[],int flags)35 static int sys_execveat(int dirfd, const char *pathname, char *const argv[],
36 char *const envp[], int flags)
37 {
38 return syscall(__NR_execveat, dirfd, pathname, argv, envp, flags);
39 }
40
drop_privileges(struct __test_metadata * const _metadata)41 static void drop_privileges(struct __test_metadata *const _metadata)
42 {
43 const unsigned int noroot = SECBIT_NOROOT | SECBIT_NOROOT_LOCKED;
44 cap_t cap_p;
45
46 if ((cap_get_secbits() & noroot) != noroot)
47 EXPECT_EQ(0, cap_set_secbits(noroot));
48
49 cap_p = cap_get_proc();
50 EXPECT_NE(NULL, cap_p);
51 EXPECT_NE(-1, cap_clear(cap_p));
52
53 /*
54 * Drops everything, especially CAP_SETPCAP, CAP_DAC_OVERRIDE, and
55 * CAP_DAC_READ_SEARCH.
56 */
57 EXPECT_NE(-1, cap_set_proc(cap_p));
58 EXPECT_NE(-1, cap_free(cap_p));
59 }
60
test_secbits_set(const unsigned int secbits)61 static int test_secbits_set(const unsigned int secbits)
62 {
63 int err;
64
65 err = prctl(PR_SET_SECUREBITS, secbits);
66 if (err)
67 return errno;
68 return 0;
69 }
70
FIXTURE(access)71 FIXTURE(access)
72 {
73 int memfd, pipefd;
74 int pipe_fds[2], socket_fds[2];
75 };
76
FIXTURE_VARIANT(access)77 FIXTURE_VARIANT(access)
78 {
79 const bool mount_exec;
80 const bool file_exec;
81 };
82
83 /* clang-format off */
FIXTURE_VARIANT_ADD(access,mount_exec_file_exec)84 FIXTURE_VARIANT_ADD(access, mount_exec_file_exec) {
85 /* clang-format on */
86 .mount_exec = true,
87 .file_exec = true,
88 };
89
90 /* clang-format off */
FIXTURE_VARIANT_ADD(access,mount_exec_file_noexec)91 FIXTURE_VARIANT_ADD(access, mount_exec_file_noexec) {
92 /* clang-format on */
93 .mount_exec = true,
94 .file_exec = false,
95 };
96
97 /* clang-format off */
FIXTURE_VARIANT_ADD(access,mount_noexec_file_exec)98 FIXTURE_VARIANT_ADD(access, mount_noexec_file_exec) {
99 /* clang-format on */
100 .mount_exec = false,
101 .file_exec = true,
102 };
103
104 /* clang-format off */
FIXTURE_VARIANT_ADD(access,mount_noexec_file_noexec)105 FIXTURE_VARIANT_ADD(access, mount_noexec_file_noexec) {
106 /* clang-format on */
107 .mount_exec = false,
108 .file_exec = false,
109 };
110
111 static const char binary_path[] = "./false";
112 static const char workdir_path[] = "./test-mount";
113 static const char reg_file_path[] = "./test-mount/regular_file";
114 static const char dir_path[] = "./test-mount/directory";
115 static const char block_dev_path[] = "./test-mount/block_device";
116 static const char char_dev_path[] = "./test-mount/character_device";
117 static const char fifo_path[] = "./test-mount/fifo";
118
FIXTURE_SETUP(access)119 FIXTURE_SETUP(access)
120 {
121 int procfd_path_size;
122 static const char path_template[] = "/proc/self/fd/%d";
123 char procfd_path[sizeof(path_template) + 10];
124
125 /* Makes sure we are not already restricted nor locked. */
126 EXPECT_EQ(0, test_secbits_set(0));
127
128 /*
129 * Cleans previous workspace if any error previously happened (don't
130 * check errors).
131 */
132 umount(workdir_path);
133 rmdir(workdir_path);
134
135 /* Creates a clean mount point. */
136 ASSERT_EQ(0, mkdir(workdir_path, 00700));
137 ASSERT_EQ(0, mount("test", workdir_path, "tmpfs",
138 MS_MGC_VAL | (variant->mount_exec ? 0 : MS_NOEXEC),
139 "mode=0700,size=9m"));
140
141 /* Creates a regular file. */
142 ASSERT_EQ(0, mknod(reg_file_path,
143 S_IFREG | (variant->file_exec ? 0700 : 0600), 0));
144 /* Creates a directory. */
145 ASSERT_EQ(0, mkdir(dir_path, variant->file_exec ? 0700 : 0600));
146 /* Creates a character device: /dev/null. */
147 ASSERT_EQ(0, mknod(char_dev_path, S_IFCHR | 0400, makedev(1, 3)));
148 /* Creates a block device: /dev/loop0 */
149 ASSERT_EQ(0, mknod(block_dev_path, S_IFBLK | 0400, makedev(7, 0)));
150 /* Creates a fifo. */
151 ASSERT_EQ(0, mknod(fifo_path, S_IFIFO | 0600, 0));
152
153 /* Creates a regular file without user mount point. */
154 self->memfd = memfd_create("test-exec-probe", MFD_CLOEXEC);
155 ASSERT_LE(0, self->memfd);
156 /* Sets mode, which must be ignored by the exec check. */
157 ASSERT_EQ(0, fchmod(self->memfd, variant->file_exec ? 0700 : 0600));
158
159 /* Creates a pipefs file descriptor. */
160 ASSERT_EQ(0, pipe(self->pipe_fds));
161 procfd_path_size = snprintf(procfd_path, sizeof(procfd_path),
162 path_template, self->pipe_fds[0]);
163 ASSERT_LT(procfd_path_size, sizeof(procfd_path));
164 self->pipefd = open(procfd_path, O_RDWR | O_CLOEXEC);
165 ASSERT_LE(0, self->pipefd);
166 ASSERT_EQ(0, fchmod(self->pipefd, variant->file_exec ? 0700 : 0600));
167
168 /* Creates a socket file descriptor. */
169 ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0,
170 self->socket_fds));
171 }
172
FIXTURE_TEARDOWN_PARENT(access)173 FIXTURE_TEARDOWN_PARENT(access)
174 {
175 /* There is no need to unlink the test files. */
176 EXPECT_EQ(0, umount(workdir_path));
177 EXPECT_EQ(0, rmdir(workdir_path));
178 }
179
fill_exec_fd(struct __test_metadata * _metadata,const int fd_out)180 static void fill_exec_fd(struct __test_metadata *_metadata, const int fd_out)
181 {
182 char buf[1024];
183 size_t len;
184 int fd_in;
185
186 fd_in = open(binary_path, O_CLOEXEC | O_RDONLY);
187 ASSERT_LE(0, fd_in);
188 /* Cannot use copy_file_range(2) because of EXDEV. */
189 len = read(fd_in, buf, sizeof(buf));
190 EXPECT_LE(0, len);
191 while (len > 0) {
192 EXPECT_EQ(len, write(fd_out, buf, len))
193 {
194 TH_LOG("Failed to write: %s (%d)", strerror(errno),
195 errno);
196 }
197 len = read(fd_in, buf, sizeof(buf));
198 EXPECT_LE(0, len);
199 }
200 EXPECT_EQ(0, close(fd_in));
201 }
202
fill_exec_path(struct __test_metadata * _metadata,const char * const path)203 static void fill_exec_path(struct __test_metadata *_metadata,
204 const char *const path)
205 {
206 int fd_out;
207
208 fd_out = open(path, O_CLOEXEC | O_WRONLY);
209 ASSERT_LE(0, fd_out)
210 {
211 TH_LOG("Failed to open %s: %s", path, strerror(errno));
212 }
213 fill_exec_fd(_metadata, fd_out);
214 EXPECT_EQ(0, close(fd_out));
215 }
216
test_exec_fd(struct __test_metadata * _metadata,const int fd,const int err_code)217 static void test_exec_fd(struct __test_metadata *_metadata, const int fd,
218 const int err_code)
219 {
220 char *const argv[] = { "", NULL };
221 int access_ret, access_errno;
222
223 /*
224 * If we really execute fd, filled with the "false" binary, the current
225 * thread will exits with an error, which will be interpreted by the
226 * test framework as an error. With AT_EXECVE_CHECK, we only check a
227 * potential successful execution.
228 */
229 access_ret = sys_execveat(fd, "", argv, NULL,
230 AT_EMPTY_PATH | AT_EXECVE_CHECK);
231 access_errno = errno;
232 if (err_code) {
233 EXPECT_EQ(-1, access_ret);
234 EXPECT_EQ(err_code, access_errno)
235 {
236 TH_LOG("Wrong error for execveat(2): %s (%d)",
237 strerror(access_errno), errno);
238 }
239 } else {
240 EXPECT_EQ(0, access_ret)
241 {
242 TH_LOG("Access denied: %s", strerror(access_errno));
243 }
244 }
245 }
246
test_exec_path(struct __test_metadata * _metadata,const char * const path,const int err_code)247 static void test_exec_path(struct __test_metadata *_metadata,
248 const char *const path, const int err_code)
249 {
250 int flags = O_CLOEXEC;
251 int fd;
252
253 /* Do not block on pipes. */
254 if (path == fifo_path)
255 flags |= O_NONBLOCK;
256
257 fd = open(path, flags | O_RDONLY);
258 ASSERT_LE(0, fd)
259 {
260 TH_LOG("Failed to open %s: %s", path, strerror(errno));
261 }
262 test_exec_fd(_metadata, fd, err_code);
263 EXPECT_EQ(0, close(fd));
264 }
265
266 /* Tests that we don't get ENOEXEC. */
TEST_F(access,regular_file_empty)267 TEST_F(access, regular_file_empty)
268 {
269 const int exec = variant->mount_exec && variant->file_exec;
270
271 test_exec_path(_metadata, reg_file_path, exec ? 0 : EACCES);
272
273 drop_privileges(_metadata);
274 test_exec_path(_metadata, reg_file_path, exec ? 0 : EACCES);
275 }
276
TEST_F(access,regular_file_elf)277 TEST_F(access, regular_file_elf)
278 {
279 const int exec = variant->mount_exec && variant->file_exec;
280
281 fill_exec_path(_metadata, reg_file_path);
282
283 test_exec_path(_metadata, reg_file_path, exec ? 0 : EACCES);
284
285 drop_privileges(_metadata);
286 test_exec_path(_metadata, reg_file_path, exec ? 0 : EACCES);
287 }
288
289 /* Tests that we don't get ENOEXEC. */
TEST_F(access,memfd_empty)290 TEST_F(access, memfd_empty)
291 {
292 const int exec = variant->file_exec;
293
294 test_exec_fd(_metadata, self->memfd, exec ? 0 : EACCES);
295
296 drop_privileges(_metadata);
297 test_exec_fd(_metadata, self->memfd, exec ? 0 : EACCES);
298 }
299
TEST_F(access,memfd_elf)300 TEST_F(access, memfd_elf)
301 {
302 const int exec = variant->file_exec;
303
304 fill_exec_fd(_metadata, self->memfd);
305
306 test_exec_fd(_metadata, self->memfd, exec ? 0 : EACCES);
307
308 drop_privileges(_metadata);
309 test_exec_fd(_metadata, self->memfd, exec ? 0 : EACCES);
310 }
311
TEST_F(access,non_regular_files)312 TEST_F(access, non_regular_files)
313 {
314 test_exec_path(_metadata, dir_path, EACCES);
315 test_exec_path(_metadata, block_dev_path, EACCES);
316 test_exec_path(_metadata, char_dev_path, EACCES);
317 test_exec_path(_metadata, fifo_path, EACCES);
318 test_exec_fd(_metadata, self->socket_fds[0], EACCES);
319 test_exec_fd(_metadata, self->pipefd, EACCES);
320 }
321
322 /* clang-format off */
FIXTURE(secbits)323 FIXTURE(secbits) {};
324 /* clang-format on */
325
FIXTURE_VARIANT(secbits)326 FIXTURE_VARIANT(secbits)
327 {
328 const bool is_privileged;
329 const int error;
330 };
331
332 /* clang-format off */
FIXTURE_VARIANT_ADD(secbits,priv)333 FIXTURE_VARIANT_ADD(secbits, priv) {
334 /* clang-format on */
335 .is_privileged = true,
336 .error = 0,
337 };
338
339 /* clang-format off */
FIXTURE_VARIANT_ADD(secbits,unpriv)340 FIXTURE_VARIANT_ADD(secbits, unpriv) {
341 /* clang-format on */
342 .is_privileged = false,
343 .error = EPERM,
344 };
345
FIXTURE_SETUP(secbits)346 FIXTURE_SETUP(secbits)
347 {
348 /* Makes sure no exec bits are set. */
349 EXPECT_EQ(0, test_secbits_set(0));
350 EXPECT_EQ(0, prctl(PR_GET_SECUREBITS));
351
352 if (!variant->is_privileged)
353 drop_privileges(_metadata);
354 }
355
FIXTURE_TEARDOWN(secbits)356 FIXTURE_TEARDOWN(secbits)
357 {
358 }
359
TEST_F(secbits,legacy)360 TEST_F(secbits, legacy)
361 {
362 EXPECT_EQ(variant->error, test_secbits_set(0));
363 }
364
365 #define CHILD(...) \
366 do { \
367 pid_t child = vfork(); \
368 EXPECT_LE(0, child); \
369 if (child == 0) { \
370 __VA_ARGS__; \
371 _exit(0); \
372 } \
373 } while (0)
374
TEST_F(secbits,exec)375 TEST_F(secbits, exec)
376 {
377 unsigned int secbits = prctl(PR_GET_SECUREBITS);
378
379 secbits |= SECBIT_EXEC_RESTRICT_FILE;
380 EXPECT_EQ(0, test_secbits_set(secbits));
381 EXPECT_EQ(secbits, prctl(PR_GET_SECUREBITS));
382 CHILD(EXPECT_EQ(secbits, prctl(PR_GET_SECUREBITS)));
383
384 secbits |= SECBIT_EXEC_DENY_INTERACTIVE;
385 EXPECT_EQ(0, test_secbits_set(secbits));
386 EXPECT_EQ(secbits, prctl(PR_GET_SECUREBITS));
387 CHILD(EXPECT_EQ(secbits, prctl(PR_GET_SECUREBITS)));
388
389 secbits &= ~(SECBIT_EXEC_RESTRICT_FILE | SECBIT_EXEC_DENY_INTERACTIVE);
390 EXPECT_EQ(0, test_secbits_set(secbits));
391 EXPECT_EQ(secbits, prctl(PR_GET_SECUREBITS));
392 CHILD(EXPECT_EQ(secbits, prctl(PR_GET_SECUREBITS)));
393 }
394
TEST_F(secbits,check_locked_set)395 TEST_F(secbits, check_locked_set)
396 {
397 unsigned int secbits = prctl(PR_GET_SECUREBITS);
398
399 secbits |= SECBIT_EXEC_RESTRICT_FILE;
400 EXPECT_EQ(0, test_secbits_set(secbits));
401 secbits |= SECBIT_EXEC_RESTRICT_FILE_LOCKED;
402 EXPECT_EQ(0, test_secbits_set(secbits));
403
404 /* Checks lock set but unchanged. */
405 EXPECT_EQ(variant->error, test_secbits_set(secbits));
406 CHILD(EXPECT_EQ(variant->error, test_secbits_set(secbits)));
407
408 secbits &= ~SECBIT_EXEC_RESTRICT_FILE;
409 EXPECT_EQ(EPERM, test_secbits_set(0));
410 CHILD(EXPECT_EQ(EPERM, test_secbits_set(0)));
411 }
412
TEST_F(secbits,check_locked_unset)413 TEST_F(secbits, check_locked_unset)
414 {
415 unsigned int secbits = prctl(PR_GET_SECUREBITS);
416
417 secbits |= SECBIT_EXEC_RESTRICT_FILE_LOCKED;
418 EXPECT_EQ(0, test_secbits_set(secbits));
419
420 /* Checks lock unset but unchanged. */
421 EXPECT_EQ(variant->error, test_secbits_set(secbits));
422 CHILD(EXPECT_EQ(variant->error, test_secbits_set(secbits)));
423
424 secbits &= ~SECBIT_EXEC_RESTRICT_FILE;
425 EXPECT_EQ(EPERM, test_secbits_set(0));
426 CHILD(EXPECT_EQ(EPERM, test_secbits_set(0)));
427 }
428
TEST_F(secbits,restrict_locked_set)429 TEST_F(secbits, restrict_locked_set)
430 {
431 unsigned int secbits = prctl(PR_GET_SECUREBITS);
432
433 secbits |= SECBIT_EXEC_DENY_INTERACTIVE;
434 EXPECT_EQ(0, test_secbits_set(secbits));
435 secbits |= SECBIT_EXEC_DENY_INTERACTIVE_LOCKED;
436 EXPECT_EQ(0, test_secbits_set(secbits));
437
438 /* Checks lock set but unchanged. */
439 EXPECT_EQ(variant->error, test_secbits_set(secbits));
440 CHILD(EXPECT_EQ(variant->error, test_secbits_set(secbits)));
441
442 secbits &= ~SECBIT_EXEC_DENY_INTERACTIVE;
443 EXPECT_EQ(EPERM, test_secbits_set(0));
444 CHILD(EXPECT_EQ(EPERM, test_secbits_set(0)));
445 }
446
TEST_F(secbits,restrict_locked_unset)447 TEST_F(secbits, restrict_locked_unset)
448 {
449 unsigned int secbits = prctl(PR_GET_SECUREBITS);
450
451 secbits |= SECBIT_EXEC_DENY_INTERACTIVE_LOCKED;
452 EXPECT_EQ(0, test_secbits_set(secbits));
453
454 /* Checks lock unset but unchanged. */
455 EXPECT_EQ(variant->error, test_secbits_set(secbits));
456 CHILD(EXPECT_EQ(variant->error, test_secbits_set(secbits)));
457
458 secbits &= ~SECBIT_EXEC_DENY_INTERACTIVE;
459 EXPECT_EQ(EPERM, test_secbits_set(0));
460 CHILD(EXPECT_EQ(EPERM, test_secbits_set(0)));
461 }
462
463 TEST_HARNESS_MAIN
464