162306a36Sopenharmony_ci// SPDX-License-Identifier: BSD-3-Clause
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Simple Landlock sandbox manager able to launch a process restricted by a
462306a36Sopenharmony_ci * user-defined filesystem access control policy.
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>
762306a36Sopenharmony_ci * Copyright © 2020 ANSSI
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#define _GNU_SOURCE
1162306a36Sopenharmony_ci#include <errno.h>
1262306a36Sopenharmony_ci#include <fcntl.h>
1362306a36Sopenharmony_ci#include <linux/landlock.h>
1462306a36Sopenharmony_ci#include <linux/prctl.h>
1562306a36Sopenharmony_ci#include <stddef.h>
1662306a36Sopenharmony_ci#include <stdio.h>
1762306a36Sopenharmony_ci#include <stdlib.h>
1862306a36Sopenharmony_ci#include <string.h>
1962306a36Sopenharmony_ci#include <sys/prctl.h>
2062306a36Sopenharmony_ci#include <sys/stat.h>
2162306a36Sopenharmony_ci#include <sys/syscall.h>
2262306a36Sopenharmony_ci#include <unistd.h>
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#ifndef landlock_create_ruleset
2562306a36Sopenharmony_cistatic inline int
2662306a36Sopenharmony_cilandlock_create_ruleset(const struct landlock_ruleset_attr *const attr,
2762306a36Sopenharmony_ci			const size_t size, const __u32 flags)
2862306a36Sopenharmony_ci{
2962306a36Sopenharmony_ci	return syscall(__NR_landlock_create_ruleset, attr, size, flags);
3062306a36Sopenharmony_ci}
3162306a36Sopenharmony_ci#endif
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci#ifndef landlock_add_rule
3462306a36Sopenharmony_cistatic inline int landlock_add_rule(const int ruleset_fd,
3562306a36Sopenharmony_ci				    const enum landlock_rule_type rule_type,
3662306a36Sopenharmony_ci				    const void *const rule_attr,
3762306a36Sopenharmony_ci				    const __u32 flags)
3862306a36Sopenharmony_ci{
3962306a36Sopenharmony_ci	return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr,
4062306a36Sopenharmony_ci		       flags);
4162306a36Sopenharmony_ci}
4262306a36Sopenharmony_ci#endif
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci#ifndef landlock_restrict_self
4562306a36Sopenharmony_cistatic inline int landlock_restrict_self(const int ruleset_fd,
4662306a36Sopenharmony_ci					 const __u32 flags)
4762306a36Sopenharmony_ci{
4862306a36Sopenharmony_ci	return syscall(__NR_landlock_restrict_self, ruleset_fd, flags);
4962306a36Sopenharmony_ci}
5062306a36Sopenharmony_ci#endif
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci#define ENV_FS_RO_NAME "LL_FS_RO"
5362306a36Sopenharmony_ci#define ENV_FS_RW_NAME "LL_FS_RW"
5462306a36Sopenharmony_ci#define ENV_PATH_TOKEN ":"
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_cistatic int parse_path(char *env_path, const char ***const path_list)
5762306a36Sopenharmony_ci{
5862306a36Sopenharmony_ci	int i, num_paths = 0;
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	if (env_path) {
6162306a36Sopenharmony_ci		num_paths++;
6262306a36Sopenharmony_ci		for (i = 0; env_path[i]; i++) {
6362306a36Sopenharmony_ci			if (env_path[i] == ENV_PATH_TOKEN[0])
6462306a36Sopenharmony_ci				num_paths++;
6562306a36Sopenharmony_ci		}
6662306a36Sopenharmony_ci	}
6762306a36Sopenharmony_ci	*path_list = malloc(num_paths * sizeof(**path_list));
6862306a36Sopenharmony_ci	for (i = 0; i < num_paths; i++)
6962306a36Sopenharmony_ci		(*path_list)[i] = strsep(&env_path, ENV_PATH_TOKEN);
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	return num_paths;
7262306a36Sopenharmony_ci}
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci/* clang-format off */
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci#define ACCESS_FILE ( \
7762306a36Sopenharmony_ci	LANDLOCK_ACCESS_FS_EXECUTE | \
7862306a36Sopenharmony_ci	LANDLOCK_ACCESS_FS_WRITE_FILE | \
7962306a36Sopenharmony_ci	LANDLOCK_ACCESS_FS_READ_FILE | \
8062306a36Sopenharmony_ci	LANDLOCK_ACCESS_FS_TRUNCATE)
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci/* clang-format on */
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_cistatic int populate_ruleset(const char *const env_var, const int ruleset_fd,
8562306a36Sopenharmony_ci			    const __u64 allowed_access)
8662306a36Sopenharmony_ci{
8762306a36Sopenharmony_ci	int num_paths, i, ret = 1;
8862306a36Sopenharmony_ci	char *env_path_name;
8962306a36Sopenharmony_ci	const char **path_list = NULL;
9062306a36Sopenharmony_ci	struct landlock_path_beneath_attr path_beneath = {
9162306a36Sopenharmony_ci		.parent_fd = -1,
9262306a36Sopenharmony_ci	};
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	env_path_name = getenv(env_var);
9562306a36Sopenharmony_ci	if (!env_path_name) {
9662306a36Sopenharmony_ci		/* Prevents users to forget a setting. */
9762306a36Sopenharmony_ci		fprintf(stderr, "Missing environment variable %s\n", env_var);
9862306a36Sopenharmony_ci		return 1;
9962306a36Sopenharmony_ci	}
10062306a36Sopenharmony_ci	env_path_name = strdup(env_path_name);
10162306a36Sopenharmony_ci	unsetenv(env_var);
10262306a36Sopenharmony_ci	num_paths = parse_path(env_path_name, &path_list);
10362306a36Sopenharmony_ci	if (num_paths == 1 && path_list[0][0] == '\0') {
10462306a36Sopenharmony_ci		/*
10562306a36Sopenharmony_ci		 * Allows to not use all possible restrictions (e.g. use
10662306a36Sopenharmony_ci		 * LL_FS_RO without LL_FS_RW).
10762306a36Sopenharmony_ci		 */
10862306a36Sopenharmony_ci		ret = 0;
10962306a36Sopenharmony_ci		goto out_free_name;
11062306a36Sopenharmony_ci	}
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	for (i = 0; i < num_paths; i++) {
11362306a36Sopenharmony_ci		struct stat statbuf;
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci		path_beneath.parent_fd = open(path_list[i], O_PATH | O_CLOEXEC);
11662306a36Sopenharmony_ci		if (path_beneath.parent_fd < 0) {
11762306a36Sopenharmony_ci			fprintf(stderr, "Failed to open \"%s\": %s\n",
11862306a36Sopenharmony_ci				path_list[i], strerror(errno));
11962306a36Sopenharmony_ci			goto out_free_name;
12062306a36Sopenharmony_ci		}
12162306a36Sopenharmony_ci		if (fstat(path_beneath.parent_fd, &statbuf)) {
12262306a36Sopenharmony_ci			close(path_beneath.parent_fd);
12362306a36Sopenharmony_ci			goto out_free_name;
12462306a36Sopenharmony_ci		}
12562306a36Sopenharmony_ci		path_beneath.allowed_access = allowed_access;
12662306a36Sopenharmony_ci		if (!S_ISDIR(statbuf.st_mode))
12762306a36Sopenharmony_ci			path_beneath.allowed_access &= ACCESS_FILE;
12862306a36Sopenharmony_ci		if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
12962306a36Sopenharmony_ci				      &path_beneath, 0)) {
13062306a36Sopenharmony_ci			fprintf(stderr,
13162306a36Sopenharmony_ci				"Failed to update the ruleset with \"%s\": %s\n",
13262306a36Sopenharmony_ci				path_list[i], strerror(errno));
13362306a36Sopenharmony_ci			close(path_beneath.parent_fd);
13462306a36Sopenharmony_ci			goto out_free_name;
13562306a36Sopenharmony_ci		}
13662306a36Sopenharmony_ci		close(path_beneath.parent_fd);
13762306a36Sopenharmony_ci	}
13862306a36Sopenharmony_ci	ret = 0;
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ciout_free_name:
14162306a36Sopenharmony_ci	free(path_list);
14262306a36Sopenharmony_ci	free(env_path_name);
14362306a36Sopenharmony_ci	return ret;
14462306a36Sopenharmony_ci}
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci/* clang-format off */
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci#define ACCESS_FS_ROUGHLY_READ ( \
14962306a36Sopenharmony_ci	LANDLOCK_ACCESS_FS_EXECUTE | \
15062306a36Sopenharmony_ci	LANDLOCK_ACCESS_FS_READ_FILE | \
15162306a36Sopenharmony_ci	LANDLOCK_ACCESS_FS_READ_DIR)
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci#define ACCESS_FS_ROUGHLY_WRITE ( \
15462306a36Sopenharmony_ci	LANDLOCK_ACCESS_FS_WRITE_FILE | \
15562306a36Sopenharmony_ci	LANDLOCK_ACCESS_FS_REMOVE_DIR | \
15662306a36Sopenharmony_ci	LANDLOCK_ACCESS_FS_REMOVE_FILE | \
15762306a36Sopenharmony_ci	LANDLOCK_ACCESS_FS_MAKE_CHAR | \
15862306a36Sopenharmony_ci	LANDLOCK_ACCESS_FS_MAKE_DIR | \
15962306a36Sopenharmony_ci	LANDLOCK_ACCESS_FS_MAKE_REG | \
16062306a36Sopenharmony_ci	LANDLOCK_ACCESS_FS_MAKE_SOCK | \
16162306a36Sopenharmony_ci	LANDLOCK_ACCESS_FS_MAKE_FIFO | \
16262306a36Sopenharmony_ci	LANDLOCK_ACCESS_FS_MAKE_BLOCK | \
16362306a36Sopenharmony_ci	LANDLOCK_ACCESS_FS_MAKE_SYM | \
16462306a36Sopenharmony_ci	LANDLOCK_ACCESS_FS_REFER | \
16562306a36Sopenharmony_ci	LANDLOCK_ACCESS_FS_TRUNCATE)
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci/* clang-format on */
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci#define LANDLOCK_ABI_LAST 3
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ciint main(const int argc, char *const argv[], char *const *const envp)
17262306a36Sopenharmony_ci{
17362306a36Sopenharmony_ci	const char *cmd_path;
17462306a36Sopenharmony_ci	char *const *cmd_argv;
17562306a36Sopenharmony_ci	int ruleset_fd, abi;
17662306a36Sopenharmony_ci	__u64 access_fs_ro = ACCESS_FS_ROUGHLY_READ,
17762306a36Sopenharmony_ci	      access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE;
17862306a36Sopenharmony_ci	struct landlock_ruleset_attr ruleset_attr = {
17962306a36Sopenharmony_ci		.handled_access_fs = access_fs_rw,
18062306a36Sopenharmony_ci	};
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	if (argc < 2) {
18362306a36Sopenharmony_ci		fprintf(stderr,
18462306a36Sopenharmony_ci			"usage: %s=\"...\" %s=\"...\" %s <cmd> [args]...\n\n",
18562306a36Sopenharmony_ci			ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]);
18662306a36Sopenharmony_ci		fprintf(stderr,
18762306a36Sopenharmony_ci			"Launch a command in a restricted environment.\n\n");
18862306a36Sopenharmony_ci		fprintf(stderr, "Environment variables containing paths, "
18962306a36Sopenharmony_ci				"each separated by a colon:\n");
19062306a36Sopenharmony_ci		fprintf(stderr,
19162306a36Sopenharmony_ci			"* %s: list of paths allowed to be used in a read-only way.\n",
19262306a36Sopenharmony_ci			ENV_FS_RO_NAME);
19362306a36Sopenharmony_ci		fprintf(stderr,
19462306a36Sopenharmony_ci			"* %s: list of paths allowed to be used in a read-write way.\n",
19562306a36Sopenharmony_ci			ENV_FS_RW_NAME);
19662306a36Sopenharmony_ci		fprintf(stderr,
19762306a36Sopenharmony_ci			"\nexample:\n"
19862306a36Sopenharmony_ci			"%s=\"/bin:/lib:/usr:/proc:/etc:/dev/urandom\" "
19962306a36Sopenharmony_ci			"%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
20062306a36Sopenharmony_ci			"%s bash -i\n\n",
20162306a36Sopenharmony_ci			ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]);
20262306a36Sopenharmony_ci		fprintf(stderr,
20362306a36Sopenharmony_ci			"This sandboxer can use Landlock features "
20462306a36Sopenharmony_ci			"up to ABI version %d.\n",
20562306a36Sopenharmony_ci			LANDLOCK_ABI_LAST);
20662306a36Sopenharmony_ci		return 1;
20762306a36Sopenharmony_ci	}
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	abi = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
21062306a36Sopenharmony_ci	if (abi < 0) {
21162306a36Sopenharmony_ci		const int err = errno;
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci		perror("Failed to check Landlock compatibility");
21462306a36Sopenharmony_ci		switch (err) {
21562306a36Sopenharmony_ci		case ENOSYS:
21662306a36Sopenharmony_ci			fprintf(stderr,
21762306a36Sopenharmony_ci				"Hint: Landlock is not supported by the current kernel. "
21862306a36Sopenharmony_ci				"To support it, build the kernel with "
21962306a36Sopenharmony_ci				"CONFIG_SECURITY_LANDLOCK=y and prepend "
22062306a36Sopenharmony_ci				"\"landlock,\" to the content of CONFIG_LSM.\n");
22162306a36Sopenharmony_ci			break;
22262306a36Sopenharmony_ci		case EOPNOTSUPP:
22362306a36Sopenharmony_ci			fprintf(stderr,
22462306a36Sopenharmony_ci				"Hint: Landlock is currently disabled. "
22562306a36Sopenharmony_ci				"It can be enabled in the kernel configuration by "
22662306a36Sopenharmony_ci				"prepending \"landlock,\" to the content of CONFIG_LSM, "
22762306a36Sopenharmony_ci				"or at boot time by setting the same content to the "
22862306a36Sopenharmony_ci				"\"lsm\" kernel parameter.\n");
22962306a36Sopenharmony_ci			break;
23062306a36Sopenharmony_ci		}
23162306a36Sopenharmony_ci		return 1;
23262306a36Sopenharmony_ci	}
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	/* Best-effort security. */
23562306a36Sopenharmony_ci	switch (abi) {
23662306a36Sopenharmony_ci	case 1:
23762306a36Sopenharmony_ci		/*
23862306a36Sopenharmony_ci		 * Removes LANDLOCK_ACCESS_FS_REFER for ABI < 2
23962306a36Sopenharmony_ci		 *
24062306a36Sopenharmony_ci		 * Note: The "refer" operations (file renaming and linking
24162306a36Sopenharmony_ci		 * across different directories) are always forbidden when using
24262306a36Sopenharmony_ci		 * Landlock with ABI 1.
24362306a36Sopenharmony_ci		 *
24462306a36Sopenharmony_ci		 * If only ABI 1 is available, this sandboxer knowingly forbids
24562306a36Sopenharmony_ci		 * refer operations.
24662306a36Sopenharmony_ci		 *
24762306a36Sopenharmony_ci		 * If a program *needs* to do refer operations after enabling
24862306a36Sopenharmony_ci		 * Landlock, it can not use Landlock at ABI level 1.  To be
24962306a36Sopenharmony_ci		 * compatible with different kernel versions, such programs
25062306a36Sopenharmony_ci		 * should then fall back to not restrict themselves at all if
25162306a36Sopenharmony_ci		 * the running kernel only supports ABI 1.
25262306a36Sopenharmony_ci		 */
25362306a36Sopenharmony_ci		ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_REFER;
25462306a36Sopenharmony_ci		__attribute__((fallthrough));
25562306a36Sopenharmony_ci	case 2:
25662306a36Sopenharmony_ci		/* Removes LANDLOCK_ACCESS_FS_TRUNCATE for ABI < 3 */
25762306a36Sopenharmony_ci		ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_TRUNCATE;
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci		fprintf(stderr,
26062306a36Sopenharmony_ci			"Hint: You should update the running kernel "
26162306a36Sopenharmony_ci			"to leverage Landlock features "
26262306a36Sopenharmony_ci			"provided by ABI version %d (instead of %d).\n",
26362306a36Sopenharmony_ci			LANDLOCK_ABI_LAST, abi);
26462306a36Sopenharmony_ci		__attribute__((fallthrough));
26562306a36Sopenharmony_ci	case LANDLOCK_ABI_LAST:
26662306a36Sopenharmony_ci		break;
26762306a36Sopenharmony_ci	default:
26862306a36Sopenharmony_ci		fprintf(stderr,
26962306a36Sopenharmony_ci			"Hint: You should update this sandboxer "
27062306a36Sopenharmony_ci			"to leverage Landlock features "
27162306a36Sopenharmony_ci			"provided by ABI version %d (instead of %d).\n",
27262306a36Sopenharmony_ci			abi, LANDLOCK_ABI_LAST);
27362306a36Sopenharmony_ci	}
27462306a36Sopenharmony_ci	access_fs_ro &= ruleset_attr.handled_access_fs;
27562306a36Sopenharmony_ci	access_fs_rw &= ruleset_attr.handled_access_fs;
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	ruleset_fd =
27862306a36Sopenharmony_ci		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
27962306a36Sopenharmony_ci	if (ruleset_fd < 0) {
28062306a36Sopenharmony_ci		perror("Failed to create a ruleset");
28162306a36Sopenharmony_ci		return 1;
28262306a36Sopenharmony_ci	}
28362306a36Sopenharmony_ci	if (populate_ruleset(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro)) {
28462306a36Sopenharmony_ci		goto err_close_ruleset;
28562306a36Sopenharmony_ci	}
28662306a36Sopenharmony_ci	if (populate_ruleset(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) {
28762306a36Sopenharmony_ci		goto err_close_ruleset;
28862306a36Sopenharmony_ci	}
28962306a36Sopenharmony_ci	if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
29062306a36Sopenharmony_ci		perror("Failed to restrict privileges");
29162306a36Sopenharmony_ci		goto err_close_ruleset;
29262306a36Sopenharmony_ci	}
29362306a36Sopenharmony_ci	if (landlock_restrict_self(ruleset_fd, 0)) {
29462306a36Sopenharmony_ci		perror("Failed to enforce ruleset");
29562306a36Sopenharmony_ci		goto err_close_ruleset;
29662306a36Sopenharmony_ci	}
29762306a36Sopenharmony_ci	close(ruleset_fd);
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	cmd_path = argv[1];
30062306a36Sopenharmony_ci	cmd_argv = argv + 1;
30162306a36Sopenharmony_ci	execvpe(cmd_path, cmd_argv, envp);
30262306a36Sopenharmony_ci	fprintf(stderr, "Failed to execute \"%s\": %s\n", cmd_path,
30362306a36Sopenharmony_ci		strerror(errno));
30462306a36Sopenharmony_ci	fprintf(stderr, "Hint: access to the binary, the interpreter or "
30562306a36Sopenharmony_ci			"shared libraries may be denied.\n");
30662306a36Sopenharmony_ci	return 1;
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_cierr_close_ruleset:
30962306a36Sopenharmony_ci	close(ruleset_fd);
31062306a36Sopenharmony_ci	return 1;
31162306a36Sopenharmony_ci}
312