File Index
Files and directories
Sign in
FILE

check-exec.c

File preview with syntax highlighting.

linux-6.19.14/tools/testing/selftests/exec/check-exec.c C
  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 <mic@digikod.net>
 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
 35static 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
 41static 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
 61static 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
 71FIXTURE(access)
 72{
 73	int memfd, pipefd;
 74	int pipe_fds[2], socket_fds[2];
 75};
 76
 77FIXTURE_VARIANT(access)
 78{
 79	const bool mount_exec;
 80	const bool file_exec;
 81};
 82
 83/* clang-format off */
 84FIXTURE_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 */
 91FIXTURE_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 */
 98FIXTURE_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 */
105FIXTURE_VARIANT_ADD(access, mount_noexec_file_noexec) {
106	/* clang-format on */
107	.mount_exec = false,
108	.file_exec = false,
109};
110
111static const char binary_path[] = "./false";
112static const char workdir_path[] = "./test-mount";
113static const char reg_file_path[] = "./test-mount/regular_file";
114static const char dir_path[] = "./test-mount/directory";
115static const char block_dev_path[] = "./test-mount/block_device";
116static const char char_dev_path[] = "./test-mount/character_device";
117static const char fifo_path[] = "./test-mount/fifo";
118
119FIXTURE_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
173FIXTURE_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
180static 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
203static 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
217static 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
247static 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. */
267TEST_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
277TEST_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. */
290TEST_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
300TEST_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
312TEST_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 */
323FIXTURE(secbits) {};
324/* clang-format on */
325
326FIXTURE_VARIANT(secbits)
327{
328	const bool is_privileged;
329	const int error;
330};
331
332/* clang-format off */
333FIXTURE_VARIANT_ADD(secbits, priv) {
334	/* clang-format on */
335	.is_privileged = true,
336	.error = 0,
337};
338
339/* clang-format off */
340FIXTURE_VARIANT_ADD(secbits, unpriv) {
341	/* clang-format on */
342	.is_privileged = false,
343	.error = EPERM,
344};
345
346FIXTURE_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
356FIXTURE_TEARDOWN(secbits)
357{
358}
359
360TEST_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
375TEST_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
395TEST_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
413TEST_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
429TEST_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
447TEST_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
463TEST_HARNESS_MAIN
~/ Read-only file preview.