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
2581709f3dSMickaël Salaün static inline int
landlock_create_ruleset(const struct landlock_ruleset_attr * const attr,const size_t size,const __u32 flags)2681709f3dSMickaë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
landlock_add_rule(const int ruleset_fd,const enum landlock_rule_type rule_type,const void * const rule_attr,const __u32 flags)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,
3681709f3dSMickaël Salaün const void *const rule_attr,
3781709f3dSMickaël Salaün const __u32 flags)
38ba84b0bfSMickaël Salaün {
3981709f3dSMickaël Salaün return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr,
4081709f3dSMickaë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
landlock_restrict_self(const int ruleset_fd,const __u32 flags)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
parse_path(char * env_path,const char *** const path_list)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 | \
79faeb9197SGünther Noack LANDLOCK_ACCESS_FS_READ_FILE | \
80faeb9197SGünther Noack LANDLOCK_ACCESS_FS_TRUNCATE)
81ba84b0bfSMickaël Salaün
829805a722SMickaël Salaün /* clang-format on */
839805a722SMickaël Salaün
populate_ruleset(const char * const env_var,const int ruleset_fd,const __u64 allowed_access)8481709f3dSMickaël Salaün static int populate_ruleset(const char *const env_var, const int ruleset_fd,
85ba84b0bfSMickaël Salaün const __u64 allowed_access)
86ba84b0bfSMickaël Salaün {
87ba84b0bfSMickaël Salaün int num_paths, i, ret = 1;
88ba84b0bfSMickaël Salaün char *env_path_name;
89ba84b0bfSMickaël Salaün const char **path_list = NULL;
90ba84b0bfSMickaël Salaün struct landlock_path_beneath_attr path_beneath = {
91ba84b0bfSMickaël Salaün .parent_fd = -1,
92ba84b0bfSMickaël Salaün };
93ba84b0bfSMickaël Salaün
94ba84b0bfSMickaël Salaün env_path_name = getenv(env_var);
95ba84b0bfSMickaël Salaün if (!env_path_name) {
96ba84b0bfSMickaël Salaün /* Prevents users to forget a setting. */
97ba84b0bfSMickaël Salaün fprintf(stderr, "Missing environment variable %s\n", env_var);
98ba84b0bfSMickaël Salaün return 1;
99ba84b0bfSMickaël Salaün }
100ba84b0bfSMickaël Salaün env_path_name = strdup(env_path_name);
101ba84b0bfSMickaël Salaün unsetenv(env_var);
102ba84b0bfSMickaël Salaün num_paths = parse_path(env_path_name, &path_list);
103ba84b0bfSMickaël Salaün if (num_paths == 1 && path_list[0][0] == '\0') {
104ba84b0bfSMickaël Salaün /*
105ba84b0bfSMickaël Salaün * Allows to not use all possible restrictions (e.g. use
106ba84b0bfSMickaël Salaün * LL_FS_RO without LL_FS_RW).
107ba84b0bfSMickaël Salaün */
108ba84b0bfSMickaël Salaün ret = 0;
109ba84b0bfSMickaël Salaün goto out_free_name;
110ba84b0bfSMickaël Salaün }
111ba84b0bfSMickaël Salaün
112ba84b0bfSMickaël Salaün for (i = 0; i < num_paths; i++) {
113ba84b0bfSMickaël Salaün struct stat statbuf;
114ba84b0bfSMickaël Salaün
11581709f3dSMickaël Salaün path_beneath.parent_fd = open(path_list[i], O_PATH | O_CLOEXEC);
116ba84b0bfSMickaël Salaün if (path_beneath.parent_fd < 0) {
117ba84b0bfSMickaël Salaün fprintf(stderr, "Failed to open \"%s\": %s\n",
11881709f3dSMickaël Salaün path_list[i], strerror(errno));
119ba84b0bfSMickaël Salaün goto out_free_name;
120ba84b0bfSMickaël Salaün }
121ba84b0bfSMickaël Salaün if (fstat(path_beneath.parent_fd, &statbuf)) {
122ba84b0bfSMickaël Salaün close(path_beneath.parent_fd);
123ba84b0bfSMickaël Salaün goto out_free_name;
124ba84b0bfSMickaël Salaün }
125ba84b0bfSMickaël Salaün path_beneath.allowed_access = allowed_access;
126ba84b0bfSMickaël Salaün if (!S_ISDIR(statbuf.st_mode))
127ba84b0bfSMickaël Salaün path_beneath.allowed_access &= ACCESS_FILE;
128ba84b0bfSMickaël Salaün if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
129ba84b0bfSMickaël Salaün &path_beneath, 0)) {
13081709f3dSMickaël Salaün fprintf(stderr,
13181709f3dSMickaël Salaün "Failed to update the ruleset with \"%s\": %s\n",
132ba84b0bfSMickaël Salaün path_list[i], strerror(errno));
133ba84b0bfSMickaël Salaün close(path_beneath.parent_fd);
134ba84b0bfSMickaël Salaün goto out_free_name;
135ba84b0bfSMickaël Salaün }
136ba84b0bfSMickaël Salaün close(path_beneath.parent_fd);
137ba84b0bfSMickaël Salaün }
138ba84b0bfSMickaël Salaün ret = 0;
139ba84b0bfSMickaël Salaün
140ba84b0bfSMickaël Salaün out_free_name:
14166b513b7STom Rix free(path_list);
142ba84b0bfSMickaël Salaün free(env_path_name);
143ba84b0bfSMickaël Salaün return ret;
144ba84b0bfSMickaël Salaün }
145ba84b0bfSMickaël Salaün
1469805a722SMickaël Salaün /* clang-format off */
1479805a722SMickaël Salaün
148ba84b0bfSMickaël Salaün #define ACCESS_FS_ROUGHLY_READ ( \
149ba84b0bfSMickaël Salaün LANDLOCK_ACCESS_FS_EXECUTE | \
150ba84b0bfSMickaël Salaün LANDLOCK_ACCESS_FS_READ_FILE | \
151ba84b0bfSMickaël Salaün LANDLOCK_ACCESS_FS_READ_DIR)
152ba84b0bfSMickaël Salaün
153ba84b0bfSMickaël Salaün #define ACCESS_FS_ROUGHLY_WRITE ( \
154ba84b0bfSMickaël Salaün LANDLOCK_ACCESS_FS_WRITE_FILE | \
155ba84b0bfSMickaël Salaün LANDLOCK_ACCESS_FS_REMOVE_DIR | \
156ba84b0bfSMickaël Salaün LANDLOCK_ACCESS_FS_REMOVE_FILE | \
157ba84b0bfSMickaël Salaün LANDLOCK_ACCESS_FS_MAKE_CHAR | \
158ba84b0bfSMickaël Salaün LANDLOCK_ACCESS_FS_MAKE_DIR | \
159ba84b0bfSMickaël Salaün LANDLOCK_ACCESS_FS_MAKE_REG | \
160ba84b0bfSMickaël Salaün LANDLOCK_ACCESS_FS_MAKE_SOCK | \
161ba84b0bfSMickaël Salaün LANDLOCK_ACCESS_FS_MAKE_FIFO | \
162ba84b0bfSMickaël Salaün LANDLOCK_ACCESS_FS_MAKE_BLOCK | \
16376b902f8SMickaël Salaün LANDLOCK_ACCESS_FS_MAKE_SYM | \
164faeb9197SGünther Noack LANDLOCK_ACCESS_FS_REFER | \
165faeb9197SGünther Noack LANDLOCK_ACCESS_FS_TRUNCATE)
16676b902f8SMickaël Salaün
1679805a722SMickaël Salaün /* clang-format on */
1689805a722SMickaël Salaün
169faeb9197SGünther Noack #define LANDLOCK_ABI_LAST 3
170903cfe8aSMickaël Salaün
main(const int argc,char * const argv[],char * const * const envp)171ba84b0bfSMickaël Salaün int main(const int argc, char *const argv[], char *const *const envp)
172ba84b0bfSMickaël Salaün {
173ba84b0bfSMickaël Salaün const char *cmd_path;
174ba84b0bfSMickaël Salaün char *const *cmd_argv;
17576b902f8SMickaël Salaün int ruleset_fd, abi;
17676b902f8SMickaël Salaün __u64 access_fs_ro = ACCESS_FS_ROUGHLY_READ,
17776b902f8SMickaël Salaün access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE;
178ba84b0bfSMickaël Salaün struct landlock_ruleset_attr ruleset_attr = {
17976b902f8SMickaël Salaün .handled_access_fs = access_fs_rw,
180ba84b0bfSMickaël Salaün };
181ba84b0bfSMickaël Salaün
182ba84b0bfSMickaël Salaün if (argc < 2) {
18381709f3dSMickaël Salaün fprintf(stderr,
18481709f3dSMickaël Salaün "usage: %s=\"...\" %s=\"...\" %s <cmd> [args]...\n\n",
185ba84b0bfSMickaël Salaün ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]);
18681709f3dSMickaël Salaün fprintf(stderr,
18781709f3dSMickaël Salaün "Launch a command in a restricted environment.\n\n");
188ba84b0bfSMickaël Salaün fprintf(stderr, "Environment variables containing paths, "
189ba84b0bfSMickaël Salaün "each separated by a colon:\n");
19081709f3dSMickaël Salaün fprintf(stderr,
19181709f3dSMickaël Salaün "* %s: list of paths allowed to be used in a read-only way.\n",
192ba84b0bfSMickaël Salaün ENV_FS_RO_NAME);
19381709f3dSMickaël Salaün fprintf(stderr,
19481709f3dSMickaël Salaün "* %s: list of paths allowed to be used in a read-write way.\n",
195ba84b0bfSMickaël Salaün ENV_FS_RW_NAME);
19681709f3dSMickaël Salaün fprintf(stderr,
19781709f3dSMickaël Salaün "\nexample:\n"
198ba84b0bfSMickaël Salaün "%s=\"/bin:/lib:/usr:/proc:/etc:/dev/urandom\" "
199ba84b0bfSMickaël Salaün "%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
200903cfe8aSMickaël Salaün "%s bash -i\n\n",
201ba84b0bfSMickaël Salaün ENV_FS_RO_NAME, ENV_FS_RW_NAME, argv[0]);
202903cfe8aSMickaël Salaün fprintf(stderr,
203903cfe8aSMickaël Salaün "This sandboxer can use Landlock features "
204903cfe8aSMickaël Salaün "up to ABI version %d.\n",
205903cfe8aSMickaël Salaün LANDLOCK_ABI_LAST);
206ba84b0bfSMickaël Salaün return 1;
207ba84b0bfSMickaël Salaün }
208ba84b0bfSMickaël Salaün
20976b902f8SMickaël Salaün abi = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
21076b902f8SMickaël Salaün if (abi < 0) {
211ba84b0bfSMickaël Salaün const int err = errno;
212ba84b0bfSMickaël Salaün
21376b902f8SMickaël Salaün perror("Failed to check Landlock compatibility");
214ba84b0bfSMickaël Salaün switch (err) {
215ba84b0bfSMickaël Salaün case ENOSYS:
21681709f3dSMickaël Salaün fprintf(stderr,
21781709f3dSMickaël Salaün "Hint: Landlock is not supported by the current kernel. "
218ba84b0bfSMickaël Salaün "To support it, build the kernel with "
219ba84b0bfSMickaël Salaün "CONFIG_SECURITY_LANDLOCK=y and prepend "
220ba84b0bfSMickaël Salaün "\"landlock,\" to the content of CONFIG_LSM.\n");
221ba84b0bfSMickaël Salaün break;
222ba84b0bfSMickaël Salaün case EOPNOTSUPP:
22381709f3dSMickaël Salaün fprintf(stderr,
22481709f3dSMickaël Salaün "Hint: Landlock is currently disabled. "
225ba84b0bfSMickaël Salaün "It can be enabled in the kernel configuration by "
226ba84b0bfSMickaël Salaün "prepending \"landlock,\" to the content of CONFIG_LSM, "
227ba84b0bfSMickaël Salaün "or at boot time by setting the same content to the "
228ba84b0bfSMickaël Salaün "\"lsm\" kernel parameter.\n");
229ba84b0bfSMickaël Salaün break;
230ba84b0bfSMickaël Salaün }
231ba84b0bfSMickaël Salaün return 1;
232ba84b0bfSMickaël Salaün }
233903cfe8aSMickaël Salaün
23476b902f8SMickaël Salaün /* Best-effort security. */
235903cfe8aSMickaël Salaün switch (abi) {
236903cfe8aSMickaël Salaün case 1:
237*f6e53fb2SGünther Noack /*
238*f6e53fb2SGünther Noack * Removes LANDLOCK_ACCESS_FS_REFER for ABI < 2
239*f6e53fb2SGünther Noack *
240*f6e53fb2SGünther Noack * Note: The "refer" operations (file renaming and linking
241*f6e53fb2SGünther Noack * across different directories) are always forbidden when using
242*f6e53fb2SGünther Noack * Landlock with ABI 1.
243*f6e53fb2SGünther Noack *
244*f6e53fb2SGünther Noack * If only ABI 1 is available, this sandboxer knowingly forbids
245*f6e53fb2SGünther Noack * refer operations.
246*f6e53fb2SGünther Noack *
247*f6e53fb2SGünther Noack * If a program *needs* to do refer operations after enabling
248*f6e53fb2SGünther Noack * Landlock, it can not use Landlock at ABI level 1. To be
249*f6e53fb2SGünther Noack * compatible with different kernel versions, such programs
250*f6e53fb2SGünther Noack * should then fall back to not restrict themselves at all if
251*f6e53fb2SGünther Noack * the running kernel only supports ABI 1.
252*f6e53fb2SGünther Noack */
253903cfe8aSMickaël Salaün ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_REFER;
254faeb9197SGünther Noack __attribute__((fallthrough));
255faeb9197SGünther Noack case 2:
256faeb9197SGünther Noack /* Removes LANDLOCK_ACCESS_FS_TRUNCATE for ABI < 3 */
257faeb9197SGünther Noack ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_TRUNCATE;
258903cfe8aSMickaël Salaün
259903cfe8aSMickaël Salaün fprintf(stderr,
260903cfe8aSMickaël Salaün "Hint: You should update the running kernel "
261903cfe8aSMickaël Salaün "to leverage Landlock features "
262903cfe8aSMickaël Salaün "provided by ABI version %d (instead of %d).\n",
263903cfe8aSMickaël Salaün LANDLOCK_ABI_LAST, abi);
264903cfe8aSMickaël Salaün __attribute__((fallthrough));
265903cfe8aSMickaël Salaün case LANDLOCK_ABI_LAST:
266903cfe8aSMickaël Salaün break;
267903cfe8aSMickaël Salaün default:
268903cfe8aSMickaël Salaün fprintf(stderr,
269903cfe8aSMickaël Salaün "Hint: You should update this sandboxer "
270903cfe8aSMickaël Salaün "to leverage Landlock features "
271903cfe8aSMickaël Salaün "provided by ABI version %d (instead of %d).\n",
272903cfe8aSMickaël Salaün abi, LANDLOCK_ABI_LAST);
27376b902f8SMickaël Salaün }
274903cfe8aSMickaël Salaün access_fs_ro &= ruleset_attr.handled_access_fs;
275903cfe8aSMickaël Salaün access_fs_rw &= ruleset_attr.handled_access_fs;
27676b902f8SMickaël Salaün
27776b902f8SMickaël Salaün ruleset_fd =
27876b902f8SMickaël Salaün landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
27976b902f8SMickaël Salaün if (ruleset_fd < 0) {
28076b902f8SMickaël Salaün perror("Failed to create a ruleset");
28176b902f8SMickaël Salaün return 1;
28276b902f8SMickaël Salaün }
28376b902f8SMickaël Salaün if (populate_ruleset(ENV_FS_RO_NAME, ruleset_fd, access_fs_ro)) {
284ba84b0bfSMickaël Salaün goto err_close_ruleset;
285ba84b0bfSMickaël Salaün }
28676b902f8SMickaël Salaün if (populate_ruleset(ENV_FS_RW_NAME, ruleset_fd, access_fs_rw)) {
287ba84b0bfSMickaël Salaün goto err_close_ruleset;
288ba84b0bfSMickaël Salaün }
289ba84b0bfSMickaël Salaün if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
290ba84b0bfSMickaël Salaün perror("Failed to restrict privileges");
291ba84b0bfSMickaël Salaün goto err_close_ruleset;
292ba84b0bfSMickaël Salaün }
293ba84b0bfSMickaël Salaün if (landlock_restrict_self(ruleset_fd, 0)) {
294ba84b0bfSMickaël Salaün perror("Failed to enforce ruleset");
295ba84b0bfSMickaël Salaün goto err_close_ruleset;
296ba84b0bfSMickaël Salaün }
297ba84b0bfSMickaël Salaün close(ruleset_fd);
298ba84b0bfSMickaël Salaün
299ba84b0bfSMickaël Salaün cmd_path = argv[1];
300ba84b0bfSMickaël Salaün cmd_argv = argv + 1;
301ba84b0bfSMickaël Salaün execvpe(cmd_path, cmd_argv, envp);
302ba84b0bfSMickaël Salaün fprintf(stderr, "Failed to execute \"%s\": %s\n", cmd_path,
303ba84b0bfSMickaël Salaün strerror(errno));
304ba84b0bfSMickaël Salaün fprintf(stderr, "Hint: access to the binary, the interpreter or "
305ba84b0bfSMickaël Salaün "shared libraries may be denied.\n");
306ba84b0bfSMickaël Salaün return 1;
307ba84b0bfSMickaël Salaün
308ba84b0bfSMickaël Salaün err_close_ruleset:
309ba84b0bfSMickaël Salaün close(ruleset_fd);
310ba84b0bfSMickaël Salaün return 1;
311ba84b0bfSMickaël Salaün }
312