|
| 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 <[email protected]> |
| 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 |
| 25 | +static inline int landlock_create_ruleset( |
| 26 | + 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 |
| 34 | +static inline int landlock_add_rule(const int ruleset_fd, |
| 35 | + const enum landlock_rule_type rule_type, |
| 36 | + const void *const rule_attr, const __u32 flags) |
| 37 | +{ |
| 38 | + return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, |
| 39 | + rule_attr, flags); |
| 40 | +} |
| 41 | +#endif |
| 42 | + |
| 43 | +#ifndef landlock_restrict_self |
| 44 | +static inline int landlock_restrict_self(const int ruleset_fd, |
| 45 | + const __u32 flags) |
| 46 | +{ |
| 47 | + return syscall(__NR_landlock_restrict_self, ruleset_fd, flags); |
| 48 | +} |
| 49 | +#endif |
| 50 | + |
| 51 | +#define ENV_FS_RO_NAME "LL_FS_RO" |
| 52 | +#define ENV_FS_RW_NAME "LL_FS_RW" |
| 53 | +#define ENV_PATH_TOKEN ":" |
| 54 | + |
| 55 | +static int parse_path(char *env_path, const char ***const path_list) |
| 56 | +{ |
| 57 | + int i, num_paths = 0; |
| 58 | + |
| 59 | + if (env_path) { |
| 60 | + num_paths++; |
| 61 | + for (i = 0; env_path[i]; i++) { |
| 62 | + if (env_path[i] == ENV_PATH_TOKEN[0]) |
| 63 | + num_paths++; |
| 64 | + } |
| 65 | + } |
| 66 | + *path_list = malloc(num_paths * sizeof(**path_list)); |
| 67 | + for (i = 0; i < num_paths; i++) |
| 68 | + (*path_list)[i] = strsep(&env_path, ENV_PATH_TOKEN); |
| 69 | + |
| 70 | + return num_paths; |
| 71 | +} |
| 72 | + |
| 73 | +#define ACCESS_FILE ( \ |
| 74 | + LANDLOCK_ACCESS_FS_EXECUTE | \ |
| 75 | + LANDLOCK_ACCESS_FS_WRITE_FILE | \ |
| 76 | + LANDLOCK_ACCESS_FS_READ_FILE) |
| 77 | + |
| 78 | +static int populate_ruleset( |
| 79 | + const char *const env_var, const int ruleset_fd, |
| 80 | + const __u64 allowed_access) |
| 81 | +{ |
| 82 | + int num_paths, i, ret = 1; |
| 83 | + char *env_path_name; |
| 84 | + const char **path_list = NULL; |
| 85 | + struct landlock_path_beneath_attr path_beneath = { |
| 86 | + .parent_fd = -1, |
| 87 | + }; |
| 88 | + |
| 89 | + env_path_name = getenv(env_var); |
| 90 | + if (!env_path_name) { |
| 91 | + /* Prevents users to forget a setting. */ |
| 92 | + fprintf(stderr, "Missing environment variable %s\n", env_var); |
| 93 | + return 1; |
| 94 | + } |
| 95 | + env_path_name = strdup(env_path_name); |
| 96 | + unsetenv(env_var); |
| 97 | + num_paths = parse_path(env_path_name, &path_list); |
| 98 | + if (num_paths == 1 && path_list[0][0] == '\0') { |
| 99 | + /* |
| 100 | + * Allows to not use all possible restrictions (e.g. use |
| 101 | + * LL_FS_RO without LL_FS_RW). |
| 102 | + */ |
| 103 | + ret = 0; |
| 104 | + goto out_free_name; |
| 105 | + } |
| 106 | + |
| 107 | + for (i = 0; i < num_paths; i++) { |
| 108 | + struct stat statbuf; |
| 109 | + |
| 110 | + path_beneath.parent_fd = open(path_list[i], O_PATH | |
| 111 | + O_CLOEXEC); |
| 112 | + if (path_beneath.parent_fd < 0) { |
| 113 | + fprintf(stderr, "Failed to open \"%s\": %s\n", |
| 114 | + path_list[i], |
| 115 | + strerror(errno)); |
| 116 | + goto out_free_name; |
| 117 | + } |
| 118 | + if (fstat(path_beneath.parent_fd, &statbuf)) { |
| 119 | + close(path_beneath.parent_fd); |
| 120 | + goto out_free_name; |
| 121 | + } |
| 122 | + path_beneath.allowed_access = allowed_access; |
| 123 | + if (!S_ISDIR(statbuf.st_mode)) |
| 124 | + path_beneath.allowed_access &= ACCESS_FILE; |
| 125 | + if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, |
| 126 | + &path_beneath, 0)) { |
| 127 | + fprintf(stderr, "Failed to update the ruleset with \"%s\": %s\n", |
| 128 | + path_list[i], strerror(errno)); |
| 129 | + close(path_beneath.parent_fd); |
| 130 | + goto out_free_name; |
| 131 | + } |
| 132 | + close(path_beneath.parent_fd); |
| 133 | + } |
| 134 | + ret = 0; |
| 135 | + |
| 136 | +out_free_name: |
| 137 | + free(env_path_name); |
| 138 | + return ret; |
| 139 | +} |
| 140 | + |
| 141 | +#define ACCESS_FS_ROUGHLY_READ ( \ |
| 142 | + LANDLOCK_ACCESS_FS_EXECUTE | \ |
| 143 | + LANDLOCK_ACCESS_FS_READ_FILE | \ |
| 144 | + LANDLOCK_ACCESS_FS_READ_DIR) |
| 145 | + |
| 146 | +#define ACCESS_FS_ROUGHLY_WRITE ( \ |
| 147 | + LANDLOCK_ACCESS_FS_WRITE_FILE | \ |
| 148 | + LANDLOCK_ACCESS_FS_REMOVE_DIR | \ |
| 149 | + LANDLOCK_ACCESS_FS_REMOVE_FILE | \ |
| 150 | + LANDLOCK_ACCESS_FS_MAKE_CHAR | \ |
| 151 | + LANDLOCK_ACCESS_FS_MAKE_DIR | \ |
| 152 | + LANDLOCK_ACCESS_FS_MAKE_REG | \ |
| 153 | + LANDLOCK_ACCESS_FS_MAKE_SOCK | \ |
| 154 | + LANDLOCK_ACCESS_FS_MAKE_FIFO | \ |
| 155 | + LANDLOCK_ACCESS_FS_MAKE_BLOCK | \ |
| 156 | + LANDLOCK_ACCESS_FS_MAKE_SYM) |
| 157 | + |
| 158 | +int main(const int argc, char *const argv[], char *const *const envp) |
| 159 | +{ |
| 160 | + const char *cmd_path; |
| 161 | + char *const *cmd_argv; |
| 162 | + int ruleset_fd; |
| 163 | + struct landlock_ruleset_attr ruleset_attr = { |
| 164 | + .handled_access_fs = ACCESS_FS_ROUGHLY_READ | |
| 165 | + ACCESS_FS_ROUGHLY_WRITE, |
| 166 | + }; |
| 167 | + |
| 168 | + if (argc < 2) { |
| 169 | + fprintf(stderr, "usage: %s=\"...\" %s=\"...\" %s <cmd> [args]...\n\n", |
| 170 | + ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]); |
| 171 | + fprintf(stderr, "Launch a command in a restricted environment.\n\n"); |
| 172 | + fprintf(stderr, "Environment variables containing paths, " |
| 173 | + "each separated by a colon:\n"); |
| 174 | + fprintf(stderr, "* %s: list of paths allowed to be used in a read-only way.\n", |
| 175 | + ENV_FS_RO_NAME); |
| 176 | + fprintf(stderr, "* %s: list of paths allowed to be used in a read-write way.\n", |
| 177 | + ENV_FS_RW_NAME); |
| 178 | + fprintf(stderr, "\nexample:\n" |
| 179 | + "%s=\"/bin:/lib:/usr:/proc:/etc:/dev/urandom\" " |
| 180 | + "%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" " |
| 181 | + "%s bash -i\n", |
| 182 | + ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]); |
| 183 | + return 1; |
| 184 | + } |
| 185 | + |
| 186 | + ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); |
| 187 | + if (ruleset_fd < 0) { |
| 188 | + const int err = errno; |
| 189 | + |
| 190 | + perror("Failed to create a ruleset"); |
| 191 | + switch (err) { |
| 192 | + case ENOSYS: |
| 193 | + fprintf(stderr, "Hint: Landlock is not supported by the current kernel. " |
| 194 | + "To support it, build the kernel with " |
| 195 | + "CONFIG_SECURITY_LANDLOCK=y and prepend " |
| 196 | + "\"landlock,\" to the content of CONFIG_LSM.\n"); |
| 197 | + break; |
| 198 | + case EOPNOTSUPP: |
| 199 | + fprintf(stderr, "Hint: Landlock is currently disabled. " |
| 200 | + "It can be enabled in the kernel configuration by " |
| 201 | + "prepending \"landlock,\" to the content of CONFIG_LSM, " |
| 202 | + "or at boot time by setting the same content to the " |
| 203 | + "\"lsm\" kernel parameter.\n"); |
| 204 | + break; |
| 205 | + } |
| 206 | + return 1; |
| 207 | + } |
| 208 | + if (populate_ruleset(ENV_FS_RO_NAME, ruleset_fd, |
| 209 | + ACCESS_FS_ROUGHLY_READ)) { |
| 210 | + goto err_close_ruleset; |
| 211 | + } |
| 212 | + if (populate_ruleset(ENV_FS_RW_NAME, ruleset_fd, |
| 213 | + ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE)) { |
| 214 | + goto err_close_ruleset; |
| 215 | + } |
| 216 | + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { |
| 217 | + perror("Failed to restrict privileges"); |
| 218 | + goto err_close_ruleset; |
| 219 | + } |
| 220 | + if (landlock_restrict_self(ruleset_fd, 0)) { |
| 221 | + perror("Failed to enforce ruleset"); |
| 222 | + goto err_close_ruleset; |
| 223 | + } |
| 224 | + close(ruleset_fd); |
| 225 | + |
| 226 | + cmd_path = argv[1]; |
| 227 | + cmd_argv = argv + 1; |
| 228 | + execvpe(cmd_path, cmd_argv, envp); |
| 229 | + fprintf(stderr, "Failed to execute \"%s\": %s\n", cmd_path, |
| 230 | + strerror(errno)); |
| 231 | + fprintf(stderr, "Hint: access to the binary, the interpreter or " |
| 232 | + "shared libraries may be denied.\n"); |
| 233 | + return 1; |
| 234 | + |
| 235 | +err_close_ruleset: |
| 236 | + close(ruleset_fd); |
| 237 | + return 1; |
| 238 | +} |
0 commit comments