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