| @@ -0,0 +1,238 @@ |
| | 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 <mic@digikod.net> |
| | 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 | } |