Commit | Line | Data |
---|---|---|
ba84b0bf MS |
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 | |
81709f3d MS |
25 | static inline int |
26 | landlock_create_ruleset(const struct landlock_ruleset_attr *const attr, | |
27 | const size_t size, const __u32 flags) | |
ba84b0bf MS |
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, | |
81709f3d MS |
35 | const enum landlock_rule_type rule_type, |
36 | const void *const rule_attr, | |
37 | const __u32 flags) | |
ba84b0bf | 38 | { |
81709f3d MS |
39 | return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr, |
40 | flags); | |
ba84b0bf MS |
41 | } |
42 | #endif | |
43 | ||
44 | #ifndef landlock_restrict_self | |
45 | static inline int landlock_restrict_self(const int ruleset_fd, | |
81709f3d | 46 | const __u32 flags) |
ba84b0bf MS |
47 | { |
48 | return syscall(__NR_landlock_restrict_self, ruleset_fd, flags); | |
49 | } | |
50 | #endif | |
51 | ||
52 | #define ENV_FS_RO_NAME "LL_FS_RO" | |
53 | #define ENV_FS_RW_NAME "LL_FS_RW" | |
54 | #define ENV_PATH_TOKEN ":" | |
55 | ||
56 | static int parse_path(char *env_path, const char ***const path_list) | |
57 | { | |
58 | int i, num_paths = 0; | |
59 | ||
60 | if (env_path) { | |
61 | num_paths++; | |
62 | for (i = 0; env_path[i]; i++) { | |
63 | if (env_path[i] == ENV_PATH_TOKEN[0]) | |
64 | num_paths++; | |
65 | } | |
66 | } | |
67 | *path_list = malloc(num_paths * sizeof(**path_list)); | |
68 | for (i = 0; i < num_paths; i++) | |
69 | (*path_list)[i] = strsep(&env_path, ENV_PATH_TOKEN); | |
70 | ||
71 | return num_paths; | |
72 | } | |
73 | ||
9805a722 MS |
74 | /* clang-format off */ |
75 | ||
ba84b0bf MS |
76 | #define ACCESS_FILE ( \ |
77 | LANDLOCK_ACCESS_FS_EXECUTE | \ | |
78 | LANDLOCK_ACCESS_FS_WRITE_FILE | \ | |
faeb9197 GN |
79 | LANDLOCK_ACCESS_FS_READ_FILE | \ |
80 | LANDLOCK_ACCESS_FS_TRUNCATE) | |
ba84b0bf | 81 | |
9805a722 MS |
82 | /* clang-format on */ |
83 | ||
81709f3d MS |
84 | static int populate_ruleset(const char *const env_var, const int ruleset_fd, |
85 | const __u64 allowed_access) | |
ba84b0bf MS |
86 | { |
87 | int num_paths, i, ret = 1; | |
88 | char *env_path_name; | |
89 | const char **path_list = NULL; | |
90 | struct landlock_path_beneath_attr path_beneath = { | |
91 | .parent_fd = -1, | |
92 | }; | |
93 | ||
94 | env_path_name = getenv(env_var); | |
95 | if (!env_path_name) { | |
96 | /* Prevents users to forget a setting. */ | |
97 | fprintf(stderr, "Missing environment variable %s\n", env_var); | |
98 | return 1; | |
99 | } | |
100 | env_path_name = strdup(env_path_name); | |
101 | unsetenv(env_var); | |
102 | num_paths = parse_path(env_path_name, &path_list); | |
103 | if (num_paths == 1 && path_list[0][0] == '\0') { | |
104 | /* | |
105 | * Allows to not use all possible restrictions (e.g. use | |
106 | * LL_FS_RO without LL_FS_RW). | |
107 | */ | |
108 | ret = 0; | |
109 | goto out_free_name; | |
110 | } | |
111 | ||
112 | for (i = 0; i < num_paths; i++) { | |
113 | struct stat statbuf; | |
114 | ||
81709f3d | 115 | path_beneath.parent_fd = open(path_list[i], O_PATH | O_CLOEXEC); |
ba84b0bf MS |
116 | if (path_beneath.parent_fd < 0) { |
117 | fprintf(stderr, "Failed to open \"%s\": %s\n", | |
81709f3d | 118 | path_list[i], strerror(errno)); |
ba84b0bf MS |
119 | goto out_free_name; |
120 | } | |
121 | if (fstat(path_beneath.parent_fd, &statbuf)) { | |
122 | close(path_beneath.parent_fd); | |
123 | goto out_free_name; | |
124 | } | |
125 | path_beneath.allowed_access = allowed_access; | |
126 | if (!S_ISDIR(statbuf.st_mode)) | |
127 | path_beneath.allowed_access &= ACCESS_FILE; | |
128 | if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, | |
81709f3d MS |
129 | &path_beneath, 0)) { |
130 | fprintf(stderr, | |
131 | "Failed to update the ruleset with \"%s\": %s\n", | |
132 | path_list[i], strerror(errno)); | |
ba84b0bf MS |
133 | close(path_beneath.parent_fd); |
134 | goto out_free_name; | |
135 | } | |
136 | close(path_beneath.parent_fd); | |
137 | } | |
138 | ret = 0; | |
139 | ||
140 | out_free_name: | |
66b513b7 | 141 | free(path_list); |
ba84b0bf MS |
142 | free(env_path_name); |
143 | return ret; | |
144 | } | |
145 | ||
9805a722 MS |
146 | /* clang-format off */ |
147 | ||
ba84b0bf MS |
148 | #define ACCESS_FS_ROUGHLY_READ ( \ |
149 | LANDLOCK_ACCESS_FS_EXECUTE | \ | |
150 | LANDLOCK_ACCESS_FS_READ_FILE | \ | |
151 | LANDLOCK_ACCESS_FS_READ_DIR) | |
152 | ||
153 | #define ACCESS_FS_ROUGHLY_WRITE ( \ | |
154 | LANDLOCK_ACCESS_FS_WRITE_FILE | \ | |
155 | LANDLOCK_ACCESS_FS_REMOVE_DIR | \ | |
156 | LANDLOCK_ACCESS_FS_REMOVE_FILE | \ | |
157 | LANDLOCK_ACCESS_FS_MAKE_CHAR | \ | |
158 | LANDLOCK_ACCESS_FS_MAKE_DIR | \ | |
159 | LANDLOCK_ACCESS_FS_MAKE_REG | \ | |
160 | LANDLOCK_ACCESS_FS_MAKE_SOCK | \ | |
161 | LANDLOCK_ACCESS_FS_MAKE_FIFO | \ | |
162 | LANDLOCK_ACCESS_FS_MAKE_BLOCK | \ | |
76b902f8 | 163 | LANDLOCK_ACCESS_FS_MAKE_SYM | \ |
faeb9197 GN |
164 | LANDLOCK_ACCESS_FS_REFER | \ |
165 | LANDLOCK_ACCESS_FS_TRUNCATE) | |
76b902f8 | 166 | |
9805a722 MS |
167 | /* clang-format on */ |
168 | ||
faeb9197 | 169 | #define LANDLOCK_ABI_LAST 3 |
903cfe8a | 170 | |
ba84b0bf MS |
171 | int main(const int argc, char *const argv[], char *const *const envp) |
172 | { | |
173 | const char *cmd_path; | |
174 | char *const *cmd_argv; | |
76b902f8 MS |
175 | int ruleset_fd, abi; |
176 | __u64 access_fs_ro = ACCESS_FS_ROUGHLY_READ, | |
177 | access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE; | |
ba84b0bf | 178 | struct landlock_ruleset_attr ruleset_attr = { |
76b902f8 | 179 | .handled_access_fs = access_fs_rw, |
ba84b0bf MS |
180 | }; |
181 | ||
182 | if (argc < 2) { | |
81709f3d MS |
183 | fprintf(stderr, |
184 | "usage: %s=\"...\" %s=\"...\" %s <cmd> [args]...\n\n", | |
185 | ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]); | |
186 | fprintf(stderr, | |
187 | "Launch a command in a restricted environment.\n\n"); | |
ba84b0bf MS |
188 | fprintf(stderr, "Environment variables containing paths, " |
189 | "each separated by a colon:\n"); | |
81709f3d MS |
190 | fprintf(stderr, |
191 | "* %s: list of paths allowed to be used in a read-only way.\n", | |
192 | ENV_FS_RO_NAME); | |
193 | fprintf(stderr, | |
194 | "* %s: list of paths allowed to be used in a read-write way.\n", | |
195 | ENV_FS_RW_NAME); | |
196 | fprintf(stderr, | |
197 | "\nexample:\n" | |
198 | "%s=\"/bin:/lib:/usr:/proc:/etc:/dev/urandom\" " | |
199 | "%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" " | |
903cfe8a | 200 | "%s bash -i\n\n", |
81709f3d | 201 | ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]); |
903cfe8a MS |
202 | fprintf(stderr, |
203 | "This sandboxer can use Landlock features " | |
204 | "up to ABI version %d.\n", | |
205 | LANDLOCK_ABI_LAST); | |
ba84b0bf MS |
206 | return 1; |
207 | } | |
208 | ||
76b902f8 MS |
209 | abi = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION); |
210 | if (abi < 0) { | |
ba84b0bf MS |
211 | const int err = errno; |
212 | ||
76b902f8 | 213 | perror("Failed to check Landlock compatibility"); |
ba84b0bf MS |
214 | switch (err) { |
215 | case ENOSYS: | |
81709f3d MS |
216 | fprintf(stderr, |
217 | "Hint: Landlock is not supported by the current kernel. " | |
218 | "To support it, build the kernel with " | |
219 | "CONFIG_SECURITY_LANDLOCK=y and prepend " | |
220 | "\"landlock,\" to the content of CONFIG_LSM.\n"); | |
ba84b0bf MS |
221 | break; |
222 | case EOPNOTSUPP: | |
81709f3d MS |
223 | fprintf(stderr, |
224 | "Hint: Landlock is currently disabled. " | |
225 | "It can be enabled in the kernel configuration by " | |
226 | "prepending \"landlock,\" to the content of CONFIG_LSM, " | |
227 | "or at boot time by setting the same content to the " | |
228 | "\"lsm\" kernel parameter.\n"); | |
ba84b0bf MS |
229 | break; |
230 | } | |
231 | return 1; | |
232 | } | |
903cfe8a | 233 | |
76b902f8 | 234 | /* Best-effort security. */ |
903cfe8a MS |
235 | switch (abi) { |
236 | case 1: | |
f6e53fb2 GN |
237 | /* |
238 | * Removes LANDLOCK_ACCESS_FS_REFER for ABI < 2 | |
239 | * | |
240 | * Note: The "refer" operations (file renaming and linking | |
241 | * across different directories) are always forbidden when using | |
242 | * Landlock with ABI 1. | |
243 | * | |
244 | * If only ABI 1 is available, this sandboxer knowingly forbids | |
245 | * refer operations. | |
246 | * | |
247 | * If a program *needs* to do refer operations after enabling | |
248 | * Landlock, it can not use Landlock at ABI level 1. To be | |
249 | * compatible with different kernel versions, such programs | |
250 | * should then fall back to not restrict themselves at all if | |
251 | * the running kernel only supports ABI 1. | |
252 | */ | |
903cfe8a | 253 | ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_REFER; |
faeb9197 GN |
254 | __attribute__((fallthrough)); |
255 | case 2: | |
256 | /* Removes LANDLOCK_ACCESS_FS_TRUNCATE for ABI < 3 */ | |
257 | ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_TRUNCATE; | |
903cfe8a MS |
258 | |
259 | fprintf(stderr, | |
260 | "Hint: You should update the running kernel " | |
261 | "to leverage Landlock features " | |
262 | "provided by ABI version %d (instead of %d).\n", | |
263 | LANDLOCK_ABI_LAST, abi); | |
264 | __attribute__((fallthrough)); | |
265 | case LANDLOCK_ABI_LAST: | |
266 | break; | |
267 | default: | |
268 | fprintf(stderr, | |
269 | "Hint: You should update this sandboxer " | |
270 | "to leverage Landlock features " | |
271 | "provided by ABI version %d (instead of %d).\n", | |
272 | abi, LANDLOCK_ABI_LAST); | |
76b902f8 | 273 | } |
903cfe8a MS |
274 | access_fs_ro &= ruleset_attr.handled_access_fs; |
275 | access_fs_rw &= ruleset_attr.handled_access_fs; | |
76b902f8 MS |
276 | |
277 | ruleset_fd = | |
278 | landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); | |
279 | if (ruleset_fd < 0) { | |
280 | perror("Failed to create a ruleset"); | |
281 | return 1; | |
282 | } | |
283 | if (populate_ruleset(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro)) { | |
ba84b0bf MS |
284 | goto err_close_ruleset; |
285 | } | |
76b902f8 | 286 | if (populate_ruleset(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) { |
ba84b0bf MS |
287 | goto err_close_ruleset; |
288 | } | |
289 | if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { | |
290 | perror("Failed to restrict privileges"); | |
291 | goto err_close_ruleset; | |
292 | } | |
293 | if (landlock_restrict_self(ruleset_fd, 0)) { | |
294 | perror("Failed to enforce ruleset"); | |
295 | goto err_close_ruleset; | |
296 | } | |
297 | close(ruleset_fd); | |
298 | ||
299 | cmd_path = argv[1]; | |
300 | cmd_argv = argv + 1; | |
301 | execvpe(cmd_path, cmd_argv, envp); | |
302 | fprintf(stderr, "Failed to execute \"%s\": %s\n", cmd_path, | |
81709f3d | 303 | strerror(errno)); |
ba84b0bf MS |
304 | fprintf(stderr, "Hint: access to the binary, the interpreter or " |
305 | "shared libraries may be denied.\n"); | |
306 | return 1; | |
307 | ||
308 | err_close_ruleset: | |
309 | close(ruleset_fd); | |
310 | return 1; | |
311 | } |