xref: /openbmc/linux/samples/landlock/sandboxer.c (revision 81709f3d)
1ba84b0bfSMickaël Salaün // SPDX-License-Identifier: BSD-3-Clause
2ba84b0bfSMickaël Salaün /*
3ba84b0bfSMickaël Salaün  * Simple Landlock sandbox manager able to launch a process restricted by a
4ba84b0bfSMickaël Salaün  * user-defined filesystem access control policy.
5ba84b0bfSMickaël Salaün  *
6ba84b0bfSMickaël Salaün  * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
7ba84b0bfSMickaël Salaün  * Copyright © 2020 ANSSI
8ba84b0bfSMickaël Salaün  */
9ba84b0bfSMickaël Salaün 
10ba84b0bfSMickaël Salaün #define _GNU_SOURCE
11ba84b0bfSMickaël Salaün #include <errno.h>
12ba84b0bfSMickaël Salaün #include <fcntl.h>
13ba84b0bfSMickaël Salaün #include <linux/landlock.h>
14ba84b0bfSMickaël Salaün #include <linux/prctl.h>
15ba84b0bfSMickaël Salaün #include <stddef.h>
16ba84b0bfSMickaël Salaün #include <stdio.h>
17ba84b0bfSMickaël Salaün #include <stdlib.h>
18ba84b0bfSMickaël Salaün #include <string.h>
19ba84b0bfSMickaël Salaün #include <sys/prctl.h>
20ba84b0bfSMickaël Salaün #include <sys/stat.h>
21ba84b0bfSMickaël Salaün #include <sys/syscall.h>
22ba84b0bfSMickaël Salaün #include <unistd.h>
23ba84b0bfSMickaël Salaün 
24ba84b0bfSMickaël Salaün #ifndef landlock_create_ruleset
25*81709f3dSMickaël Salaün static inline int
26*81709f3dSMickaël Salaün landlock_create_ruleset(const struct landlock_ruleset_attr *const attr,
27ba84b0bfSMickaël Salaün 			const size_t size, const __u32 flags)
28ba84b0bfSMickaël Salaün {
29ba84b0bfSMickaël Salaün 	return syscall(__NR_landlock_create_ruleset, attr, size, flags);
30ba84b0bfSMickaël Salaün }
31ba84b0bfSMickaël Salaün #endif
32ba84b0bfSMickaël Salaün 
33ba84b0bfSMickaël Salaün #ifndef landlock_add_rule
34ba84b0bfSMickaël Salaün static inline int landlock_add_rule(const int ruleset_fd,
35ba84b0bfSMickaël Salaün 				    const enum landlock_rule_type rule_type,
36*81709f3dSMickaël Salaün 				    const void *const rule_attr,
37*81709f3dSMickaël Salaün 				    const __u32 flags)
38ba84b0bfSMickaël Salaün {
39*81709f3dSMickaël Salaün 	return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr,
40*81709f3dSMickaël Salaün 		       flags);
41ba84b0bfSMickaël Salaün }
42ba84b0bfSMickaël Salaün #endif
43ba84b0bfSMickaël Salaün 
44ba84b0bfSMickaël Salaün #ifndef landlock_restrict_self
45ba84b0bfSMickaël Salaün static inline int landlock_restrict_self(const int ruleset_fd,
46ba84b0bfSMickaël Salaün 					 const __u32 flags)
47ba84b0bfSMickaël Salaün {
48ba84b0bfSMickaël Salaün 	return syscall(__NR_landlock_restrict_self, ruleset_fd, flags);
49ba84b0bfSMickaël Salaün }
50ba84b0bfSMickaël Salaün #endif
51ba84b0bfSMickaël Salaün 
52ba84b0bfSMickaël Salaün #define ENV_FS_RO_NAME "LL_FS_RO"
53ba84b0bfSMickaël Salaün #define ENV_FS_RW_NAME "LL_FS_RW"
54ba84b0bfSMickaël Salaün #define ENV_PATH_TOKEN ":"
55ba84b0bfSMickaël Salaün 
56ba84b0bfSMickaël Salaün static int parse_path(char *env_path, const char ***const path_list)
57ba84b0bfSMickaël Salaün {
58ba84b0bfSMickaël Salaün 	int i, num_paths = 0;
59ba84b0bfSMickaël Salaün 
60ba84b0bfSMickaël Salaün 	if (env_path) {
61ba84b0bfSMickaël Salaün 		num_paths++;
62ba84b0bfSMickaël Salaün 		for (i = 0; env_path[i]; i++) {
63ba84b0bfSMickaël Salaün 			if (env_path[i] == ENV_PATH_TOKEN[0])
64ba84b0bfSMickaël Salaün 				num_paths++;
65ba84b0bfSMickaël Salaün 		}
66ba84b0bfSMickaël Salaün 	}
67ba84b0bfSMickaël Salaün 	*path_list = malloc(num_paths * sizeof(**path_list));
68ba84b0bfSMickaël Salaün 	for (i = 0; i < num_paths; i++)
69ba84b0bfSMickaël Salaün 		(*path_list)[i] = strsep(&env_path, ENV_PATH_TOKEN);
70ba84b0bfSMickaël Salaün 
71ba84b0bfSMickaël Salaün 	return num_paths;
72ba84b0bfSMickaël Salaün }
73ba84b0bfSMickaël Salaün 
749805a722SMickaël Salaün /* clang-format off */
759805a722SMickaël Salaün 
76ba84b0bfSMickaël Salaün #define ACCESS_FILE ( \
77ba84b0bfSMickaël Salaün 	LANDLOCK_ACCESS_FS_EXECUTE | \
78ba84b0bfSMickaël Salaün 	LANDLOCK_ACCESS_FS_WRITE_FILE | \
79ba84b0bfSMickaël Salaün 	LANDLOCK_ACCESS_FS_READ_FILE)
80ba84b0bfSMickaël Salaün 
819805a722SMickaël Salaün /* clang-format on */
829805a722SMickaël Salaün 
83*81709f3dSMickaël Salaün static int populate_ruleset(const char *const env_var, const int ruleset_fd,
84ba84b0bfSMickaël Salaün 			    const __u64 allowed_access)
85ba84b0bfSMickaël Salaün {
86ba84b0bfSMickaël Salaün 	int num_paths, i, ret = 1;
87ba84b0bfSMickaël Salaün 	char *env_path_name;
88ba84b0bfSMickaël Salaün 	const char **path_list = NULL;
89ba84b0bfSMickaël Salaün 	struct landlock_path_beneath_attr path_beneath = {
90ba84b0bfSMickaël Salaün 		.parent_fd = -1,
91ba84b0bfSMickaël Salaün 	};
92ba84b0bfSMickaël Salaün 
93ba84b0bfSMickaël Salaün 	env_path_name = getenv(env_var);
94ba84b0bfSMickaël Salaün 	if (!env_path_name) {
95ba84b0bfSMickaël Salaün 		/* Prevents users to forget a setting. */
96ba84b0bfSMickaël Salaün 		fprintf(stderr, "Missing environment variable %s\n", env_var);
97ba84b0bfSMickaël Salaün 		return 1;
98ba84b0bfSMickaël Salaün 	}
99ba84b0bfSMickaël Salaün 	env_path_name = strdup(env_path_name);
100ba84b0bfSMickaël Salaün 	unsetenv(env_var);
101ba84b0bfSMickaël Salaün 	num_paths = parse_path(env_path_name, &path_list);
102ba84b0bfSMickaël Salaün 	if (num_paths == 1 && path_list[0][0] == '\0') {
103ba84b0bfSMickaël Salaün 		/*
104ba84b0bfSMickaël Salaün 		 * Allows to not use all possible restrictions (e.g. use
105ba84b0bfSMickaël Salaün 		 * LL_FS_RO without LL_FS_RW).
106ba84b0bfSMickaël Salaün 		 */
107ba84b0bfSMickaël Salaün 		ret = 0;
108ba84b0bfSMickaël Salaün 		goto out_free_name;
109ba84b0bfSMickaël Salaün 	}
110ba84b0bfSMickaël Salaün 
111ba84b0bfSMickaël Salaün 	for (i = 0; i < num_paths; i++) {
112ba84b0bfSMickaël Salaün 		struct stat statbuf;
113ba84b0bfSMickaël Salaün 
114*81709f3dSMickaël Salaün 		path_beneath.parent_fd = open(path_list[i], O_PATH | O_CLOEXEC);
115ba84b0bfSMickaël Salaün 		if (path_beneath.parent_fd < 0) {
116ba84b0bfSMickaël Salaün 			fprintf(stderr, "Failed to open \"%s\": %s\n",
117*81709f3dSMickaël Salaün 				path_list[i], strerror(errno));
118ba84b0bfSMickaël Salaün 			goto out_free_name;
119ba84b0bfSMickaël Salaün 		}
120ba84b0bfSMickaël Salaün 		if (fstat(path_beneath.parent_fd, &statbuf)) {
121ba84b0bfSMickaël Salaün 			close(path_beneath.parent_fd);
122ba84b0bfSMickaël Salaün 			goto out_free_name;
123ba84b0bfSMickaël Salaün 		}
124ba84b0bfSMickaël Salaün 		path_beneath.allowed_access = allowed_access;
125ba84b0bfSMickaël Salaün 		if (!S_ISDIR(statbuf.st_mode))
126ba84b0bfSMickaël Salaün 			path_beneath.allowed_access &= ACCESS_FILE;
127ba84b0bfSMickaël Salaün 		if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
128ba84b0bfSMickaël Salaün 				      &path_beneath, 0)) {
129*81709f3dSMickaël Salaün 			fprintf(stderr,
130*81709f3dSMickaël Salaün 				"Failed to update the ruleset with \"%s\": %s\n",
131ba84b0bfSMickaël Salaün 				path_list[i], strerror(errno));
132ba84b0bfSMickaël Salaün 			close(path_beneath.parent_fd);
133ba84b0bfSMickaël Salaün 			goto out_free_name;
134ba84b0bfSMickaël Salaün 		}
135ba84b0bfSMickaël Salaün 		close(path_beneath.parent_fd);
136ba84b0bfSMickaël Salaün 	}
137ba84b0bfSMickaël Salaün 	ret = 0;
138ba84b0bfSMickaël Salaün 
139ba84b0bfSMickaël Salaün out_free_name:
14066b513b7STom Rix 	free(path_list);
141ba84b0bfSMickaël Salaün 	free(env_path_name);
142ba84b0bfSMickaël Salaün 	return ret;
143ba84b0bfSMickaël Salaün }
144ba84b0bfSMickaël Salaün 
1459805a722SMickaël Salaün /* clang-format off */
1469805a722SMickaël Salaün 
147ba84b0bfSMickaël Salaün #define ACCESS_FS_ROUGHLY_READ ( \
148ba84b0bfSMickaël Salaün 	LANDLOCK_ACCESS_FS_EXECUTE | \
149ba84b0bfSMickaël Salaün 	LANDLOCK_ACCESS_FS_READ_FILE | \
150ba84b0bfSMickaël Salaün 	LANDLOCK_ACCESS_FS_READ_DIR)
151ba84b0bfSMickaël Salaün 
152ba84b0bfSMickaël Salaün #define ACCESS_FS_ROUGHLY_WRITE ( \
153ba84b0bfSMickaël Salaün 	LANDLOCK_ACCESS_FS_WRITE_FILE | \
154ba84b0bfSMickaël Salaün 	LANDLOCK_ACCESS_FS_REMOVE_DIR | \
155ba84b0bfSMickaël Salaün 	LANDLOCK_ACCESS_FS_REMOVE_FILE | \
156ba84b0bfSMickaël Salaün 	LANDLOCK_ACCESS_FS_MAKE_CHAR | \
157ba84b0bfSMickaël Salaün 	LANDLOCK_ACCESS_FS_MAKE_DIR | \
158ba84b0bfSMickaël Salaün 	LANDLOCK_ACCESS_FS_MAKE_REG | \
159ba84b0bfSMickaël Salaün 	LANDLOCK_ACCESS_FS_MAKE_SOCK | \
160ba84b0bfSMickaël Salaün 	LANDLOCK_ACCESS_FS_MAKE_FIFO | \
161ba84b0bfSMickaël Salaün 	LANDLOCK_ACCESS_FS_MAKE_BLOCK | \
162ba84b0bfSMickaël Salaün 	LANDLOCK_ACCESS_FS_MAKE_SYM)
163ba84b0bfSMickaël Salaün 
1649805a722SMickaël Salaün /* clang-format on */
1659805a722SMickaël Salaün 
166ba84b0bfSMickaël Salaün int main(const int argc, char *const argv[], char *const *const envp)
167ba84b0bfSMickaël Salaün {
168ba84b0bfSMickaël Salaün 	const char *cmd_path;
169ba84b0bfSMickaël Salaün 	char *const *cmd_argv;
170ba84b0bfSMickaël Salaün 	int ruleset_fd;
171ba84b0bfSMickaël Salaün 	struct landlock_ruleset_attr ruleset_attr = {
172ba84b0bfSMickaël Salaün 		.handled_access_fs = ACCESS_FS_ROUGHLY_READ |
173ba84b0bfSMickaël Salaün 				     ACCESS_FS_ROUGHLY_WRITE,
174ba84b0bfSMickaël Salaün 	};
175ba84b0bfSMickaël Salaün 
176ba84b0bfSMickaël Salaün 	if (argc < 2) {
177*81709f3dSMickaël Salaün 		fprintf(stderr,
178*81709f3dSMickaël Salaün 			"usage: %s=\"...\" %s=\"...\" %s <cmd> [args]...\n\n",
179ba84b0bfSMickaël Salaün 			ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]);
180*81709f3dSMickaël Salaün 		fprintf(stderr,
181*81709f3dSMickaël Salaün 			"Launch a command in a restricted environment.\n\n");
182ba84b0bfSMickaël Salaün 		fprintf(stderr, "Environment variables containing paths, "
183ba84b0bfSMickaël Salaün 				"each separated by a colon:\n");
184*81709f3dSMickaël Salaün 		fprintf(stderr,
185*81709f3dSMickaël Salaün 			"* %s: list of paths allowed to be used in a read-only way.\n",
186ba84b0bfSMickaël Salaün 			ENV_FS_RO_NAME);
187*81709f3dSMickaël Salaün 		fprintf(stderr,
188*81709f3dSMickaël Salaün 			"* %s: list of paths allowed to be used in a read-write way.\n",
189ba84b0bfSMickaël Salaün 			ENV_FS_RW_NAME);
190*81709f3dSMickaël Salaün 		fprintf(stderr,
191*81709f3dSMickaël Salaün 			"\nexample:\n"
192ba84b0bfSMickaël Salaün 			"%s=\"/bin:/lib:/usr:/proc:/etc:/dev/urandom\" "
193ba84b0bfSMickaël Salaün 			"%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
194ba84b0bfSMickaël Salaün 			"%s bash -i\n",
195ba84b0bfSMickaël Salaün 			ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]);
196ba84b0bfSMickaël Salaün 		return 1;
197ba84b0bfSMickaël Salaün 	}
198ba84b0bfSMickaël Salaün 
199*81709f3dSMickaël Salaün 	ruleset_fd =
200*81709f3dSMickaël Salaün 		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
201ba84b0bfSMickaël Salaün 	if (ruleset_fd < 0) {
202ba84b0bfSMickaël Salaün 		const int err = errno;
203ba84b0bfSMickaël Salaün 
204ba84b0bfSMickaël Salaün 		perror("Failed to create a ruleset");
205ba84b0bfSMickaël Salaün 		switch (err) {
206ba84b0bfSMickaël Salaün 		case ENOSYS:
207*81709f3dSMickaël Salaün 			fprintf(stderr,
208*81709f3dSMickaël Salaün 				"Hint: Landlock is not supported by the current kernel. "
209ba84b0bfSMickaël Salaün 				"To support it, build the kernel with "
210ba84b0bfSMickaël Salaün 				"CONFIG_SECURITY_LANDLOCK=y and prepend "
211ba84b0bfSMickaël Salaün 				"\"landlock,\" to the content of CONFIG_LSM.\n");
212ba84b0bfSMickaël Salaün 			break;
213ba84b0bfSMickaël Salaün 		case EOPNOTSUPP:
214*81709f3dSMickaël Salaün 			fprintf(stderr,
215*81709f3dSMickaël Salaün 				"Hint: Landlock is currently disabled. "
216ba84b0bfSMickaël Salaün 				"It can be enabled in the kernel configuration by "
217ba84b0bfSMickaël Salaün 				"prepending \"landlock,\" to the content of CONFIG_LSM, "
218ba84b0bfSMickaël Salaün 				"or at boot time by setting the same content to the "
219ba84b0bfSMickaël Salaün 				"\"lsm\" kernel parameter.\n");
220ba84b0bfSMickaël Salaün 			break;
221ba84b0bfSMickaël Salaün 		}
222ba84b0bfSMickaël Salaün 		return 1;
223ba84b0bfSMickaël Salaün 	}
224ba84b0bfSMickaël Salaün 	if (populate_ruleset(ENV_FS_RO_NAME, ruleset_fd,
225ba84b0bfSMickaël Salaün 			     ACCESS_FS_ROUGHLY_READ)) {
226ba84b0bfSMickaël Salaün 		goto err_close_ruleset;
227ba84b0bfSMickaël Salaün 	}
228ba84b0bfSMickaël Salaün 	if (populate_ruleset(ENV_FS_RW_NAME, ruleset_fd,
229*81709f3dSMickaël Salaün 			     ACCESS_FS_ROUGHLY_READ |
230*81709f3dSMickaël Salaün 				     ACCESS_FS_ROUGHLY_WRITE)) {
231ba84b0bfSMickaël Salaün 		goto err_close_ruleset;
232ba84b0bfSMickaël Salaün 	}
233ba84b0bfSMickaël Salaün 	if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
234ba84b0bfSMickaël Salaün 		perror("Failed to restrict privileges");
235ba84b0bfSMickaël Salaün 		goto err_close_ruleset;
236ba84b0bfSMickaël Salaün 	}
237ba84b0bfSMickaël Salaün 	if (landlock_restrict_self(ruleset_fd, 0)) {
238ba84b0bfSMickaël Salaün 		perror("Failed to enforce ruleset");
239ba84b0bfSMickaël Salaün 		goto err_close_ruleset;
240ba84b0bfSMickaël Salaün 	}
241ba84b0bfSMickaël Salaün 	close(ruleset_fd);
242ba84b0bfSMickaël Salaün 
243ba84b0bfSMickaël Salaün 	cmd_path = argv[1];
244ba84b0bfSMickaël Salaün 	cmd_argv = argv + 1;
245ba84b0bfSMickaël Salaün 	execvpe(cmd_path, cmd_argv, envp);
246ba84b0bfSMickaël Salaün 	fprintf(stderr, "Failed to execute \"%s\": %s\n", cmd_path,
247ba84b0bfSMickaël Salaün 		strerror(errno));
248ba84b0bfSMickaël Salaün 	fprintf(stderr, "Hint: access to the binary, the interpreter or "
249ba84b0bfSMickaël Salaün 			"shared libraries may be denied.\n");
250ba84b0bfSMickaël Salaün 	return 1;
251ba84b0bfSMickaël Salaün 
252ba84b0bfSMickaël Salaün err_close_ruleset:
253ba84b0bfSMickaël Salaün 	close(ruleset_fd);
254ba84b0bfSMickaël Salaün 	return 1;
255ba84b0bfSMickaël Salaün }
256