diff options
Diffstat (limited to 'samples/landlock/sandboxer.c')
-rw-r--r-- | samples/landlock/sandboxer.c | 205 |
1 files changed, 157 insertions, 48 deletions
diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c index 08596c0ef070..07fab2ef534e 100644 --- a/samples/landlock/sandboxer.c +++ b/samples/landlock/sandboxer.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BSD-3-Clause /* - * Simple Landlock sandbox manager able to launch a process restricted by a - * user-defined filesystem access control policy. + * Simple Landlock sandbox manager able to execute a process restricted by + * user-defined file system and network access control policies. * * Copyright © 2017-2020 Mickaël Salaün <mic@digikod.net> * Copyright © 2020 ANSSI @@ -14,6 +14,7 @@ #include <fcntl.h> #include <linux/landlock.h> #include <linux/prctl.h> +#include <linux/socket.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> @@ -22,6 +23,7 @@ #include <sys/stat.h> #include <sys/syscall.h> #include <unistd.h> +#include <stdbool.h> #ifndef landlock_create_ruleset static inline int @@ -55,8 +57,28 @@ static inline int landlock_restrict_self(const int ruleset_fd, #define ENV_FS_RW_NAME "LL_FS_RW" #define ENV_TCP_BIND_NAME "LL_TCP_BIND" #define ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT" +#define ENV_SCOPED_NAME "LL_SCOPED" #define ENV_DELIMITER ":" +static int str2num(const char *numstr, __u64 *num_dst) +{ + char *endptr = NULL; + int err = 0; + __u64 num; + + errno = 0; + num = strtoull(numstr, &endptr, 10); + if (errno != 0) + err = errno; + /* Was the string empty, or not entirely parsed successfully? */ + else if ((*numstr == '\0') || (*endptr != '\0')) + err = EINVAL; + else + *num_dst = num; + + return err; +} + static int parse_path(char *env_path, const char ***const path_list) { int i, num_paths = 0; @@ -69,6 +91,9 @@ static int parse_path(char *env_path, const char ***const path_list) } } *path_list = malloc(num_paths * sizeof(**path_list)); + if (!*path_list) + return -1; + for (i = 0; i < num_paths; i++) (*path_list)[i] = strsep(&env_path, ENV_DELIMITER); @@ -81,7 +106,8 @@ static int parse_path(char *env_path, const char ***const path_list) LANDLOCK_ACCESS_FS_EXECUTE | \ LANDLOCK_ACCESS_FS_WRITE_FILE | \ LANDLOCK_ACCESS_FS_READ_FILE | \ - LANDLOCK_ACCESS_FS_TRUNCATE) + LANDLOCK_ACCESS_FS_TRUNCATE | \ + LANDLOCK_ACCESS_FS_IOCTL_DEV) /* clang-format on */ @@ -104,6 +130,10 @@ static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd, env_path_name = strdup(env_path_name); unsetenv(env_var); num_paths = parse_path(env_path_name, &path_list); + if (num_paths < 0) { + fprintf(stderr, "Failed to allocate memory\n"); + goto out_free_name; + } if (num_paths == 1 && path_list[0][0] == '\0') { /* * Allows to not use all possible restrictions (e.g. use @@ -120,9 +150,11 @@ static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd, if (path_beneath.parent_fd < 0) { fprintf(stderr, "Failed to open \"%s\": %s\n", path_list[i], strerror(errno)); - goto out_free_name; + continue; } if (fstat(path_beneath.parent_fd, &statbuf)) { + fprintf(stderr, "Failed to stat \"%s\": %s\n", + path_list[i], strerror(errno)); close(path_beneath.parent_fd); goto out_free_name; } @@ -151,10 +183,9 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd, const __u64 allowed_access) { int ret = 1; - char *env_port_name, *strport; + char *env_port_name, *env_port_name_next, *strport; struct landlock_net_port_attr net_port = { .allowed_access = allowed_access, - .port = 0, }; env_port_name = getenv(env_var); @@ -163,8 +194,19 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd, env_port_name = strdup(env_port_name); unsetenv(env_var); - while ((strport = strsep(&env_port_name, ENV_DELIMITER))) { - net_port.port = atoi(strport); + env_port_name_next = env_port_name; + while ((strport = strsep(&env_port_name_next, ENV_DELIMITER))) { + __u64 port; + + if (strcmp(strport, "") == 0) + continue; + + if (str2num(strport, &port)) { + fprintf(stderr, "Failed to parse port at \"%s\"\n", + strport); + goto out_free_name; + } + net_port.port = port; if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT, &net_port, 0)) { fprintf(stderr, @@ -180,6 +222,55 @@ out_free_name: return ret; } +/* Returns true on error, false otherwise. */ +static bool check_ruleset_scope(const char *const env_var, + struct landlock_ruleset_attr *ruleset_attr) +{ + char *env_type_scope, *env_type_scope_next, *ipc_scoping_name; + bool error = false; + bool abstract_scoping = false; + bool signal_scoping = false; + + /* Scoping is not supported by Landlock ABI */ + if (!(ruleset_attr->scoped & + (LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | LANDLOCK_SCOPE_SIGNAL))) + goto out_unset; + + env_type_scope = getenv(env_var); + /* Scoping is not supported by the user */ + if (!env_type_scope || strcmp("", env_type_scope) == 0) + goto out_unset; + + env_type_scope = strdup(env_type_scope); + env_type_scope_next = env_type_scope; + while ((ipc_scoping_name = + strsep(&env_type_scope_next, ENV_DELIMITER))) { + if (strcmp("a", ipc_scoping_name) == 0 && !abstract_scoping) { + abstract_scoping = true; + } else if (strcmp("s", ipc_scoping_name) == 0 && + !signal_scoping) { + signal_scoping = true; + } else { + fprintf(stderr, "Unknown or duplicate scope \"%s\"\n", + ipc_scoping_name); + error = true; + goto out_free_name; + } + } + +out_free_name: + free(env_type_scope); + +out_unset: + if (!abstract_scoping) + ruleset_attr->scoped &= ~LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET; + if (!signal_scoping) + ruleset_attr->scoped &= ~LANDLOCK_SCOPE_SIGNAL; + + unsetenv(env_var); + return error; +} + /* clang-format off */ #define ACCESS_FS_ROUGHLY_READ ( \ @@ -199,11 +290,50 @@ out_free_name: LANDLOCK_ACCESS_FS_MAKE_BLOCK | \ LANDLOCK_ACCESS_FS_MAKE_SYM | \ LANDLOCK_ACCESS_FS_REFER | \ - LANDLOCK_ACCESS_FS_TRUNCATE) + LANDLOCK_ACCESS_FS_TRUNCATE | \ + LANDLOCK_ACCESS_FS_IOCTL_DEV) /* clang-format on */ -#define LANDLOCK_ABI_LAST 4 +#define LANDLOCK_ABI_LAST 6 + +#define XSTR(s) #s +#define STR(s) XSTR(s) + +/* clang-format off */ + +static const char help[] = + "usage: " ENV_FS_RO_NAME "=\"...\" " ENV_FS_RW_NAME "=\"...\" " + "[other environment variables] %1$s <cmd> [args]...\n" + "\n" + "Execute the given command in a restricted environment.\n" + "Multi-valued settings (lists of ports, paths, scopes) are colon-delimited.\n" + "\n" + "Mandatory settings:\n" + "* " ENV_FS_RO_NAME ": paths allowed to be used in a read-only way\n" + "* " ENV_FS_RW_NAME ": paths allowed to be used in a read-write way\n" + "\n" + "Optional settings (when not set, their associated access check " + "is always allowed, which is different from an empty string which " + "means an empty list):\n" + "* " ENV_TCP_BIND_NAME ": ports allowed to bind (server)\n" + "* " ENV_TCP_CONNECT_NAME ": ports allowed to connect (client)\n" + "* " ENV_SCOPED_NAME ": actions denied on the outside of the landlock domain\n" + " - \"a\" to restrict opening abstract unix sockets\n" + " - \"s\" to restrict sending signals\n" + "\n" + "Example:\n" + ENV_FS_RO_NAME "=\"${PATH}:/lib:/usr:/proc:/etc:/dev/urandom\" " + ENV_FS_RW_NAME "=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" " + ENV_TCP_BIND_NAME "=\"9418\" " + ENV_TCP_CONNECT_NAME "=\"80:443\" " + ENV_SCOPED_NAME "=\"a:s\" " + "%1$s bash -i\n" + "\n" + "This sandboxer can use Landlock features up to ABI version " + STR(LANDLOCK_ABI_LAST) ".\n"; + +/* clang-format on */ int main(const int argc, char *const argv[], char *const *const envp) { @@ -218,47 +348,12 @@ int main(const int argc, char *const argv[], char *const *const envp) .handled_access_fs = access_fs_rw, .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | LANDLOCK_ACCESS_NET_CONNECT_TCP, + .scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | + LANDLOCK_SCOPE_SIGNAL, }; if (argc < 2) { - fprintf(stderr, - "usage: %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\"%s " - "<cmd> [args]...\n\n", - ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME, - ENV_TCP_CONNECT_NAME, argv[0]); - fprintf(stderr, - "Launch a command in a restricted environment.\n\n"); - fprintf(stderr, - "Environment variables containing paths and ports " - "each separated by a colon:\n"); - fprintf(stderr, - "* %s: list of paths allowed to be used in a read-only way.\n", - ENV_FS_RO_NAME); - fprintf(stderr, - "* %s: list of paths allowed to be used in a read-write way.\n\n", - ENV_FS_RW_NAME); - fprintf(stderr, - "Environment variables containing ports are optional " - "and could be skipped.\n"); - fprintf(stderr, - "* %s: list of ports allowed to bind (server).\n", - ENV_TCP_BIND_NAME); - fprintf(stderr, - "* %s: list of ports allowed to connect (client).\n", - ENV_TCP_CONNECT_NAME); - fprintf(stderr, - "\nexample:\n" - "%s=\"/bin:/lib:/usr:/proc:/etc:/dev/urandom\" " - "%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" " - "%s=\"9418\" " - "%s=\"80:443\" " - "%s bash -i\n\n", - ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME, - ENV_TCP_CONNECT_NAME, argv[0]); - fprintf(stderr, - "This sandboxer can use Landlock features " - "up to ABI version %d.\n", - LANDLOCK_ABI_LAST); + fprintf(stderr, help, argv[0]); return 1; } @@ -317,6 +412,16 @@ int main(const int argc, char *const argv[], char *const *const envp) ruleset_attr.handled_access_net &= ~(LANDLOCK_ACCESS_NET_BIND_TCP | LANDLOCK_ACCESS_NET_CONNECT_TCP); + __attribute__((fallthrough)); + case 4: + /* Removes LANDLOCK_ACCESS_FS_IOCTL_DEV for ABI < 5 */ + ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_IOCTL_DEV; + + __attribute__((fallthrough)); + case 5: + /* Removes LANDLOCK_SCOPE_* for ABI < 6 */ + ruleset_attr.scoped &= ~(LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | + LANDLOCK_SCOPE_SIGNAL); fprintf(stderr, "Hint: You should update the running kernel " "to leverage Landlock features " @@ -348,6 +453,9 @@ int main(const int argc, char *const argv[], char *const *const envp) ~LANDLOCK_ACCESS_NET_CONNECT_TCP; } + if (check_ruleset_scope(ENV_SCOPED_NAME, &ruleset_attr)) + return 1; + ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); if (ruleset_fd < 0) { @@ -383,6 +491,7 @@ int main(const int argc, char *const argv[], char *const *const envp) cmd_path = argv[1]; cmd_argv = argv + 1; + fprintf(stderr, "Executing the sandboxed command...\n"); execvpe(cmd_path, cmd_argv, envp); fprintf(stderr, "Failed to execute \"%s\": %s\n", cmd_path, strerror(errno)); |