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