Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  1// SPDX-License-Identifier: BSD-3-Clause
  2/*
  3 * Simple Landlock sandbox manager able to launch a process restricted by a
  4 * user-defined filesystem access control policy.
  5 *
  6 * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
  7 * Copyright © 2020 ANSSI
  8 */
  9
 10#define _GNU_SOURCE
 11#include <errno.h>
 12#include <fcntl.h>
 13#include <linux/landlock.h>
 14#include <linux/prctl.h>
 15#include <stddef.h>
 16#include <stdio.h>
 17#include <stdlib.h>
 18#include <string.h>
 19#include <sys/prctl.h>
 20#include <sys/stat.h>
 21#include <sys/syscall.h>
 22#include <unistd.h>
 23
 24#ifndef landlock_create_ruleset
 25static inline int
 26landlock_create_ruleset(const struct landlock_ruleset_attr *const attr,
 27			const size_t size, const __u32 flags)
 28{
 29	return syscall(__NR_landlock_create_ruleset, attr, size, flags);
 30}
 31#endif
 32
 33#ifndef landlock_add_rule
 34static inline int landlock_add_rule(const int ruleset_fd,
 35				    const enum landlock_rule_type rule_type,
 36				    const void *const rule_attr,
 37				    const __u32 flags)
 38{
 39	return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr,
 40		       flags);
 41}
 42#endif
 43
 44#ifndef landlock_restrict_self
 45static inline int landlock_restrict_self(const int ruleset_fd,
 46					 const __u32 flags)
 47{
 48	return syscall(__NR_landlock_restrict_self, ruleset_fd, flags);
 49}
 50#endif
 51
 52#define ENV_FS_RO_NAME "LL_FS_RO"
 53#define ENV_FS_RW_NAME "LL_FS_RW"
 54#define ENV_PATH_TOKEN ":"
 55
 56static int parse_path(char *env_path, const char ***const path_list)
 57{
 58	int i, num_paths = 0;
 59
 60	if (env_path) {
 61		num_paths++;
 62		for (i = 0; env_path[i]; i++) {
 63			if (env_path[i] == ENV_PATH_TOKEN[0])
 64				num_paths++;
 65		}
 66	}
 67	*path_list = malloc(num_paths * sizeof(**path_list));
 68	for (i = 0; i < num_paths; i++)
 69		(*path_list)[i] = strsep(&env_path, ENV_PATH_TOKEN);
 70
 71	return num_paths;
 72}
 73
 74/* clang-format off */
 75
 76#define ACCESS_FILE ( \
 77	LANDLOCK_ACCESS_FS_EXECUTE | \
 78	LANDLOCK_ACCESS_FS_WRITE_FILE | \
 79	LANDLOCK_ACCESS_FS_READ_FILE | \
 80	LANDLOCK_ACCESS_FS_TRUNCATE)
 81
 82/* clang-format on */
 83
 84static int populate_ruleset(const char *const env_var, const int ruleset_fd,
 85			    const __u64 allowed_access)
 86{
 87	int num_paths, i, ret = 1;
 88	char *env_path_name;
 89	const char **path_list = NULL;
 90	struct landlock_path_beneath_attr path_beneath = {
 91		.parent_fd = -1,
 92	};
 93
 94	env_path_name = getenv(env_var);
 95	if (!env_path_name) {
 96		/* Prevents users to forget a setting. */
 97		fprintf(stderr, "Missing environment variable %s\n", env_var);
 98		return 1;
 99	}
100	env_path_name = strdup(env_path_name);
101	unsetenv(env_var);
102	num_paths = parse_path(env_path_name, &path_list);
103	if (num_paths == 1 && path_list[0][0] == '\0') {
104		/*
105		 * Allows to not use all possible restrictions (e.g. use
106		 * LL_FS_RO without LL_FS_RW).
107		 */
108		ret = 0;
109		goto out_free_name;
110	}
111
112	for (i = 0; i < num_paths; i++) {
113		struct stat statbuf;
114
115		path_beneath.parent_fd = open(path_list[i], O_PATH | O_CLOEXEC);
116		if (path_beneath.parent_fd < 0) {
117			fprintf(stderr, "Failed to open \"%s\": %s\n",
118				path_list[i], strerror(errno));
119			goto out_free_name;
120		}
121		if (fstat(path_beneath.parent_fd, &statbuf)) {
122			close(path_beneath.parent_fd);
123			goto out_free_name;
124		}
125		path_beneath.allowed_access = allowed_access;
126		if (!S_ISDIR(statbuf.st_mode))
127			path_beneath.allowed_access &= ACCESS_FILE;
128		if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
129				      &path_beneath, 0)) {
130			fprintf(stderr,
131				"Failed to update the ruleset with \"%s\": %s\n",
132				path_list[i], strerror(errno));
133			close(path_beneath.parent_fd);
134			goto out_free_name;
135		}
136		close(path_beneath.parent_fd);
137	}
138	ret = 0;
139
140out_free_name:
141	free(path_list);
142	free(env_path_name);
143	return ret;
144}
145
146/* clang-format off */
147
148#define ACCESS_FS_ROUGHLY_READ ( \
149	LANDLOCK_ACCESS_FS_EXECUTE | \
150	LANDLOCK_ACCESS_FS_READ_FILE | \
151	LANDLOCK_ACCESS_FS_READ_DIR)
152
153#define ACCESS_FS_ROUGHLY_WRITE ( \
154	LANDLOCK_ACCESS_FS_WRITE_FILE | \
155	LANDLOCK_ACCESS_FS_REMOVE_DIR | \
156	LANDLOCK_ACCESS_FS_REMOVE_FILE | \
157	LANDLOCK_ACCESS_FS_MAKE_CHAR | \
158	LANDLOCK_ACCESS_FS_MAKE_DIR | \
159	LANDLOCK_ACCESS_FS_MAKE_REG | \
160	LANDLOCK_ACCESS_FS_MAKE_SOCK | \
161	LANDLOCK_ACCESS_FS_MAKE_FIFO | \
162	LANDLOCK_ACCESS_FS_MAKE_BLOCK | \
163	LANDLOCK_ACCESS_FS_MAKE_SYM | \
164	LANDLOCK_ACCESS_FS_REFER | \
165	LANDLOCK_ACCESS_FS_TRUNCATE)
166
167/* clang-format on */
168
169#define LANDLOCK_ABI_LAST 3
170
171int main(const int argc, char *const argv[], char *const *const envp)
172{
173	const char *cmd_path;
174	char *const *cmd_argv;
175	int ruleset_fd, abi;
176	__u64 access_fs_ro = ACCESS_FS_ROUGHLY_READ,
177	      access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE;
178	struct landlock_ruleset_attr ruleset_attr = {
179		.handled_access_fs = access_fs_rw,
180	};
181
182	if (argc < 2) {
183		fprintf(stderr,
184			"usage: %s=\"...\" %s=\"...\" %s <cmd> [args]...\n\n",
185			ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]);
186		fprintf(stderr,
187			"Launch a command in a restricted environment.\n\n");
188		fprintf(stderr, "Environment variables containing paths, "
189				"each separated by a colon:\n");
190		fprintf(stderr,
191			"* %s: list of paths allowed to be used in a read-only way.\n",
192			ENV_FS_RO_NAME);
193		fprintf(stderr,
194			"* %s: list of paths allowed to be used in a read-write way.\n",
195			ENV_FS_RW_NAME);
196		fprintf(stderr,
197			"\nexample:\n"
198			"%s=\"/bin:/lib:/usr:/proc:/etc:/dev/urandom\" "
199			"%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
200			"%s bash -i\n\n",
201			ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]);
202		fprintf(stderr,
203			"This sandboxer can use Landlock features "
204			"up to ABI version %d.\n",
205			LANDLOCK_ABI_LAST);
206		return 1;
207	}
208
209	abi = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
210	if (abi < 0) {
211		const int err = errno;
212
213		perror("Failed to check Landlock compatibility");
214		switch (err) {
215		case ENOSYS:
216			fprintf(stderr,
217				"Hint: Landlock is not supported by the current kernel. "
218				"To support it, build the kernel with "
219				"CONFIG_SECURITY_LANDLOCK=y and prepend "
220				"\"landlock,\" to the content of CONFIG_LSM.\n");
221			break;
222		case EOPNOTSUPP:
223			fprintf(stderr,
224				"Hint: Landlock is currently disabled. "
225				"It can be enabled in the kernel configuration by "
226				"prepending \"landlock,\" to the content of CONFIG_LSM, "
227				"or at boot time by setting the same content to the "
228				"\"lsm\" kernel parameter.\n");
229			break;
230		}
231		return 1;
232	}
233
234	/* Best-effort security. */
235	switch (abi) {
236	case 1:
237		/*
238		 * Removes LANDLOCK_ACCESS_FS_REFER for ABI < 2
239		 *
240		 * Note: The "refer" operations (file renaming and linking
241		 * across different directories) are always forbidden when using
242		 * Landlock with ABI 1.
243		 *
244		 * If only ABI 1 is available, this sandboxer knowingly forbids
245		 * refer operations.
246		 *
247		 * If a program *needs* to do refer operations after enabling
248		 * Landlock, it can not use Landlock at ABI level 1.  To be
249		 * compatible with different kernel versions, such programs
250		 * should then fall back to not restrict themselves at all if
251		 * the running kernel only supports ABI 1.
252		 */
253		ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_REFER;
254		__attribute__((fallthrough));
255	case 2:
256		/* Removes LANDLOCK_ACCESS_FS_TRUNCATE for ABI < 3 */
257		ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_TRUNCATE;
258
259		fprintf(stderr,
260			"Hint: You should update the running kernel "
261			"to leverage Landlock features "
262			"provided by ABI version %d (instead of %d).\n",
263			LANDLOCK_ABI_LAST, abi);
264		__attribute__((fallthrough));
265	case LANDLOCK_ABI_LAST:
266		break;
267	default:
268		fprintf(stderr,
269			"Hint: You should update this sandboxer "
270			"to leverage Landlock features "
271			"provided by ABI version %d (instead of %d).\n",
272			abi, LANDLOCK_ABI_LAST);
273	}
274	access_fs_ro &= ruleset_attr.handled_access_fs;
275	access_fs_rw &= ruleset_attr.handled_access_fs;
276
277	ruleset_fd =
278		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
279	if (ruleset_fd < 0) {
280		perror("Failed to create a ruleset");
281		return 1;
282	}
283	if (populate_ruleset(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro)) {
284		goto err_close_ruleset;
285	}
286	if (populate_ruleset(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) {
287		goto err_close_ruleset;
288	}
289	if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
290		perror("Failed to restrict privileges");
291		goto err_close_ruleset;
292	}
293	if (landlock_restrict_self(ruleset_fd, 0)) {
294		perror("Failed to enforce ruleset");
295		goto err_close_ruleset;
296	}
297	close(ruleset_fd);
298
299	cmd_path = argv[1];
300	cmd_argv = argv + 1;
301	execvpe(cmd_path, cmd_argv, envp);
302	fprintf(stderr, "Failed to execute \"%s\": %s\n", cmd_path,
303		strerror(errno));
304	fprintf(stderr, "Hint: access to the binary, the interpreter or "
305			"shared libraries may be denied.\n");
306	return 1;
307
308err_close_ruleset:
309	close(ruleset_fd);
310	return 1;
311}