[RFC PATCH v4 09/15] landlock: TCP network hooks implementation
Konstantin Meskhidze
konstantin.meskhidze at huawei.com
Wed Mar 9 13:44:53 UTC 2022
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 at huawei.com>
---
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 at huawei.com>
+ *
+ */
+
+#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 at huawei.com>
+ *
+ */
+
+#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
More information about the Linux-security-module-archive
mailing list