Add new socket-related rule type, presented via landlock_socket_attr
struct. Add all neccessary entities for socket ruleset support.
Add flag LANDLOCK_ACCESS_SOCKET_CREATE that will provide the
ability to control socket creation.
Change landlock_key.data type from uinptr_t to u64. Socket rule has to
contain 32-bit socket family and type values, so landlock_key can be
represented as 64-bit number the first 32 bits of which correspond to
the socket family and last - to the type.
Change ABI version to 5.
Signed-off-by: Ivanov Mikhail <ivanov.mikhail1@xxxxxxxxxxxxxxxxxxx>
Reviewed-by: Konstantin Meskhidze <konstantin.meskhidze@xxxxxxxxxx>
---
include/uapi/linux/landlock.h | 49 +++++++++++++++++
security/landlock/Makefile | 2 +-
security/landlock/limits.h | 5 ++
security/landlock/net.c | 2 +-
security/landlock/ruleset.c | 35 +++++++++++--
security/landlock/ruleset.h | 44 ++++++++++++++--
security/landlock/socket.c | 43 +++++++++++++++
security/landlock/socket.h | 17 ++++++
security/landlock/syscalls.c | 55 ++++++++++++++++++--
tools/testing/selftests/landlock/base_test.c | 2 +-
10 files changed, 241 insertions(+), 13 deletions(-)
create mode 100644 security/landlock/socket.c
create mode 100644 security/landlock/socket.h
diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index 25c8d7677..8551ade38 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -37,6 +37,13 @@ struct landlock_ruleset_attr {
* rule explicitly allow them.
*/
__u64 handled_access_net;
+
+ /**
+ * @handled_access_net: Bitmask of actions (cf. `Socket flags`_)
+ * that is handled by this ruleset and should then be forbidden if no
+ * rule explicitly allow them.
+ */
+ __u64 handled_access_socket;
};
/*
@@ -65,6 +72,11 @@ enum landlock_rule_type {
* landlock_net_port_attr .
*/
LANDLOCK_RULE_NET_PORT,
+ /**
+ * @LANDLOCK_RULE_SOCKET: Type of a &struct
+ * landlock_socket_attr .
+ */
+ LANDLOCK_RULE_SOCKET,
};
/**
@@ -115,6 +127,27 @@ struct landlock_net_port_attr {
__u64 port;
};
+/**
+ * struct landlock_socket_attr - Socket definition
+ *
+ * Argument of sys_landlock_add_rule().
+ */
+struct landlock_socket_attr {
+ /**
+ * @allowed_access: Bitmask of allowed access for a socket
+ * (cf. `Socket flags`_).
+ */
+ __u64 allowed_access;
+ /**
+ * @domain: Protocol family used for communication (see socket(2)).
+ */
+ int domain;
+ /**
+ * @type: Socket type (see socket(2)).
+ */
+ int type;
+};
+
/**
* DOC: fs_access
*
@@ -244,4 +277,20 @@ struct landlock_net_port_attr {
#define LANDLOCK_ACCESS_NET_BIND_TCP (1ULL << 0)
#define LANDLOCK_ACCESS_NET_CONNECT_TCP (1ULL << 1)
/* clang-format on */
+
+/**
+ * DOC: socket_acess
+ *
+ * Socket flags
+ * ~~~~~~~~~~~~~~~~
+ *
+ * These flags enable to restrict a sandboxed process to a set of
+ * socket-related actions for specific protocols. This is supported
+ * since the Landlock ABI version 5.
+ *
+ * - %LANDLOCK_ACCESS_SOCKET_CREATE: Create a socket
+ */
+/* clang-format off */
+#define LANDLOCK_ACCESS_SOCKET_CREATE (1ULL << 0)
+/* clang-format on */
#endif /* _UAPI_LINUX_LANDLOCK_H */
diff --git a/security/landlock/Makefile b/security/landlock/Makefile
index b4538b7cf..ff1dd98f6 100644
--- a/security/landlock/Makefile
+++ b/security/landlock/Makefile
@@ -1,6 +1,6 @@
obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o
landlock-y := setup.o syscalls.o object.o ruleset.o \
- cred.o task.o fs.o
+ cred.o task.o fs.o socket.o
landlock-$(CONFIG_INET) += net.o
diff --git a/security/landlock/limits.h b/security/landlock/limits.h
index 93c9c6f91..ebdab587c 100644
--- a/security/landlock/limits.h
+++ b/security/landlock/limits.h
@@ -28,6 +28,11 @@
#define LANDLOCK_NUM_ACCESS_NET __const_hweight64(LANDLOCK_MASK_ACCESS_NET)
#define LANDLOCK_SHIFT_ACCESS_NET LANDLOCK_NUM_ACCESS_FS
+#define LANDLOCK_LAST_ACCESS_SOCKET LANDLOCK_ACCESS_SOCKET_CREATE
+#define LANDLOCK_MASK_ACCESS_SOCKET ((LANDLOCK_LAST_ACCESS_SOCKET << 1) - 1)
+#define LANDLOCK_NUM_ACCESS_SOCKET __const_hweight64(LANDLOCK_MASK_ACCESS_SOCKET)
+#define LANDLOCK_SHIFT_ACCESS_SOCKET LANDLOCK_NUM_ACCESS_SOCKET
+
/* clang-format on */
#endif /* _SECURITY_LANDLOCK_LIMITS_H */
diff --git a/security/landlock/net.c b/security/landlock/net.c
index c8bcd29bd..0e3770d14 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -159,7 +159,7 @@ static int current_check_access_socket(struct socket *const sock,
return -EINVAL;
}
- id.key.data = (__force uintptr_t)port;
+ id.key.data = (__force u64)port;
BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
rule = landlock_find_rule(dom, id);
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index e0a5fbf92..1f1ed8181 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -40,6 +40,7 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
#if IS_ENABLED(CONFIG_INET)
new_ruleset->root_net_port = RB_ROOT;
#endif /* IS_ENABLED(CONFIG_INET) */
+ new_ruleset->root_socket = RB_ROOT;
new_ruleset->num_layers = num_layers;
/*
@@ -52,12 +53,13 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
struct landlock_ruleset *
landlock_create_ruleset(const access_mask_t fs_access_mask,
- const access_mask_t net_access_mask)
+ const access_mask_t net_access_mask,
+ const access_mask_t socket_access_mask)
{
struct landlock_ruleset *new_ruleset;
/* Informs about useless ruleset. */
- if (!fs_access_mask && !net_access_mask)
+ if (!fs_access_mask && !net_access_mask && !socket_access_mask)
return ERR_PTR(-ENOMSG);
new_ruleset = create_ruleset(1);
if (IS_ERR(new_ruleset))
@@ -66,6 +68,8 @@ landlock_create_ruleset(const access_mask_t fs_access_mask,
landlock_add_fs_access_mask(new_ruleset, fs_access_mask, 0);
if (net_access_mask)
landlock_add_net_access_mask(new_ruleset, net_access_mask, 0);
+ if (socket_access_mask)
+ landlock_add_socket_access_mask(new_ruleset, socket_access_mask, 0);
return new_ruleset;
}
@@ -89,6 +93,9 @@ static bool is_object_pointer(const enum landlock_key_type key_type)
return false;
#endif /* IS_ENABLED(CONFIG_INET) */
+ case LANDLOCK_KEY_SOCKET:
+ return false;
+
default:
WARN_ON_ONCE(1);
return false;
@@ -146,6 +153,9 @@ static struct rb_root *get_root(struct landlock_ruleset *const ruleset,
return &ruleset->root_net_port;
#endif /* IS_ENABLED(CONFIG_INET) */
+ case LANDLOCK_KEY_SOCKET:
+ return &ruleset->root_socket;
+
default:
WARN_ON_ONCE(1);
return ERR_PTR(-EINVAL);
@@ -175,7 +185,8 @@ static void build_check_ruleset(void)
BUILD_BUG_ON(ruleset.num_layers < LANDLOCK_MAX_NUM_LAYERS);
BUILD_BUG_ON(access_masks <
((LANDLOCK_MASK_ACCESS_FS << LANDLOCK_SHIFT_ACCESS_FS) |
- (LANDLOCK_MASK_ACCESS_NET << LANDLOCK_SHIFT_ACCESS_NET)));
+ (LANDLOCK_MASK_ACCESS_NET << LANDLOCK_SHIFT_ACCESS_NET) |
+ (LANDLOCK_MASK_ACCESS_SOCKET << LANDLOCK_SHIFT_ACCESS_SOCKET)));
}
/**
@@ -399,6 +410,11 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
goto out_unlock;
#endif /* IS_ENABLED(CONFIG_INET) */
+ /* Merges the @src socket tree. */
+ err = merge_tree(dst, src, LANDLOCK_KEY_SOCKET);
+ if (err)
+ goto out_unlock;
+
out_unlock:
mutex_unlock(&src->lock);
mutex_unlock(&dst->lock);
@@ -462,6 +478,11 @@ static int inherit_ruleset(struct landlock_ruleset *const parent,
goto out_unlock;
#endif /* IS_ENABLED(CONFIG_INET) */
+ /* Copies the @parent socket tree. */
+ err = inherit_tree(parent, child, LANDLOCK_KEY_SOCKET);
+ if (err)
+ goto out_unlock;
+
if (WARN_ON_ONCE(child->num_layers <= parent->num_layers)) {
err = -EINVAL;
goto out_unlock;
@@ -498,6 +519,10 @@ static void free_ruleset(struct landlock_ruleset *const ruleset)
free_rule(freeme, LANDLOCK_KEY_NET_PORT);
#endif /* IS_ENABLED(CONFIG_INET) */
+ rbtree_postorder_for_each_entry_safe(freeme, next,
+ &ruleset->root_socket, node)
+ free_rule(freeme, LANDLOCK_KEY_SOCKET);
+
put_hierarchy(ruleset->hierarchy);
kfree(ruleset);
}
@@ -708,6 +733,10 @@ landlock_init_layer_masks(const struct landlock_ruleset *const domain,
break;
#endif /* IS_ENABLED(CONFIG_INET) */
+ case LANDLOCK_KEY_SOCKET:
+ get_access_mask = landlock_get_socket_access_mask;
+ num_access = LANDLOCK_NUM_ACCESS_SOCKET;
+ break;
default:
WARN_ON_ONCE(1);
return 0;
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index c7f152678..f4213db09 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -72,10 +72,10 @@ union landlock_key {
*/
struct landlock_object *object;
/**
- * @data: Raw data to identify an arbitrary 32-bit value
+ * @data: Raw data to identify an arbitrary 64-bit value
* (e.g. a TCP port).
*/
- uintptr_t data;
+ u64 data;
};
/**
@@ -92,6 +92,12 @@ enum landlock_key_type {
* node keys.
*/
LANDLOCK_KEY_NET_PORT,
+
+ /**
+ * @LANDLOCK_KEY_SOCKET: Type of &landlock_ruleset.root_socket's
+ * node keys.
+ */
+ LANDLOCK_KEY_SOCKET,
};
/**
@@ -177,6 +183,15 @@ struct landlock_ruleset {
struct rb_root root_net_port;
#endif /* IS_ENABLED(CONFIG_INET) */
+ /**
+ * @root_socket: Root of a red-black tree containing &struct
+ * landlock_rule nodes with socket type, described by (domain, type)
+ * pair (see socket(2)). Once a ruleset is tied to a
+ * process (i.e. as a domain), this tree is immutable until @usage
+ * reaches zero.
+ */
+ struct rb_root root_socket;
+
/**
* @hierarchy: Enables hierarchy identification even when a parent
* domain vanishes. This is needed for the ptrace protection.
@@ -233,7 +248,8 @@ struct landlock_ruleset {
struct landlock_ruleset *
landlock_create_ruleset(const access_mask_t access_mask_fs,
- const access_mask_t access_mask_net);
+ const access_mask_t access_mask_net,
+ const access_mask_t socket_access_mask);
void landlock_put_ruleset(struct landlock_ruleset *const ruleset);
void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset);
@@ -282,6 +298,19 @@ landlock_add_net_access_mask(struct landlock_ruleset *const ruleset,
(net_mask << LANDLOCK_SHIFT_ACCESS_NET);
}
+static inline void
+landlock_add_socket_access_mask(struct landlock_ruleset *const ruleset,
+ const access_mask_t socket_access_mask,
+ const u16 layer_level)
+{
+ access_mask_t socket_mask = socket_access_mask & LANDLOCK_MASK_ACCESS_SOCKET;
+
+ /* Should already be checked in sys_landlock_create_ruleset(). */
+ WARN_ON_ONCE(socket_access_mask != socket_mask);
+ ruleset->access_masks[layer_level] |=
+ (socket_mask << LANDLOCK_SHIFT_ACCESS_SOCKET);
+}
+
static inline access_mask_t
landlock_get_raw_fs_access_mask(const struct landlock_ruleset *const ruleset,
const u16 layer_level)
@@ -309,6 +338,15 @@ landlock_get_net_access_mask(const struct landlock_ruleset *const ruleset,
LANDLOCK_MASK_ACCESS_NET;
}
+static inline access_mask_t
+landlock_get_socket_access_mask(const struct landlock_ruleset *const ruleset,
+ const u16 layer_level)
+{
+ return (ruleset->access_masks[layer_level] >>
+ LANDLOCK_SHIFT_ACCESS_SOCKET) &
+ LANDLOCK_MASK_ACCESS_SOCKET;
+}
+
bool landlock_unmask_layers(const struct landlock_rule *const rule,
const access_mask_t access_request,
layer_mask_t (*const layer_masks)[],
diff --git a/security/landlock/socket.c b/security/landlock/socket.c
new file mode 100644
index 000000000..88b4ef3a1
--- /dev/null
+++ b/security/landlock/socket.c
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock LSM - Socket management and hooks
+ *
+ * Copyright © 2024 Huawei Tech. Co., Ltd.
+ */
+
+#include "limits.h"
+#include "ruleset.h"
+#include "socket.h"
+
+union socket_key {
+ struct {
+ int domain;
+ int type;
+ } __packed content;
+ u64 val;
+};
+
+int landlock_append_socket_rule(struct landlock_ruleset *const ruleset,
+ const int domain, const int type, access_mask_t access_rights)
+{
+ int err;
+ const union socket_key socket_key = {
+ .content.domain = domain,
+ .content.type = type
+ };