Support of socket_bind() and socket_connect() hooks. Its possible to restrict binding and connecting of TCP types of sockets to particular ports. Its just basic idea how Landlock could support network confinement. Signed-off-by: Konstantin Meskhidze <konstantin.meskhidze@xxxxxxxxxx> --- Changes since v3: * Split commit. * Add SECURITY_NETWORK in config. * Add IS_ENABLED(CONFIG_INET) if a kernel has no INET configuration. * Add hook_socket_bind and hook_socket_connect hooks. --- security/landlock/Kconfig | 1 + security/landlock/Makefile | 2 +- security/landlock/net.c | 180 +++++++++++++++++++++++++++++++++++ security/landlock/net.h | 22 +++++ security/landlock/ruleset.h | 6 ++ security/landlock/setup.c | 2 + security/landlock/syscalls.c | 61 +++++++++++- 7 files changed, 271 insertions(+), 3 deletions(-) create mode 100644 security/landlock/net.c create mode 100644 security/landlock/net.h diff --git a/security/landlock/Kconfig b/security/landlock/Kconfig index 8e33c4e8ffb8..2741f97169a7 100644 --- a/security/landlock/Kconfig +++ b/security/landlock/Kconfig @@ -4,6 +4,7 @@ config SECURITY_LANDLOCK bool "Landlock support" depends on SECURITY && !ARCH_EPHEMERAL_INODES select SECURITY_PATH + select SECURITY_NETWORK help Landlock is a sandboxing mechanism that enables processes to restrict themselves (and their future children) by gradually enforcing diff --git a/security/landlock/Makefile b/security/landlock/Makefile index 7bbd2f413b3e..afa44baaa83a 100644 --- a/security/landlock/Makefile +++ b/security/landlock/Makefile @@ -1,4 +1,4 @@ obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o landlock-y := setup.o syscalls.o object.o ruleset.o \ - cred.o ptrace.o fs.o + cred.o ptrace.o fs.o net.o diff --git a/security/landlock/net.c b/security/landlock/net.c new file mode 100644 index 000000000000..7fbb857c39e2 --- /dev/null +++ b/security/landlock/net.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Landlock LSM - Network management and hooks + * + * Copyright (C) 2022 Huawei Tech. Co., Ltd. + * Author: Konstantin Meskhidze <konstantin.meskhidze@xxxxxxxxxx> + * + */ + +#include <linux/in.h> +#include <linux/net.h> +#include <linux/socket.h> +#include <net/ipv6.h> + +#include "cred.h" +#include "limits.h" +#include "net.h" + +int landlock_append_net_rule(struct landlock_ruleset *const ruleset, + u16 port, u32 access_rights) +{ + int err; + + /* Transforms relative access rights to absolute ones. */ + access_rights |= LANDLOCK_MASK_ACCESS_NET & + ~landlock_get_net_access_mask(ruleset, 0); + + mutex_lock(&ruleset->lock); + err = landlock_insert_rule(ruleset, NULL, (uintptr_t)port, access_rights, + LANDLOCK_RULE_NET_SERVICE); + mutex_unlock(&ruleset->lock); + + return err; +} + +static int check_socket_access(const struct landlock_ruleset *const domain, + u16 port, u32 access_request) +{ + bool allowed = false; + u64 layer_mask; + size_t i; + + /* Make sure all layers can be checked. */ + BUILD_BUG_ON(BITS_PER_TYPE(layer_mask) < LANDLOCK_MAX_NUM_LAYERS); + + if (WARN_ON_ONCE(!domain)) + return 0; + if (WARN_ON_ONCE(domain->num_layers < 1)) + return -EACCES; + + /* + * Saves all layers handling a subset of requested + * socket access rules. + */ + layer_mask = 0; + for (i = 0; i < domain->num_layers; i++) { + if (landlock_get_net_access_mask(domain, i) & access_request) + layer_mask |= BIT_ULL(i); + } + /* An access request not handled by the domain is allowed. */ + if (layer_mask == 0) + return 0; + + /* + * We need to walk through all the hierarchy to not miss any relevant + * restriction. + */ + layer_mask = landlock_unmask_layers(domain, NULL, port, + access_request, layer_mask, + LANDLOCK_RULE_NET_SERVICE); + if (layer_mask == 0) + allowed = true; + + return allowed ? 0 : -EACCES; +} + +static int hook_socket_bind(struct socket *sock, struct sockaddr *address, int addrlen) +{ +#if IS_ENABLED(CONFIG_INET) + short socket_type; + struct sockaddr_in *sockaddr; + struct sockaddr_in6 *sockaddr_ip6; + u16 port; + const struct landlock_ruleset *const dom = landlock_get_current_domain(); + + if (!dom) + return 0; + + /* Check if the hook is AF_INET* socket's action */ + if ((address->sa_family != AF_INET) && (address->sa_family != AF_INET6)) + return 0; + + socket_type = sock->type; + /* Check if it's a TCP socket */ + if (socket_type != SOCK_STREAM) + return 0; + + /* Get port value in host byte order */ + switch (address->sa_family) { + case AF_INET: + sockaddr = (struct sockaddr_in *)address; + port = ntohs(sockaddr->sin_port); + break; + case AF_INET6: + sockaddr_ip6 = (struct sockaddr_in6 *)address; + port = ntohs(sockaddr_ip6->sin6_port); + break; + } + + return check_socket_access(dom, port, LANDLOCK_ACCESS_NET_BIND_TCP); +#else + return 0; +#endif +} + +static int hook_socket_connect(struct socket *sock, struct sockaddr *address, int addrlen) +{ +#if IS_ENABLED(CONFIG_INET) + short socket_type; + struct sockaddr_in *sockaddr; + struct sockaddr_in6 *sockaddr_ip6; + u16 port; + const struct landlock_ruleset *const dom = landlock_get_current_domain(); + + if (!dom) + return 0; + + /* Check if the hook is AF_INET* socket's action */ + if ((address->sa_family != AF_INET) && (address->sa_family != AF_INET6)) { + /* Check if the socket_connect() hook has AF_UNSPEC flag*/ + if (address->sa_family == AF_UNSPEC) { + u16 i; + /* + * If just in a layer a mask supports connect access, + * the socket_connect() hook with AF_UNSPEC family flag + * must be banned. This prevents from disconnecting already + * connected sockets. + */ + for (i = 0; i < dom->num_layers; i++) { + if (landlock_get_net_access_mask(dom, i) & + LANDLOCK_ACCESS_NET_CONNECT_TCP) + return -EACCES; + } + } + return 0; + } + + socket_type = sock->type; + /* Check if it's a TCP socket */ + if (socket_type != SOCK_STREAM) + return 0; + + /* Get port value in host byte order */ + switch (address->sa_family) { + case AF_INET: + sockaddr = (struct sockaddr_in *)address; + port = ntohs(sockaddr->sin_port); + break; + case AF_INET6: + sockaddr_ip6 = (struct sockaddr_in6 *)address; + port = ntohs(sockaddr_ip6->sin6_port); + break; + } + + return check_socket_access(dom, port, LANDLOCK_ACCESS_NET_CONNECT_TCP); +#else + return 0; +#endif +} + +static struct security_hook_list landlock_hooks[] __lsm_ro_after_init = { + LSM_HOOK_INIT(socket_bind, hook_socket_bind), + LSM_HOOK_INIT(socket_connect, hook_socket_connect), +}; + +__init void landlock_add_net_hooks(void) +{ + security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks), + LANDLOCK_NAME); +} diff --git a/security/landlock/net.h b/security/landlock/net.h new file mode 100644 index 000000000000..345bdc1dc84f --- /dev/null +++ b/security/landlock/net.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Landlock LSM - Network management and hooks + * + * Copyright (C) 2022 Huawei Tech. Co., Ltd. + * Author: Konstantin Meskhidze <konstantin.meskhidze@xxxxxxxxxx> + * + */ + +#ifndef _SECURITY_LANDLOCK_NET_H +#define _SECURITY_LANDLOCK_NET_H + +#include "common.h" +#include "ruleset.h" +#include "setup.h" + +__init void landlock_add_net_hooks(void); + +int landlock_append_net_rule(struct landlock_ruleset *const ruleset, + u16 port, u32 access_hierarchy); + +#endif /* _SECURITY_LANDLOCK_NET_H */ diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index abf3e09a65cd..74e9d3d26bd6 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -193,6 +193,12 @@ void landlock_set_fs_access_mask(struct landlock_ruleset *ruleset, u32 landlock_get_fs_access_mask(const struct landlock_ruleset *ruleset, u16 mask_level); +void landlock_set_net_access_mask(struct landlock_ruleset *ruleset, + const struct landlock_access_mask *access_mask_set, + u16 mask_level); + +u32 landlock_get_net_access_mask(const struct landlock_ruleset *ruleset, u16 mask_level); + u64 landlock_unmask_layers(const struct landlock_ruleset *const domain, const struct landlock_object *object_ptr, const u16 port, const u32 access_request, diff --git a/security/landlock/setup.c b/security/landlock/setup.c index f8e8e980454c..8059dc0b47d3 100644 --- a/security/landlock/setup.c +++ b/security/landlock/setup.c @@ -14,6 +14,7 @@ #include "fs.h" #include "ptrace.h" #include "setup.h" +#include "net.h" bool landlock_initialized __lsm_ro_after_init = false; @@ -28,6 +29,7 @@ static int __init landlock_init(void) landlock_add_cred_hooks(); landlock_add_ptrace_hooks(); landlock_add_fs_hooks(); + landlock_add_net_hooks(); landlock_initialized = true; pr_info("Up and running.\n"); return 0; diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index b91455a19356..2d45ea94e6d2 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -29,6 +29,7 @@ #include "cred.h" #include "fs.h" #include "limits.h" +#include "net.h" #include "ruleset.h" #include "setup.h" @@ -73,7 +74,8 @@ static void build_check_abi(void) { struct landlock_ruleset_attr ruleset_attr; struct landlock_path_beneath_attr path_beneath_attr; - size_t ruleset_size, path_beneath_size; + struct landlock_net_service_attr net_service_attr; + size_t ruleset_size, path_beneath_size, net_service_size; /* * For each user space ABI structures, first checks that there is no @@ -89,6 +91,11 @@ static void build_check_abi(void) path_beneath_size += sizeof(path_beneath_attr.parent_fd); BUILD_BUG_ON(sizeof(path_beneath_attr) != path_beneath_size); BUILD_BUG_ON(sizeof(path_beneath_attr) != 12); + + net_service_size = sizeof(net_service_attr.allowed_access); + net_service_size += sizeof(net_service_attr.port); + BUILD_BUG_ON(sizeof(net_service_attr) != net_service_size); + BUILD_BUG_ON(sizeof(net_service_attr) != 10); } /* Ruleset handling */ @@ -311,7 +318,6 @@ static int add_rule_path_beneath(const int ruleset_fd, const void *const rule_at * Checks that allowed_access matches the @ruleset constraints * (ruleset->access_masks[0] is automatically upgraded to 64-bits). */ - if ((path_beneath_attr.allowed_access | landlock_get_fs_access_mask(ruleset, 0)) != landlock_get_fs_access_mask(ruleset, 0)) { err = -EINVAL; @@ -333,6 +339,50 @@ static int add_rule_path_beneath(const int ruleset_fd, const void *const rule_at return err; } +static int add_rule_net_service(const int ruleset_fd, const void *const rule_attr) +{ + struct landlock_net_service_attr net_service_attr; + struct landlock_ruleset *ruleset; + int res, err; + + /* Copies raw user space buffer, only one type for now. */ + res = copy_from_user(&net_service_attr, rule_attr, + sizeof(net_service_attr)); + if (res) + return -EFAULT; + + /* Gets and checks the ruleset. */ + ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE); + if (IS_ERR(ruleset)) + return PTR_ERR(ruleset); + + /* + * Informs about useless rule: empty allowed_access (i.e. deny rules) + * are ignored by network actions + */ + if (!net_service_attr.allowed_access) { + err = -ENOMSG; + goto out_put_ruleset; + } + /* + * Checks that allowed_access matches the @ruleset constraints + * (ruleset->access_masks[0] is automatically upgraded to 64-bits). + */ + if ((net_service_attr.allowed_access | landlock_get_net_access_mask(ruleset, 0)) != + landlock_get_net_access_mask(ruleset, 0)) { + err = -EINVAL; + goto out_put_ruleset; + } + + /* Imports the new rule. */ + err = landlock_append_net_rule(ruleset, net_service_attr.port, + net_service_attr.allowed_access); + +out_put_ruleset: + landlock_put_ruleset(ruleset); + return err; +} + /** * sys_landlock_add_rule - Add a new rule to a ruleset * @@ -379,6 +429,13 @@ SYSCALL_DEFINE4(landlock_add_rule, case LANDLOCK_RULE_PATH_BENEATH: err = add_rule_path_beneath(ruleset_fd, rule_attr); break; + case LANDLOCK_RULE_NET_SERVICE: +#if IS_ENABLED(CONFIG_INET) + err = add_rule_net_service(ruleset_fd, rule_attr); +#else + err = -EOPNOTSUPP; +#endif + break; default: err = -EINVAL; } -- 2.25.1