brintos

brintos / linux-shallow public Read only

0
0
Text · 13.6 KiB · 57565df Raw
499 lines · c
1// SPDX-License-Identifier: BSD-3-Clause2/*3 * Simple Landlock sandbox manager able to execute a process restricted by4 * user-defined file system and network access control policies.5 *6 * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net>7 * Copyright © 2020 ANSSI8 */9 10#define _GNU_SOURCE11#define __SANE_USERSPACE_TYPES__12#include <arpa/inet.h>13#include <errno.h>14#include <fcntl.h>15#include <linux/landlock.h>16#include <linux/prctl.h>17#include <linux/socket.h>18#include <stddef.h>19#include <stdio.h>20#include <stdlib.h>21#include <string.h>22#include <sys/prctl.h>23#include <sys/stat.h>24#include <sys/syscall.h>25#include <unistd.h>26#include <stdbool.h>27 28#ifndef landlock_create_ruleset29static inline int30landlock_create_ruleset(const struct landlock_ruleset_attr *const attr,31			const size_t size, const __u32 flags)32{33	return syscall(__NR_landlock_create_ruleset, attr, size, flags);34}35#endif36 37#ifndef landlock_add_rule38static inline int landlock_add_rule(const int ruleset_fd,39				    const enum landlock_rule_type rule_type,40				    const void *const rule_attr,41				    const __u32 flags)42{43	return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr,44		       flags);45}46#endif47 48#ifndef landlock_restrict_self49static inline int landlock_restrict_self(const int ruleset_fd,50					 const __u32 flags)51{52	return syscall(__NR_landlock_restrict_self, ruleset_fd, flags);53}54#endif55 56#define ENV_FS_RO_NAME "LL_FS_RO"57#define ENV_FS_RW_NAME "LL_FS_RW"58#define ENV_TCP_BIND_NAME "LL_TCP_BIND"59#define ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT"60#define ENV_SCOPED_NAME "LL_SCOPED"61#define ENV_DELIMITER ":"62 63static int str2num(const char *numstr, __u64 *num_dst)64{65	char *endptr = NULL;66	int err = 0;67	__u64 num;68 69	errno = 0;70	num = strtoull(numstr, &endptr, 10);71	if (errno != 0)72		err = errno;73	/* Was the string empty, or not entirely parsed successfully? */74	else if ((*numstr == '\0') || (*endptr != '\0'))75		err = EINVAL;76	else77		*num_dst = num;78 79	return err;80}81 82static int parse_path(char *env_path, const char ***const path_list)83{84	int i, num_paths = 0;85 86	if (env_path) {87		num_paths++;88		for (i = 0; env_path[i]; i++) {89			if (env_path[i] == ENV_DELIMITER[0])90				num_paths++;91		}92	}93	*path_list = malloc(num_paths * sizeof(**path_list));94	for (i = 0; i < num_paths; i++)95		(*path_list)[i] = strsep(&env_path, ENV_DELIMITER);96 97	return num_paths;98}99 100/* clang-format off */101 102#define ACCESS_FILE ( \103	LANDLOCK_ACCESS_FS_EXECUTE | \104	LANDLOCK_ACCESS_FS_WRITE_FILE | \105	LANDLOCK_ACCESS_FS_READ_FILE | \106	LANDLOCK_ACCESS_FS_TRUNCATE | \107	LANDLOCK_ACCESS_FS_IOCTL_DEV)108 109/* clang-format on */110 111static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd,112			       const __u64 allowed_access)113{114	int num_paths, i, ret = 1;115	char *env_path_name;116	const char **path_list = NULL;117	struct landlock_path_beneath_attr path_beneath = {118		.parent_fd = -1,119	};120 121	env_path_name = getenv(env_var);122	if (!env_path_name) {123		/* Prevents users to forget a setting. */124		fprintf(stderr, "Missing environment variable %s\n", env_var);125		return 1;126	}127	env_path_name = strdup(env_path_name);128	unsetenv(env_var);129	num_paths = parse_path(env_path_name, &path_list);130	if (num_paths == 1 && path_list[0][0] == '\0') {131		/*132		 * Allows to not use all possible restrictions (e.g. use133		 * LL_FS_RO without LL_FS_RW).134		 */135		ret = 0;136		goto out_free_name;137	}138 139	for (i = 0; i < num_paths; i++) {140		struct stat statbuf;141 142		path_beneath.parent_fd = open(path_list[i], O_PATH | O_CLOEXEC);143		if (path_beneath.parent_fd < 0) {144			fprintf(stderr, "Failed to open \"%s\": %s\n",145				path_list[i], strerror(errno));146			continue;147		}148		if (fstat(path_beneath.parent_fd, &statbuf)) {149			fprintf(stderr, "Failed to stat \"%s\": %s\n",150				path_list[i], strerror(errno));151			close(path_beneath.parent_fd);152			goto out_free_name;153		}154		path_beneath.allowed_access = allowed_access;155		if (!S_ISDIR(statbuf.st_mode))156			path_beneath.allowed_access &= ACCESS_FILE;157		if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,158				      &path_beneath, 0)) {159			fprintf(stderr,160				"Failed to update the ruleset with \"%s\": %s\n",161				path_list[i], strerror(errno));162			close(path_beneath.parent_fd);163			goto out_free_name;164		}165		close(path_beneath.parent_fd);166	}167	ret = 0;168 169out_free_name:170	free(path_list);171	free(env_path_name);172	return ret;173}174 175static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,176				const __u64 allowed_access)177{178	int ret = 1;179	char *env_port_name, *env_port_name_next, *strport;180	struct landlock_net_port_attr net_port = {181		.allowed_access = allowed_access,182	};183 184	env_port_name = getenv(env_var);185	if (!env_port_name)186		return 0;187	env_port_name = strdup(env_port_name);188	unsetenv(env_var);189 190	env_port_name_next = env_port_name;191	while ((strport = strsep(&env_port_name_next, ENV_DELIMITER))) {192		__u64 port;193 194		if (strcmp(strport, "") == 0)195			continue;196 197		if (str2num(strport, &port)) {198			fprintf(stderr, "Failed to parse port at \"%s\"\n",199				strport);200			goto out_free_name;201		}202		net_port.port = port;203		if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,204				      &net_port, 0)) {205			fprintf(stderr,206				"Failed to update the ruleset with port \"%llu\": %s\n",207				net_port.port, strerror(errno));208			goto out_free_name;209		}210	}211	ret = 0;212 213out_free_name:214	free(env_port_name);215	return ret;216}217 218/* Returns true on error, false otherwise. */219static bool check_ruleset_scope(const char *const env_var,220				struct landlock_ruleset_attr *ruleset_attr)221{222	char *env_type_scope, *env_type_scope_next, *ipc_scoping_name;223	bool error = false;224	bool abstract_scoping = false;225	bool signal_scoping = false;226 227	/* Scoping is not supported by Landlock ABI */228	if (!(ruleset_attr->scoped &229	      (LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | LANDLOCK_SCOPE_SIGNAL)))230		goto out_unset;231 232	env_type_scope = getenv(env_var);233	/* Scoping is not supported by the user */234	if (!env_type_scope || strcmp("", env_type_scope) == 0)235		goto out_unset;236 237	env_type_scope = strdup(env_type_scope);238	env_type_scope_next = env_type_scope;239	while ((ipc_scoping_name =240			strsep(&env_type_scope_next, ENV_DELIMITER))) {241		if (strcmp("a", ipc_scoping_name) == 0 && !abstract_scoping) {242			abstract_scoping = true;243		} else if (strcmp("s", ipc_scoping_name) == 0 &&244			   !signal_scoping) {245			signal_scoping = true;246		} else {247			fprintf(stderr, "Unknown or duplicate scope \"%s\"\n",248				ipc_scoping_name);249			error = true;250			goto out_free_name;251		}252	}253 254out_free_name:255	free(env_type_scope);256 257out_unset:258	if (!abstract_scoping)259		ruleset_attr->scoped &= ~LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET;260	if (!signal_scoping)261		ruleset_attr->scoped &= ~LANDLOCK_SCOPE_SIGNAL;262 263	unsetenv(env_var);264	return error;265}266 267/* clang-format off */268 269#define ACCESS_FS_ROUGHLY_READ ( \270	LANDLOCK_ACCESS_FS_EXECUTE | \271	LANDLOCK_ACCESS_FS_READ_FILE | \272	LANDLOCK_ACCESS_FS_READ_DIR)273 274#define ACCESS_FS_ROUGHLY_WRITE ( \275	LANDLOCK_ACCESS_FS_WRITE_FILE | \276	LANDLOCK_ACCESS_FS_REMOVE_DIR | \277	LANDLOCK_ACCESS_FS_REMOVE_FILE | \278	LANDLOCK_ACCESS_FS_MAKE_CHAR | \279	LANDLOCK_ACCESS_FS_MAKE_DIR | \280	LANDLOCK_ACCESS_FS_MAKE_REG | \281	LANDLOCK_ACCESS_FS_MAKE_SOCK | \282	LANDLOCK_ACCESS_FS_MAKE_FIFO | \283	LANDLOCK_ACCESS_FS_MAKE_BLOCK | \284	LANDLOCK_ACCESS_FS_MAKE_SYM | \285	LANDLOCK_ACCESS_FS_REFER | \286	LANDLOCK_ACCESS_FS_TRUNCATE | \287	LANDLOCK_ACCESS_FS_IOCTL_DEV)288 289/* clang-format on */290 291#define LANDLOCK_ABI_LAST 6292 293#define XSTR(s) #s294#define STR(s) XSTR(s)295 296/* clang-format off */297 298static const char help[] =299	"usage: " ENV_FS_RO_NAME "=\"...\" " ENV_FS_RW_NAME "=\"...\" "300	"[other environment variables] %1$s <cmd> [args]...\n"301	"\n"302	"Execute the given command in a restricted environment.\n"303	"Multi-valued settings (lists of ports, paths, scopes) are colon-delimited.\n"304	"\n"305	"Mandatory settings:\n"306	"* " ENV_FS_RO_NAME ": paths allowed to be used in a read-only way\n"307	"* " ENV_FS_RW_NAME ": paths allowed to be used in a read-write way\n"308	"\n"309	"Optional settings (when not set, their associated access check "310	"is always allowed, which is different from an empty string which "311	"means an empty list):\n"312	"* " ENV_TCP_BIND_NAME ": ports allowed to bind (server)\n"313	"* " ENV_TCP_CONNECT_NAME ": ports allowed to connect (client)\n"314	"* " ENV_SCOPED_NAME ": actions denied on the outside of the landlock domain\n"315	"  - \"a\" to restrict opening abstract unix sockets\n"316	"  - \"s\" to restrict sending signals\n"317	"\n"318	"Example:\n"319	ENV_FS_RO_NAME "=\"${PATH}:/lib:/usr:/proc:/etc:/dev/urandom\" "320	ENV_FS_RW_NAME "=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "321	ENV_TCP_BIND_NAME "=\"9418\" "322	ENV_TCP_CONNECT_NAME "=\"80:443\" "323	ENV_SCOPED_NAME "=\"a:s\" "324	"%1$s bash -i\n"325	"\n"326	"This sandboxer can use Landlock features up to ABI version "327	STR(LANDLOCK_ABI_LAST) ".\n";328 329/* clang-format on */330 331int main(const int argc, char *const argv[], char *const *const envp)332{333	const char *cmd_path;334	char *const *cmd_argv;335	int ruleset_fd, abi;336	char *env_port_name;337	__u64 access_fs_ro = ACCESS_FS_ROUGHLY_READ,338	      access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE;339 340	struct landlock_ruleset_attr ruleset_attr = {341		.handled_access_fs = access_fs_rw,342		.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |343				      LANDLOCK_ACCESS_NET_CONNECT_TCP,344		.scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |345			  LANDLOCK_SCOPE_SIGNAL,346	};347 348	if (argc < 2) {349		fprintf(stderr, help, argv[0]);350		return 1;351	}352 353	abi = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);354	if (abi < 0) {355		const int err = errno;356 357		perror("Failed to check Landlock compatibility");358		switch (err) {359		case ENOSYS:360			fprintf(stderr,361				"Hint: Landlock is not supported by the current kernel. "362				"To support it, build the kernel with "363				"CONFIG_SECURITY_LANDLOCK=y and prepend "364				"\"landlock,\" to the content of CONFIG_LSM.\n");365			break;366		case EOPNOTSUPP:367			fprintf(stderr,368				"Hint: Landlock is currently disabled. "369				"It can be enabled in the kernel configuration by "370				"prepending \"landlock,\" to the content of CONFIG_LSM, "371				"or at boot time by setting the same content to the "372				"\"lsm\" kernel parameter.\n");373			break;374		}375		return 1;376	}377 378	/* Best-effort security. */379	switch (abi) {380	case 1:381		/*382		 * Removes LANDLOCK_ACCESS_FS_REFER for ABI < 2383		 *384		 * Note: The "refer" operations (file renaming and linking385		 * across different directories) are always forbidden when using386		 * Landlock with ABI 1.387		 *388		 * If only ABI 1 is available, this sandboxer knowingly forbids389		 * refer operations.390		 *391		 * If a program *needs* to do refer operations after enabling392		 * Landlock, it can not use Landlock at ABI level 1.  To be393		 * compatible with different kernel versions, such programs394		 * should then fall back to not restrict themselves at all if395		 * the running kernel only supports ABI 1.396		 */397		ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_REFER;398		__attribute__((fallthrough));399	case 2:400		/* Removes LANDLOCK_ACCESS_FS_TRUNCATE for ABI < 3 */401		ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_TRUNCATE;402		__attribute__((fallthrough));403	case 3:404		/* Removes network support for ABI < 4 */405		ruleset_attr.handled_access_net &=406			~(LANDLOCK_ACCESS_NET_BIND_TCP |407			  LANDLOCK_ACCESS_NET_CONNECT_TCP);408		__attribute__((fallthrough));409	case 4:410		/* Removes LANDLOCK_ACCESS_FS_IOCTL_DEV for ABI < 5 */411		ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_IOCTL_DEV;412 413		__attribute__((fallthrough));414	case 5:415		/* Removes LANDLOCK_SCOPE_* for ABI < 6 */416		ruleset_attr.scoped &= ~(LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |417					 LANDLOCK_SCOPE_SIGNAL);418		fprintf(stderr,419			"Hint: You should update the running kernel "420			"to leverage Landlock features "421			"provided by ABI version %d (instead of %d).\n",422			LANDLOCK_ABI_LAST, abi);423		__attribute__((fallthrough));424	case LANDLOCK_ABI_LAST:425		break;426	default:427		fprintf(stderr,428			"Hint: You should update this sandboxer "429			"to leverage Landlock features "430			"provided by ABI version %d (instead of %d).\n",431			abi, LANDLOCK_ABI_LAST);432	}433	access_fs_ro &= ruleset_attr.handled_access_fs;434	access_fs_rw &= ruleset_attr.handled_access_fs;435 436	/* Removes bind access attribute if not supported by a user. */437	env_port_name = getenv(ENV_TCP_BIND_NAME);438	if (!env_port_name) {439		ruleset_attr.handled_access_net &=440			~LANDLOCK_ACCESS_NET_BIND_TCP;441	}442	/* Removes connect access attribute if not supported by a user. */443	env_port_name = getenv(ENV_TCP_CONNECT_NAME);444	if (!env_port_name) {445		ruleset_attr.handled_access_net &=446			~LANDLOCK_ACCESS_NET_CONNECT_TCP;447	}448 449	if (check_ruleset_scope(ENV_SCOPED_NAME, &ruleset_attr))450		return 1;451 452	ruleset_fd =453		landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);454	if (ruleset_fd < 0) {455		perror("Failed to create a ruleset");456		return 1;457	}458 459	if (populate_ruleset_fs(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro)) {460		goto err_close_ruleset;461	}462	if (populate_ruleset_fs(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) {463		goto err_close_ruleset;464	}465 466	if (populate_ruleset_net(ENV_TCP_BIND_NAME, ruleset_fd,467				 LANDLOCK_ACCESS_NET_BIND_TCP)) {468		goto err_close_ruleset;469	}470	if (populate_ruleset_net(ENV_TCP_CONNECT_NAME, ruleset_fd,471				 LANDLOCK_ACCESS_NET_CONNECT_TCP)) {472		goto err_close_ruleset;473	}474 475	if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {476		perror("Failed to restrict privileges");477		goto err_close_ruleset;478	}479	if (landlock_restrict_self(ruleset_fd, 0)) {480		perror("Failed to enforce ruleset");481		goto err_close_ruleset;482	}483	close(ruleset_fd);484 485	cmd_path = argv[1];486	cmd_argv = argv + 1;487	fprintf(stderr, "Executing the sandboxed command...\n");488	execvpe(cmd_path, cmd_argv, envp);489	fprintf(stderr, "Failed to execute \"%s\": %s\n", cmd_path,490		strerror(errno));491	fprintf(stderr, "Hint: access to the binary, the interpreter or "492			"shared libraries may be denied.\n");493	return 1;494 495err_close_ruleset:496	close(ruleset_fd);497	return 1;498}499