summaryrefslogtreecommitdiff
path: root/security
diff options
context:
space:
mode:
Diffstat (limited to 'security')
-rw-r--r--security/landlock/Kconfig1
-rw-r--r--security/landlock/Makefile2
-rw-r--r--security/landlock/fs.c232
-rw-r--r--security/landlock/limits.h6
-rw-r--r--security/landlock/net.c200
-rw-r--r--security/landlock/net.h33
-rw-r--r--security/landlock/ruleset.c405
-rw-r--r--security/landlock/ruleset.h185
-rw-r--r--security/landlock/setup.c2
-rw-r--r--security/landlock/syscalls.c158
10 files changed, 923 insertions, 301 deletions
diff --git a/security/landlock/Kconfig b/security/landlock/Kconfig
index c1e862a38410..c4bf0d5eff39 100644
--- a/security/landlock/Kconfig
+++ b/security/landlock/Kconfig
@@ -3,6 +3,7 @@
config SECURITY_LANDLOCK
bool "Landlock support"
depends on SECURITY
+ select SECURITY_NETWORK
select SECURITY_PATH
help
Landlock is a sandboxing mechanism that enables processes to restrict
diff --git a/security/landlock/Makefile b/security/landlock/Makefile
index 7bbd2f413b3e..c2e116f2a299 100644
--- a/security/landlock/Makefile
+++ b/security/landlock/Makefile
@@ -2,3 +2,5 @@ obj-$(CONFIG_SECURITY_LANDLOCK) := landlock.o
landlock-y := setup.o syscalls.o object.o ruleset.o \
cred.o ptrace.o fs.o
+
+landlock-$(CONFIG_INET) += net.o
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 1c0c198f6fdb..bc7c126deea2 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -151,16 +151,6 @@ retry:
/* clang-format on */
/*
- * All access rights that are denied by default whether they are handled or not
- * by a ruleset/layer. This must be ORed with all ruleset->fs_access_masks[]
- * entries when we need to get the absolute handled access masks.
- */
-/* clang-format off */
-#define ACCESS_INITIALLY_DENIED ( \
- LANDLOCK_ACCESS_FS_REFER)
-/* clang-format on */
-
-/*
* @path: Should have been checked by get_path_from_fd().
*/
int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
@@ -168,7 +158,9 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
access_mask_t access_rights)
{
int err;
- struct landlock_object *object;
+ struct landlock_id id = {
+ .type = LANDLOCK_KEY_INODE,
+ };
/* Files only get access rights that make sense. */
if (!d_is_dir(path->dentry) &&
@@ -178,20 +170,19 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
return -EINVAL;
/* Transforms relative access rights to absolute ones. */
- access_rights |=
- LANDLOCK_MASK_ACCESS_FS &
- ~(ruleset->fs_access_masks[0] | ACCESS_INITIALLY_DENIED);
- object = get_inode_object(d_backing_inode(path->dentry));
- if (IS_ERR(object))
- return PTR_ERR(object);
+ access_rights |= LANDLOCK_MASK_ACCESS_FS &
+ ~landlock_get_fs_access_mask(ruleset, 0);
+ id.key.object = get_inode_object(d_backing_inode(path->dentry));
+ if (IS_ERR(id.key.object))
+ return PTR_ERR(id.key.object);
mutex_lock(&ruleset->lock);
- err = landlock_insert_rule(ruleset, object, access_rights);
+ err = landlock_insert_rule(ruleset, id, access_rights);
mutex_unlock(&ruleset->lock);
/*
* No need to check for an error because landlock_insert_rule()
* increments the refcount for the new object if needed.
*/
- landlock_put_object(object);
+ landlock_put_object(id.key.object);
return err;
}
@@ -208,6 +199,9 @@ find_rule(const struct landlock_ruleset *const domain,
{
const struct landlock_rule *rule;
const struct inode *inode;
+ struct landlock_id id = {
+ .type = LANDLOCK_KEY_INODE,
+ };
/* Ignores nonexistent leafs. */
if (d_is_negative(dentry))
@@ -215,67 +209,13 @@ find_rule(const struct landlock_ruleset *const domain,
inode = d_backing_inode(dentry);
rcu_read_lock();
- rule = landlock_find_rule(
- domain, rcu_dereference(landlock_inode(inode)->object));
+ id.key.object = rcu_dereference(landlock_inode(inode)->object);
+ rule = landlock_find_rule(domain, id);
rcu_read_unlock();
return rule;
}
/*
- * @layer_masks is read and may be updated according to the access request and
- * the matching rule.
- *
- * Returns true if the request is allowed (i.e. relevant layer masks for the
- * request are empty).
- */
-static inline bool
-unmask_layers(const struct landlock_rule *const rule,
- const access_mask_t access_request,
- layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
-{
- size_t layer_level;
-
- if (!access_request || !layer_masks)
- return true;
- if (!rule)
- return false;
-
- /*
- * An access is granted if, for each policy layer, at least one rule
- * encountered on the pathwalk grants the requested access,
- * regardless of its position in the layer stack. We must then check
- * the remaining layers for each inode, from the first added layer to
- * the last one. When there is multiple requested accesses, for each
- * policy layer, the full set of requested accesses may not be granted
- * by only one rule, but by the union (binary OR) of multiple rules.
- * E.g. /a/b <execute> + /a <read> => /a/b <execute + read>
- */
- for (layer_level = 0; layer_level < rule->num_layers; layer_level++) {
- const struct landlock_layer *const layer =
- &rule->layers[layer_level];
- const layer_mask_t layer_bit = BIT_ULL(layer->level - 1);
- const unsigned long access_req = access_request;
- unsigned long access_bit;
- bool is_empty;
-
- /*
- * Records in @layer_masks which layer grants access to each
- * requested access.
- */
- is_empty = true;
- for_each_set_bit(access_bit, &access_req,
- ARRAY_SIZE(*layer_masks)) {
- if (layer->access & BIT_ULL(access_bit))
- (*layer_masks)[access_bit] &= ~layer_bit;
- is_empty = is_empty && !(*layer_masks)[access_bit];
- }
- if (is_empty)
- return true;
- }
- return false;
-}
-
-/*
* Allows access to pseudo filesystems that will never be mountable (e.g.
* sockfs, pipefs), but can still be reachable through
* /proc/<pid>/fd/<file-descriptor>
@@ -287,64 +227,35 @@ static inline bool is_nouser_or_private(const struct dentry *dentry)
unlikely(IS_PRIVATE(d_backing_inode(dentry))));
}
-static inline access_mask_t
-get_handled_accesses(const struct landlock_ruleset *const domain)
+static access_mask_t
+get_raw_handled_fs_accesses(const struct landlock_ruleset *const domain)
{
- access_mask_t access_dom = ACCESS_INITIALLY_DENIED;
+ access_mask_t access_dom = 0;
size_t layer_level;
for (layer_level = 0; layer_level < domain->num_layers; layer_level++)
- access_dom |= domain->fs_access_masks[layer_level];
- return access_dom & LANDLOCK_MASK_ACCESS_FS;
+ access_dom |=
+ landlock_get_raw_fs_access_mask(domain, layer_level);
+ return access_dom;
}
-/**
- * init_layer_masks - Initialize layer masks from an access request
- *
- * Populates @layer_masks such that for each access right in @access_request,
- * the bits for all the layers are set where this access right is handled.
- *
- * @domain: The domain that defines the current restrictions.
- * @access_request: The requested access rights to check.
- * @layer_masks: The layer masks to populate.
- *
- * Returns: An access mask where each access right bit is set which is handled
- * in any of the active layers in @domain.
- */
-static inline access_mask_t
-init_layer_masks(const struct landlock_ruleset *const domain,
- const access_mask_t access_request,
- layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
+static access_mask_t
+get_handled_fs_accesses(const struct landlock_ruleset *const domain)
{
- access_mask_t handled_accesses = 0;
- size_t layer_level;
+ /* Handles all initially denied by default access rights. */
+ return get_raw_handled_fs_accesses(domain) |
+ LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
+}
- memset(layer_masks, 0, sizeof(*layer_masks));
- /* An empty access request can happen because of O_WRONLY | O_RDWR. */
- if (!access_request)
- return 0;
+static const struct landlock_ruleset *get_current_fs_domain(void)
+{
+ const struct landlock_ruleset *const dom =
+ landlock_get_current_domain();
- /* Saves all handled accesses per layer. */
- for (layer_level = 0; layer_level < domain->num_layers; layer_level++) {
- const unsigned long access_req = access_request;
- unsigned long access_bit;
+ if (!dom || !get_raw_handled_fs_accesses(dom))
+ return NULL;
- for_each_set_bit(access_bit, &access_req,
- ARRAY_SIZE(*layer_masks)) {
- /*
- * Artificially handles all initially denied by default
- * access rights.
- */
- if (BIT_ULL(access_bit) &
- (domain->fs_access_masks[layer_level] |
- ACCESS_INITIALLY_DENIED)) {
- (*layer_masks)[access_bit] |=
- BIT_ULL(layer_level);
- handled_accesses |= BIT_ULL(access_bit);
- }
- }
- }
- return handled_accesses;
+ return dom;
}
/*
@@ -519,7 +430,7 @@ static bool is_access_to_paths_allowed(
* a superset of the meaningful requested accesses).
*/
access_masked_parent1 = access_masked_parent2 =
- get_handled_accesses(domain);
+ get_handled_fs_accesses(domain);
is_dom_check = true;
} else {
if (WARN_ON_ONCE(dentry_child1 || dentry_child2))
@@ -531,18 +442,22 @@ static bool is_access_to_paths_allowed(
}
if (unlikely(dentry_child1)) {
- unmask_layers(find_rule(domain, dentry_child1),
- init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
- &_layer_masks_child1),
- &_layer_masks_child1);
+ landlock_unmask_layers(
+ find_rule(domain, dentry_child1),
+ landlock_init_layer_masks(
+ domain, LANDLOCK_MASK_ACCESS_FS,
+ &_layer_masks_child1, LANDLOCK_KEY_INODE),
+ &_layer_masks_child1, ARRAY_SIZE(_layer_masks_child1));
layer_masks_child1 = &_layer_masks_child1;
child1_is_directory = d_is_dir(dentry_child1);
}
if (unlikely(dentry_child2)) {
- unmask_layers(find_rule(domain, dentry_child2),
- init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
- &_layer_masks_child2),
- &_layer_masks_child2);
+ landlock_unmask_layers(
+ find_rule(domain, dentry_child2),
+ landlock_init_layer_masks(
+ domain, LANDLOCK_MASK_ACCESS_FS,
+ &_layer_masks_child2, LANDLOCK_KEY_INODE),
+ &_layer_masks_child2, ARRAY_SIZE(_layer_masks_child2));
layer_masks_child2 = &_layer_masks_child2;
child2_is_directory = d_is_dir(dentry_child2);
}
@@ -594,15 +509,16 @@ static bool is_access_to_paths_allowed(
}
rule = find_rule(domain, walker_path.dentry);
- allowed_parent1 = unmask_layers(rule, access_masked_parent1,
- layer_masks_parent1);
- allowed_parent2 = unmask_layers(rule, access_masked_parent2,
- layer_masks_parent2);
+ allowed_parent1 = landlock_unmask_layers(
+ rule, access_masked_parent1, layer_masks_parent1,
+ ARRAY_SIZE(*layer_masks_parent1));
+ allowed_parent2 = landlock_unmask_layers(
+ rule, access_masked_parent2, layer_masks_parent2,
+ ARRAY_SIZE(*layer_masks_parent2));
/* Stops when a rule from each layer grants access. */
if (allowed_parent1 && allowed_parent2)
break;
-
jump_up:
if (walker_path.dentry == walker_path.mnt->mnt_root) {
if (follow_up(&walker_path)) {
@@ -641,7 +557,8 @@ static inline int check_access_path(const struct landlock_ruleset *const domain,
{
layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
- access_request = init_layer_masks(domain, access_request, &layer_masks);
+ access_request = landlock_init_layer_masks(
+ domain, access_request, &layer_masks, LANDLOCK_KEY_INODE);
if (is_access_to_paths_allowed(domain, path, access_request,
&layer_masks, NULL, 0, NULL, NULL))
return 0;
@@ -651,8 +568,7 @@ static inline int check_access_path(const struct landlock_ruleset *const domain,
static inline int current_check_access_path(const struct path *const path,
const access_mask_t access_request)
{
- const struct landlock_ruleset *const dom =
- landlock_get_current_domain();
+ const struct landlock_ruleset *const dom = get_current_fs_domain();
if (!dom)
return 0;
@@ -727,16 +643,18 @@ static bool collect_domain_accesses(
if (is_nouser_or_private(dir))
return true;
- access_dom = init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
- layer_masks_dom);
+ access_dom = landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
+ layer_masks_dom,
+ LANDLOCK_KEY_INODE);
dget(dir);
while (true) {
struct dentry *parent_dentry;
/* Gets all layers allowing all domain accesses. */
- if (unmask_layers(find_rule(domain, dir), access_dom,
- layer_masks_dom)) {
+ if (landlock_unmask_layers(find_rule(domain, dir), access_dom,
+ layer_masks_dom,
+ ARRAY_SIZE(*layer_masks_dom))) {
/*
* Stops when all handled accesses are allowed by at
* least one rule in each layer.
@@ -815,8 +733,7 @@ static int current_check_refer_path(struct dentry *const old_dentry,
struct dentry *const new_dentry,
const bool removable, const bool exchange)
{
- const struct landlock_ruleset *const dom =
- landlock_get_current_domain();
+ const struct landlock_ruleset *const dom = get_current_fs_domain();
bool allow_parent1, allow_parent2;
access_mask_t access_request_parent1, access_request_parent2;
struct path mnt_dir;
@@ -850,9 +767,9 @@ static int current_check_refer_path(struct dentry *const old_dentry,
* The LANDLOCK_ACCESS_FS_REFER access right is not required
* for same-directory referer (i.e. no reparenting).
*/
- access_request_parent1 = init_layer_masks(
+ access_request_parent1 = landlock_init_layer_masks(
dom, access_request_parent1 | access_request_parent2,
- &layer_masks_parent1);
+ &layer_masks_parent1, LANDLOCK_KEY_INODE);
if (is_access_to_paths_allowed(
dom, new_dir, access_request_parent1,
&layer_masks_parent1, NULL, 0, NULL, NULL))
@@ -1050,7 +967,7 @@ static int hook_sb_mount(const char *const dev_name,
const struct path *const path, const char *const type,
const unsigned long flags, void *const data)
{
- if (!landlock_get_current_domain())
+ if (!get_current_fs_domain())
return 0;
return -EPERM;
}
@@ -1058,7 +975,7 @@ static int hook_sb_mount(const char *const dev_name,
static int hook_move_mount(const struct path *const from_path,
const struct path *const to_path)
{
- if (!landlock_get_current_domain())
+ if (!get_current_fs_domain())
return 0;
return -EPERM;
}
@@ -1069,14 +986,14 @@ static int hook_move_mount(const struct path *const from_path,
*/
static int hook_sb_umount(struct vfsmount *const mnt, const int flags)
{
- if (!landlock_get_current_domain())
+ if (!get_current_fs_domain())
return 0;
return -EPERM;
}
static int hook_sb_remount(struct super_block *const sb, void *const mnt_opts)
{
- if (!landlock_get_current_domain())
+ if (!get_current_fs_domain())
return 0;
return -EPERM;
}
@@ -1092,7 +1009,7 @@ static int hook_sb_remount(struct super_block *const sb, void *const mnt_opts)
static int hook_sb_pivotroot(const struct path *const old_path,
const struct path *const new_path)
{
- if (!landlock_get_current_domain())
+ if (!get_current_fs_domain())
return 0;
return -EPERM;
}
@@ -1128,8 +1045,7 @@ static int hook_path_mknod(const struct path *const dir,
struct dentry *const dentry, const umode_t mode,
const unsigned int dev)
{
- const struct landlock_ruleset *const dom =
- landlock_get_current_domain();
+ const struct landlock_ruleset *const dom = get_current_fs_domain();
if (!dom)
return 0;
@@ -1208,8 +1124,7 @@ static int hook_file_open(struct file *const file)
layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
access_mask_t open_access_request, full_access_request, allowed_access;
const access_mask_t optional_access = LANDLOCK_ACCESS_FS_TRUNCATE;
- const struct landlock_ruleset *const dom =
- landlock_get_current_domain();
+ const struct landlock_ruleset *const dom = get_current_fs_domain();
if (!dom)
return 0;
@@ -1229,7 +1144,8 @@ static int hook_file_open(struct file *const file)
if (is_access_to_paths_allowed(
dom, &file->f_path,
- init_layer_masks(dom, full_access_request, &layer_masks),
+ landlock_init_layer_masks(dom, full_access_request,
+ &layer_masks, LANDLOCK_KEY_INODE),
&layer_masks, NULL, 0, NULL, NULL)) {
allowed_access = full_access_request;
} else {
diff --git a/security/landlock/limits.h b/security/landlock/limits.h
index 82288f0e9e5e..93c9c6f91556 100644
--- a/security/landlock/limits.h
+++ b/security/landlock/limits.h
@@ -21,6 +21,12 @@
#define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_TRUNCATE
#define LANDLOCK_MASK_ACCESS_FS ((LANDLOCK_LAST_ACCESS_FS << 1) - 1)
#define LANDLOCK_NUM_ACCESS_FS __const_hweight64(LANDLOCK_MASK_ACCESS_FS)
+#define LANDLOCK_SHIFT_ACCESS_FS 0
+
+#define LANDLOCK_LAST_ACCESS_NET LANDLOCK_ACCESS_NET_CONNECT_TCP
+#define LANDLOCK_MASK_ACCESS_NET ((LANDLOCK_LAST_ACCESS_NET << 1) - 1)
+#define LANDLOCK_NUM_ACCESS_NET __const_hweight64(LANDLOCK_MASK_ACCESS_NET)
+#define LANDLOCK_SHIFT_ACCESS_NET LANDLOCK_NUM_ACCESS_FS
/* clang-format on */
diff --git a/security/landlock/net.c b/security/landlock/net.c
new file mode 100644
index 000000000000..aaa92c2b1f08
--- /dev/null
+++ b/security/landlock/net.c
@@ -0,0 +1,200 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Landlock LSM - Network management and hooks
+ *
+ * Copyright © 2022-2023 Huawei Tech. Co., Ltd.
+ * Copyright © 2022-2023 Microsoft Corporation
+ */
+
+#include <linux/in.h>
+#include <linux/net.h>
+#include <linux/socket.h>
+#include <net/ipv6.h>
+
+#include "common.h"
+#include "cred.h"
+#include "limits.h"
+#include "net.h"
+#include "ruleset.h"
+
+int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
+ const u16 port, access_mask_t access_rights)
+{
+ int err;
+ const struct landlock_id id = {
+ .key.data = (__force uintptr_t)htons(port),
+ .type = LANDLOCK_KEY_NET_PORT,
+ };
+
+ BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
+
+ /* 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, id, access_rights);
+ mutex_unlock(&ruleset->lock);
+
+ return err;
+}
+
+static access_mask_t
+get_raw_handled_net_accesses(const struct landlock_ruleset *const domain)
+{
+ access_mask_t access_dom = 0;
+ size_t layer_level;
+
+ for (layer_level = 0; layer_level < domain->num_layers; layer_level++)
+ access_dom |= landlock_get_net_access_mask(domain, layer_level);
+ return access_dom;
+}
+
+static const struct landlock_ruleset *get_current_net_domain(void)
+{
+ const struct landlock_ruleset *const dom =
+ landlock_get_current_domain();
+
+ if (!dom || !get_raw_handled_net_accesses(dom))
+ return NULL;
+
+ return dom;
+}
+
+static int current_check_access_socket(struct socket *const sock,
+ struct sockaddr *const address,
+ const int addrlen,
+ const access_mask_t access_request)
+{
+ __be16 port;
+ layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {};
+ const struct landlock_rule *rule;
+ access_mask_t handled_access;
+ struct landlock_id id = {
+ .type = LANDLOCK_KEY_NET_PORT,
+ };
+ const struct landlock_ruleset *const dom = get_current_net_domain();
+
+ if (!dom)
+ return 0;
+ if (WARN_ON_ONCE(dom->num_layers < 1))
+ return -EACCES;
+
+ /* Checks if it's a (potential) TCP socket. */
+ if (sock->type != SOCK_STREAM)
+ return 0;
+
+ /* Checks for minimal header length to safely read sa_family. */
+ if (addrlen < offsetofend(typeof(*address), sa_family))
+ return -EINVAL;
+
+ switch (address->sa_family) {
+ case AF_UNSPEC:
+ case AF_INET:
+ if (addrlen < sizeof(struct sockaddr_in))
+ return -EINVAL;
+ port = ((struct sockaddr_in *)address)->sin_port;
+ break;
+
+#if IS_ENABLED(CONFIG_IPV6)
+ case AF_INET6:
+ if (addrlen < SIN6_LEN_RFC2133)
+ return -EINVAL;
+ port = ((struct sockaddr_in6 *)address)->sin6_port;
+ break;
+#endif /* IS_ENABLED(CONFIG_IPV6) */
+
+ default:
+ return 0;
+ }
+
+ /* Specific AF_UNSPEC handling. */
+ if (address->sa_family == AF_UNSPEC) {
+ /*
+ * Connecting to an address with AF_UNSPEC dissolves the TCP
+ * association, which have the same effect as closing the
+ * connection while retaining the socket object (i.e., the file
+ * descriptor). As for dropping privileges, closing
+ * connections is always allowed.
+ *
+ * For a TCP access control system, this request is legitimate.
+ * Let the network stack handle potential inconsistencies and
+ * return -EINVAL if needed.
+ */
+ if (access_request == LANDLOCK_ACCESS_NET_CONNECT_TCP)
+ return 0;
+
+ /*
+ * For compatibility reason, accept AF_UNSPEC for bind
+ * accesses (mapped to AF_INET) only if the address is
+ * INADDR_ANY (cf. __inet_bind). Checking the address is
+ * required to not wrongfully return -EACCES instead of
+ * -EAFNOSUPPORT.
+ *
+ * We could return 0 and let the network stack handle these
+ * checks, but it is safer to return a proper error and test
+ * consistency thanks to kselftest.
+ */
+ if (access_request == LANDLOCK_ACCESS_NET_BIND_TCP) {
+ /* addrlen has already been checked for AF_UNSPEC. */
+ const struct sockaddr_in *const sockaddr =
+ (struct sockaddr_in *)address;
+
+ if (sock->sk->__sk_common.skc_family != AF_INET)
+ return -EINVAL;
+
+ if (sockaddr->sin_addr.s_addr != htonl(INADDR_ANY))
+ return -EAFNOSUPPORT;
+ }
+ } else {
+ /*
+ * Checks sa_family consistency to not wrongfully return
+ * -EACCES instead of -EINVAL. Valid sa_family changes are
+ * only (from AF_INET or AF_INET6) to AF_UNSPEC.
+ *
+ * We could return 0 and let the network stack handle this
+ * check, but it is safer to return a proper error and test
+ * consistency thanks to kselftest.
+ */
+ if (address->sa_family != sock->sk->__sk_common.skc_family)
+ return -EINVAL;
+ }
+
+ id.key.data = (__force uintptr_t)port;
+ BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
+
+ rule = landlock_find_rule(dom, id);
+ handled_access = landlock_init_layer_masks(
+ dom, access_request, &layer_masks, LANDLOCK_KEY_NET_PORT);
+ if (landlock_unmask_layers(rule, handled_access, &layer_masks,
+ ARRAY_SIZE(layer_masks)))
+ return 0;
+
+ return -EACCES;
+}
+
+static int hook_socket_bind(struct socket *const sock,
+ struct sockaddr *const address, const int addrlen)
+{
+ return current_check_access_socket(sock, address, addrlen,
+ LANDLOCK_ACCESS_NET_BIND_TCP);
+}
+
+static int hook_socket_connect(struct socket *const sock,
+ struct sockaddr *const address,
+ const int addrlen)
+{
+ return current_check_access_socket(sock, address, addrlen,
+ LANDLOCK_ACCESS_NET_CONNECT_TCP);
+}
+
+static struct security_hook_list landlock_hooks[] __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..09960c237a13
--- /dev/null
+++ b/security/landlock/net.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Landlock LSM - Network management and hooks
+ *
+ * Copyright © 2022-2023 Huawei Tech. Co., Ltd.
+ */
+
+#ifndef _SECURITY_LANDLOCK_NET_H
+#define _SECURITY_LANDLOCK_NET_H
+
+#include "common.h"
+#include "ruleset.h"
+#include "setup.h"
+
+#if IS_ENABLED(CONFIG_INET)
+__init void landlock_add_net_hooks(void);
+
+int landlock_append_net_rule(struct landlock_ruleset *const ruleset,
+ const u16 port, access_mask_t access_rights);
+#else /* IS_ENABLED(CONFIG_INET) */
+static inline void landlock_add_net_hooks(void)
+{
+}
+
+static inline int
+landlock_append_net_rule(struct landlock_ruleset *const ruleset, const u16 port,
+ access_mask_t access_rights)
+{
+ return -EAFNOSUPPORT;
+}
+#endif /* IS_ENABLED(CONFIG_INET) */
+
+#endif /* _SECURITY_LANDLOCK_NET_H */
diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c
index 996484f98bfd..ffedc99f2b68 100644
--- a/security/landlock/ruleset.c
+++ b/security/landlock/ruleset.c
@@ -29,33 +29,43 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers)
struct landlock_ruleset *new_ruleset;
new_ruleset =
- kzalloc(struct_size(new_ruleset, fs_access_masks, num_layers),
+ kzalloc(struct_size(new_ruleset, access_masks, num_layers),
GFP_KERNEL_ACCOUNT);
if (!new_ruleset)
return ERR_PTR(-ENOMEM);
refcount_set(&new_ruleset->usage, 1);
mutex_init(&new_ruleset->lock);
- new_ruleset->root = RB_ROOT;
+ new_ruleset->root_inode = RB_ROOT;
+
+#if IS_ENABLED(CONFIG_INET)
+ new_ruleset->root_net_port = RB_ROOT;
+#endif /* IS_ENABLED(CONFIG_INET) */
+
new_ruleset->num_layers = num_layers;
/*
* hierarchy = NULL
* num_rules = 0
- * fs_access_masks[] = 0
+ * access_masks[] = 0
*/
return new_ruleset;
}
struct landlock_ruleset *
-landlock_create_ruleset(const access_mask_t fs_access_mask)
+landlock_create_ruleset(const access_mask_t fs_access_mask,
+ const access_mask_t net_access_mask)
{
struct landlock_ruleset *new_ruleset;
/* Informs about useless ruleset. */
- if (!fs_access_mask)
+ if (!fs_access_mask && !net_access_mask)
return ERR_PTR(-ENOMSG);
new_ruleset = create_ruleset(1);
- if (!IS_ERR(new_ruleset))
- new_ruleset->fs_access_masks[0] = fs_access_mask;
+ if (IS_ERR(new_ruleset))
+ return new_ruleset;
+ if (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);
return new_ruleset;
}
@@ -68,8 +78,25 @@ static void build_check_rule(void)
BUILD_BUG_ON(rule.num_layers < LANDLOCK_MAX_NUM_LAYERS);
}
+static bool is_object_pointer(const enum landlock_key_type key_type)
+{
+ switch (key_type) {
+ case LANDLOCK_KEY_INODE:
+ return true;
+
+#if IS_ENABLED(CONFIG_INET)
+ case LANDLOCK_KEY_NET_PORT:
+ return false;
+#endif /* IS_ENABLED(CONFIG_INET) */
+
+ default:
+ WARN_ON_ONCE(1);
+ return false;
+ }
+}
+
static struct landlock_rule *
-create_rule(struct landlock_object *const object,
+create_rule(const struct landlock_id id,
const struct landlock_layer (*const layers)[], const u32 num_layers,
const struct landlock_layer *const new_layer)
{
@@ -90,8 +117,13 @@ create_rule(struct landlock_object *const object,
if (!new_rule)
return ERR_PTR(-ENOMEM);
RB_CLEAR_NODE(&new_rule->node);
- landlock_get_object(object);
- new_rule->object = object;
+ if (is_object_pointer(id.type)) {
+ /* This should be catched by insert_rule(). */
+ WARN_ON_ONCE(!id.key.object);
+ landlock_get_object(id.key.object);
+ }
+
+ new_rule->key = id.key;
new_rule->num_layers = new_num_layers;
/* Copies the original layer stack. */
memcpy(new_rule->layers, layers,
@@ -102,12 +134,32 @@ create_rule(struct landlock_object *const object,
return new_rule;
}
-static void free_rule(struct landlock_rule *const rule)
+static struct rb_root *get_root(struct landlock_ruleset *const ruleset,
+ const enum landlock_key_type key_type)
+{
+ switch (key_type) {
+ case LANDLOCK_KEY_INODE:
+ return &ruleset->root_inode;
+
+#if IS_ENABLED(CONFIG_INET)
+ case LANDLOCK_KEY_NET_PORT:
+ return &ruleset->root_net_port;
+#endif /* IS_ENABLED(CONFIG_INET) */
+
+ default:
+ WARN_ON_ONCE(1);
+ return ERR_PTR(-EINVAL);
+ }
+}
+
+static void free_rule(struct landlock_rule *const rule,
+ const enum landlock_key_type key_type)
{
might_sleep();
if (!rule)
return;
- landlock_put_object(rule->object);
+ if (is_object_pointer(key_type))
+ landlock_put_object(rule->key.object);
kfree(rule);
}
@@ -117,19 +169,21 @@ static void build_check_ruleset(void)
.num_rules = ~0,
.num_layers = ~0,
};
- typeof(ruleset.fs_access_masks[0]) fs_access_mask = ~0;
+ typeof(ruleset.access_masks[0]) access_masks = ~0;
BUILD_BUG_ON(ruleset.num_rules < LANDLOCK_MAX_NUM_RULES);
BUILD_BUG_ON(ruleset.num_layers < LANDLOCK_MAX_NUM_LAYERS);
- BUILD_BUG_ON(fs_access_mask < LANDLOCK_MASK_ACCESS_FS);
+ BUILD_BUG_ON(access_masks <
+ ((LANDLOCK_MASK_ACCESS_FS << LANDLOCK_SHIFT_ACCESS_FS) |
+ (LANDLOCK_MASK_ACCESS_NET << LANDLOCK_SHIFT_ACCESS_NET)));
}
/**
* insert_rule - Create and insert a rule in a ruleset
*
* @ruleset: The ruleset to be updated.
- * @object: The object to build the new rule with. The underlying kernel
- * object must be held by the caller.
+ * @id: The ID to build the new rule with. The underlying kernel object, if
+ * any, must be held by the caller.
* @layers: One or multiple layers to be copied into the new rule.
* @num_layers: The number of @layers entries.
*
@@ -143,26 +197,35 @@ static void build_check_ruleset(void)
* access rights.
*/
static int insert_rule(struct landlock_ruleset *const ruleset,
- struct landlock_object *const object,
+ const struct landlock_id id,
const struct landlock_layer (*const layers)[],
- size_t num_layers)
+ const size_t num_layers)
{
struct rb_node **walker_node;
struct rb_node *parent_node = NULL;
struct landlock_rule *new_rule;
+ struct rb_root *root;
might_sleep();
lockdep_assert_held(&ruleset->lock);
- if (WARN_ON_ONCE(!object || !layers))
+ if (WARN_ON_ONCE(!layers))
return -ENOENT;
- walker_node = &(ruleset->root.rb_node);
+
+ if (is_object_pointer(id.type) && WARN_ON_ONCE(!id.key.object))
+ return -ENOENT;
+
+ root = get_root(ruleset, id.type);
+ if (IS_ERR(root))
+ return PTR_ERR(root);
+
+ walker_node = &root->rb_node;
while (*walker_node) {
struct landlock_rule *const this =
rb_entry(*walker_node, struct landlock_rule, node);
- if (this->object != object) {
+ if (this->key.data != id.key.data) {
parent_node = *walker_node;
- if (this->object < object)
+ if (this->key.data < id.key.data)
walker_node = &((*walker_node)->rb_right);
else
walker_node = &((*walker_node)->rb_left);
@@ -194,24 +257,24 @@ static int insert_rule(struct landlock_ruleset *const ruleset,
* Intersects access rights when it is a merge between a
* ruleset and a domain.
*/
- new_rule = create_rule(object, &this->layers, this->num_layers,
+ new_rule = create_rule(id, &this->layers, this->num_layers,
&(*layers)[0]);
if (IS_ERR(new_rule))
return PTR_ERR(new_rule);
- rb_replace_node(&this->node, &new_rule->node, &ruleset->root);
- free_rule(this);
+ rb_replace_node(&this->node, &new_rule->node, root);
+ free_rule(this, id.type);
return 0;
}
- /* There is no match for @object. */
+ /* There is no match for @id. */
build_check_ruleset();
if (ruleset->num_rules >= LANDLOCK_MAX_NUM_RULES)
return -E2BIG;
- new_rule = create_rule(object, layers, num_layers, NULL);
+ new_rule = create_rule(id, layers, num_layers, NULL);
if (IS_ERR(new_rule))
return PTR_ERR(new_rule);
rb_link_node(&new_rule->node, parent_node, walker_node);
- rb_insert_color(&new_rule->node, &ruleset->root);
+ rb_insert_color(&new_rule->node, root);
ruleset->num_rules++;
return 0;
}
@@ -229,7 +292,7 @@ static void build_check_layer(void)
/* @ruleset must be locked by the caller. */
int landlock_insert_rule(struct landlock_ruleset *const ruleset,
- struct landlock_object *const object,
+ const struct landlock_id id,
const access_mask_t access)
{
struct landlock_layer layers[] = { {
@@ -239,7 +302,7 @@ int landlock_insert_rule(struct landlock_ruleset *const ruleset,
} };
build_check_layer();
- return insert_rule(ruleset, object, &layers, ARRAY_SIZE(layers));
+ return insert_rule(ruleset, id, &layers, ARRAY_SIZE(layers));
}
static inline void get_hierarchy(struct landlock_hierarchy *const hierarchy)
@@ -258,10 +321,51 @@ static void put_hierarchy(struct landlock_hierarchy *hierarchy)
}
}
+static int merge_tree(struct landlock_ruleset *const dst,
+ struct landlock_ruleset *const src,
+ const enum landlock_key_type key_type)
+{
+ struct landlock_rule *walker_rule, *next_rule;
+ struct rb_root *src_root;
+ int err = 0;
+
+ might_sleep();
+ lockdep_assert_held(&dst->lock);
+ lockdep_assert_held(&src->lock);
+
+ src_root = get_root(src, key_type);
+ if (IS_ERR(src_root))
+ return PTR_ERR(src_root);
+
+ /* Merges the @src tree. */
+ rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, src_root,
+ node) {
+ struct landlock_layer layers[] = { {
+ .level = dst->num_layers,
+ } };
+ const struct landlock_id id = {
+ .key = walker_rule->key,
+ .type = key_type,
+ };
+
+ if (WARN_ON_ONCE(walker_rule->num_layers != 1))
+ return -EINVAL;
+
+ if (WARN_ON_ONCE(walker_rule->layers[0].level != 0))
+ return -EINVAL;
+
+ layers[0].access = walker_rule->layers[0].access;
+
+ err = insert_rule(dst, id, &layers, ARRAY_SIZE(layers));
+ if (err)
+ return err;
+ }
+ return err;
+}
+
static int merge_ruleset(struct landlock_ruleset *const dst,
struct landlock_ruleset *const src)
{
- struct landlock_rule *walker_rule, *next_rule;
int err = 0;
might_sleep();
@@ -281,29 +385,19 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
err = -EINVAL;
goto out_unlock;
}
- dst->fs_access_masks[dst->num_layers - 1] = src->fs_access_masks[0];
+ dst->access_masks[dst->num_layers - 1] = src->access_masks[0];
- /* Merges the @src tree. */
- rbtree_postorder_for_each_entry_safe(walker_rule, next_rule, &src->root,
- node) {
- struct landlock_layer layers[] = { {
- .level = dst->num_layers,
- } };
+ /* Merges the @src inode tree. */
+ err = merge_tree(dst, src, LANDLOCK_KEY_INODE);
+ if (err)
+ goto out_unlock;
- if (WARN_ON_ONCE(walker_rule->num_layers != 1)) {
- err = -EINVAL;
- goto out_unlock;
- }
- if (WARN_ON_ONCE(walker_rule->layers[0].level != 0)) {
- err = -EINVAL;
- goto out_unlock;
- }
- layers[0].access = walker_rule->layers[0].access;
- err = insert_rule(dst, walker_rule->object, &layers,
- ARRAY_SIZE(layers));
- if (err)
- goto out_unlock;
- }
+#if IS_ENABLED(CONFIG_INET)
+ /* Merges the @src network port tree. */
+ err = merge_tree(dst, src, LANDLOCK_KEY_NET_PORT);
+ if (err)
+ goto out_unlock;
+#endif /* IS_ENABLED(CONFIG_INET) */
out_unlock:
mutex_unlock(&src->lock);
@@ -311,10 +405,41 @@ out_unlock:
return err;
}
+static int inherit_tree(struct landlock_ruleset *const parent,
+ struct landlock_ruleset *const child,
+ const enum landlock_key_type key_type)
+{
+ struct landlock_rule *walker_rule, *next_rule;
+ struct rb_root *parent_root;
+ int err = 0;
+
+ might_sleep();
+ lockdep_assert_held(&parent->lock);
+ lockdep_assert_held(&child->lock);
+
+ parent_root = get_root(parent, key_type);
+ if (IS_ERR(parent_root))
+ return PTR_ERR(parent_root);
+
+ /* Copies the @parent inode or network tree. */
+ rbtree_postorder_for_each_entry_safe(walker_rule, next_rule,
+ parent_root, node) {
+ const struct landlock_id id = {
+ .key = walker_rule->key,
+ .type = key_type,
+ };
+
+ err = insert_rule(child, id, &walker_rule->layers,
+ walker_rule->num_layers);
+ if (err)
+ return err;
+ }
+ return err;
+}
+
static int inherit_ruleset(struct landlock_ruleset *const parent,
struct landlock_ruleset *const child)
{
- struct landlock_rule *walker_rule, *next_rule;
int err = 0;
might_sleep();
@@ -325,23 +450,25 @@ static int inherit_ruleset(struct landlock_ruleset *const parent,
mutex_lock(&child->lock);
mutex_lock_nested(&parent->lock, SINGLE_DEPTH_NESTING);
- /* Copies the @parent tree. */
- rbtree_postorder_for_each_entry_safe(walker_rule, next_rule,
- &parent->root, node) {
- err = insert_rule(child, walker_rule->object,
- &walker_rule->layers,
- walker_rule->num_layers);
- if (err)
- goto out_unlock;
- }
+ /* Copies the @parent inode tree. */
+ err = inherit_tree(parent, child, LANDLOCK_KEY_INODE);
+ if (err)
+ goto out_unlock;
+
+#if IS_ENABLED(CONFIG_INET)
+ /* Copies the @parent network port tree. */
+ err = inherit_tree(parent, child, LANDLOCK_KEY_NET_PORT);
+ if (err)
+ goto out_unlock;
+#endif /* IS_ENABLED(CONFIG_INET) */
if (WARN_ON_ONCE(child->num_layers <= parent->num_layers)) {
err = -EINVAL;
goto out_unlock;
}
/* Copies the parent layer stack and leaves a space for the new layer. */
- memcpy(child->fs_access_masks, parent->fs_access_masks,
- flex_array_size(parent, fs_access_masks, parent->num_layers));
+ memcpy(child->access_masks, parent->access_masks,
+ flex_array_size(parent, access_masks, parent->num_layers));
if (WARN_ON_ONCE(!parent->hierarchy)) {
err = -EINVAL;
@@ -361,8 +488,16 @@ static void free_ruleset(struct landlock_ruleset *const ruleset)
struct landlock_rule *freeme, *next;
might_sleep();
- rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root, node)
- free_rule(freeme);
+ rbtree_postorder_for_each_entry_safe(freeme, next, &ruleset->root_inode,
+ node)
+ free_rule(freeme, LANDLOCK_KEY_INODE);
+
+#if IS_ENABLED(CONFIG_INET)
+ rbtree_postorder_for_each_entry_safe(freeme, next,
+ &ruleset->root_net_port, node)
+ free_rule(freeme, LANDLOCK_KEY_NET_PORT);
+#endif /* IS_ENABLED(CONFIG_INET) */
+
put_hierarchy(ruleset->hierarchy);
kfree(ruleset);
}
@@ -453,23 +588,151 @@ out_put_dom:
*/
const struct landlock_rule *
landlock_find_rule(const struct landlock_ruleset *const ruleset,
- const struct landlock_object *const object)
+ const struct landlock_id id)
{
+ const struct rb_root *root;
const struct rb_node *node;
- if (!object)
+ root = get_root((struct landlock_ruleset *)ruleset, id.type);
+ if (IS_ERR(root))
return NULL;
- node = ruleset->root.rb_node;
+ node = root->rb_node;
+
while (node) {
struct landlock_rule *this =
rb_entry(node, struct landlock_rule, node);
- if (this->object == object)
+ if (this->key.data == id.key.data)
return this;
- if (this->object < object)
+ if (this->key.data < id.key.data)
node = node->rb_right;
else
node = node->rb_left;
}
return NULL;
}
+
+/*
+ * @layer_masks is read and may be updated according to the access request and
+ * the matching rule.
+ * @masks_array_size must be equal to ARRAY_SIZE(*layer_masks).
+ *
+ * Returns true if the request is allowed (i.e. relevant layer masks for the
+ * request are empty).
+ */
+bool landlock_unmask_layers(const struct landlock_rule *const rule,
+ const access_mask_t access_request,
+ layer_mask_t (*const layer_masks)[],
+ const size_t masks_array_size)
+{
+ size_t layer_level;
+
+ if (!access_request || !layer_masks)
+ return true;
+ if (!rule)
+ return false;
+
+ /*
+ * An access is granted if, for each policy layer, at least one rule
+ * encountered on the pathwalk grants the requested access,
+ * regardless of its position in the layer stack. We must then check
+ * the remaining layers for each inode, from the first added layer to
+ * the last one. When there is multiple requested accesses, for each
+ * policy layer, the full set of requested accesses may not be granted
+ * by only one rule, but by the union (binary OR) of multiple rules.
+ * E.g. /a/b <execute> + /a <read> => /a/b <execute + read>
+ */
+ for (layer_level = 0; layer_level < rule->num_layers; layer_level++) {
+ const struct landlock_layer *const layer =
+ &rule->layers[layer_level];
+ const layer_mask_t layer_bit = BIT_ULL(layer->level - 1);
+ const unsigned long access_req = access_request;
+ unsigned long access_bit;
+ bool is_empty;
+
+ /*
+ * Records in @layer_masks which layer grants access to each
+ * requested access.
+ */
+ is_empty = true;
+ for_each_set_bit(access_bit, &access_req, masks_array_size) {
+ if (layer->access & BIT_ULL(access_bit))
+ (*layer_masks)[access_bit] &= ~layer_bit;
+ is_empty = is_empty && !(*layer_masks)[access_bit];
+ }
+ if (is_empty)
+ return true;
+ }
+ return false;
+}
+
+typedef access_mask_t
+get_access_mask_t(const struct landlock_ruleset *const ruleset,
+ const u16 layer_level);
+
+/**
+ * landlock_init_layer_masks - Initialize layer masks from an access request
+ *
+ * Populates @layer_masks such that for each access right in @access_request,
+ * the bits for all the layers are set where this access right is handled.
+ *
+ * @domain: The domain that defines the current restrictions.
+ * @access_request: The requested access rights to check.
+ * @layer_masks: It must contain %LANDLOCK_NUM_ACCESS_FS or
+ * %LANDLOCK_NUM_ACCESS_NET elements according to @key_type.
+ * @key_type: The key type to switch between access masks of different types.
+ *
+ * Returns: An access mask where each access right bit is set which is handled
+ * in any of the active layers in @domain.
+ */
+access_mask_t
+landlock_init_layer_masks(const struct landlock_ruleset *const domain,
+ const access_mask_t access_request,
+ layer_mask_t (*const layer_masks)[],
+ const enum landlock_key_type key_type)
+{
+ access_mask_t handled_accesses = 0;
+ size_t layer_level, num_access;
+ get_access_mask_t *get_access_mask;
+
+ switch (key_type) {
+ case LANDLOCK_KEY_INODE:
+ get_access_mask = landlock_get_fs_access_mask;
+ num_access = LANDLOCK_NUM_ACCESS_FS;
+ break;
+
+#if IS_ENABLED(CONFIG_INET)
+ case LANDLOCK_KEY_NET_PORT:
+ get_access_mask = landlock_get_net_access_mask;
+ num_access = LANDLOCK_NUM_ACCESS_NET;
+ break;
+#endif /* IS_ENABLED(CONFIG_INET) */
+
+ default:
+ WARN_ON_ONCE(1);
+ return 0;
+ }
+
+ memset(layer_masks, 0,
+ array_size(sizeof((*layer_masks)[0]), num_access));
+
+ /* An empty access request can happen because of O_WRONLY | O_RDWR. */
+ if (!access_request)
+ return 0;
+
+ /* Saves all handled accesses per layer. */
+ for (layer_level = 0; layer_level < domain->num_layers; layer_level++) {
+ const unsigned long access_req = access_request;
+ unsigned long access_bit;
+
+ for_each_set_bit(access_bit, &access_req, num_access) {
+ if (BIT_ULL(access_bit) &
+ get_access_mask(domain, layer_level)) {
+ (*layer_masks)[access_bit] |=
+ BIT_ULL(layer_level);
+ handled_accesses |= BIT_ULL(access_bit);
+ }
+ }
+ }
+ return handled_accesses;
+}
diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h
index 55b1df8f66a8..c7f1526784fd 100644
--- a/security/landlock/ruleset.h
+++ b/security/landlock/ruleset.h
@@ -15,16 +15,35 @@
#include <linux/rbtree.h>
#include <linux/refcount.h>
#include <linux/workqueue.h>
+#include <uapi/linux/landlock.h>
#include "limits.h"
#include "object.h"
+/*
+ * All access rights that are denied by default whether they are handled or not
+ * by a ruleset/layer. This must be ORed with all ruleset->access_masks[]
+ * entries when we need to get the absolute handled access masks.
+ */
+/* clang-format off */
+#define LANDLOCK_ACCESS_FS_INITIALLY_DENIED ( \
+ LANDLOCK_ACCESS_FS_REFER)
+/* clang-format on */
+
typedef u16 access_mask_t;
/* Makes sure all filesystem access rights can be stored. */
static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
+/* Makes sure all network access rights can be stored. */
+static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_NET);
/* Makes sure for_each_set_bit() and for_each_clear_bit() calls are OK. */
static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));
+/* Ruleset access masks. */
+typedef u32 access_masks_t;
+/* Makes sure all ruleset access rights can be stored. */
+static_assert(BITS_PER_TYPE(access_masks_t) >=
+ LANDLOCK_NUM_ACCESS_FS + LANDLOCK_NUM_ACCESS_NET);
+
typedef u16 layer_mask_t;
/* Makes sure all layers can be checked. */
static_assert(BITS_PER_TYPE(layer_mask_t) >= LANDLOCK_MAX_NUM_LAYERS);
@@ -45,6 +64,52 @@ struct landlock_layer {
};
/**
+ * union landlock_key - Key of a ruleset's red-black tree
+ */
+union landlock_key {
+ /**
+ * @object: Pointer to identify a kernel object (e.g. an inode).
+ */
+ struct landlock_object *object;
+ /**
+ * @data: Raw data to identify an arbitrary 32-bit value
+ * (e.g. a TCP port).
+ */
+ uintptr_t data;
+};
+
+/**
+ * enum landlock_key_type - Type of &union landlock_key
+ */
+enum landlock_key_type {
+ /**
+ * @LANDLOCK_KEY_INODE: Type of &landlock_ruleset.root_inode's node
+ * keys.
+ */
+ LANDLOCK_KEY_INODE = 1,
+ /**
+ * @LANDLOCK_KEY_NET_PORT: Type of &landlock_ruleset.root_net_port's
+ * node keys.
+ */
+ LANDLOCK_KEY_NET_PORT,
+};
+
+/**
+ * struct landlock_id - Unique rule identifier for a ruleset
+ */
+struct landlock_id {
+ /**
+ * @key: Identifies either a kernel object (e.g. an inode) or
+ * a raw value (e.g. a TCP port).
+ */
+ union landlock_key key;
+ /**
+ * @type: Type of a landlock_ruleset's root tree.
+ */
+ const enum landlock_key_type type;
+};
+
+/**
* struct landlock_rule - Access rights tied to an object
*/
struct landlock_rule {
@@ -53,12 +118,13 @@ struct landlock_rule {
*/
struct rb_node node;
/**
- * @object: Pointer to identify a kernel object (e.g. an inode). This
- * is used as a key for this ruleset element. This pointer is set once
- * and never modified. It always points to an allocated object because
- * each rule increments the refcount of its object.
+ * @key: A union to identify either a kernel object (e.g. an inode) or
+ * a raw data value (e.g. a network socket port). This is used as a key
+ * for this ruleset element. The pointer is set once and never
+ * modified. It always points to an allocated object because each rule
+ * increments the refcount of its object.
*/
- struct landlock_object *object;
+ union landlock_key key;
/**
* @num_layers: Number of entries in @layers.
*/
@@ -94,11 +160,23 @@ struct landlock_hierarchy {
*/
struct landlock_ruleset {
/**
- * @root: Root of a red-black tree containing &struct landlock_rule
- * nodes. Once a ruleset is tied to a process (i.e. as a domain), this
- * tree is immutable until @usage reaches zero.
+ * @root_inode: Root of a red-black tree containing &struct
+ * landlock_rule nodes with inode object. 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;
+ struct rb_root root_inode;
+
+#if IS_ENABLED(CONFIG_INET)
+ /**
+ * @root_net_port: Root of a red-black tree containing &struct
+ * landlock_rule nodes with network port. 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_net_port;
+#endif /* IS_ENABLED(CONFIG_INET) */
+
/**
* @hierarchy: Enables hierarchy identification even when a parent
* domain vanishes. This is needed for the ptrace protection.
@@ -110,7 +188,7 @@ struct landlock_ruleset {
* section. This is only used by
* landlock_put_ruleset_deferred() when @usage reaches zero.
* The fields @lock, @usage, @num_rules, @num_layers and
- * @fs_access_masks are then unused.
+ * @access_masks are then unused.
*/
struct work_struct work_free;
struct {
@@ -137,30 +215,31 @@ struct landlock_ruleset {
*/
u32 num_layers;
/**
- * @fs_access_masks: Contains the subset of filesystem
- * actions that are restricted by a ruleset. A domain
- * saves all layers of merged rulesets in a stack
- * (FAM), starting from the first layer to the last
- * one. These layers are used when merging rulesets,
- * for user space backward compatibility (i.e.
- * future-proof), and to properly handle merged
+ * @access_masks: Contains the subset of filesystem and
+ * network actions that are restricted by a ruleset.
+ * A domain saves all layers of merged rulesets in a
+ * stack (FAM), starting from the first layer to the
+ * last one. These layers are used when merging
+ * rulesets, for user space backward compatibility
+ * (i.e. future-proof), and to properly handle merged
* rulesets without overlapping access rights. These
* layers are set once and never changed for the
* lifetime of the ruleset.
*/
- access_mask_t fs_access_masks[];
+ access_masks_t access_masks[];
};
};
};
struct landlock_ruleset *
-landlock_create_ruleset(const access_mask_t fs_access_mask);
+landlock_create_ruleset(const access_mask_t access_mask_fs,
+ const access_mask_t access_mask_net);
void landlock_put_ruleset(struct landlock_ruleset *const ruleset);
void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset);
int landlock_insert_rule(struct landlock_ruleset *const ruleset,
- struct landlock_object *const object,
+ const struct landlock_id id,
const access_mask_t access);
struct landlock_ruleset *
@@ -169,7 +248,7 @@ landlock_merge_ruleset(struct landlock_ruleset *const parent,
const struct landlock_rule *
landlock_find_rule(const struct landlock_ruleset *const ruleset,
- const struct landlock_object *const object);
+ const struct landlock_id id);
static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
{
@@ -177,4 +256,68 @@ static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset)
refcount_inc(&ruleset->usage);
}
+static inline void
+landlock_add_fs_access_mask(struct landlock_ruleset *const ruleset,
+ const access_mask_t fs_access_mask,
+ const u16 layer_level)
+{
+ access_mask_t fs_mask = fs_access_mask & LANDLOCK_MASK_ACCESS_FS;
+
+ /* Should already be checked in sys_landlock_create_ruleset(). */
+ WARN_ON_ONCE(fs_access_mask != fs_mask);
+ ruleset->access_masks[layer_level] |=
+ (fs_mask << LANDLOCK_SHIFT_ACCESS_FS);
+}
+
+static inline void
+landlock_add_net_access_mask(struct landlock_ruleset *const ruleset,
+ const access_mask_t net_access_mask,
+ const u16 layer_level)
+{
+ access_mask_t net_mask = net_access_mask & LANDLOCK_MASK_ACCESS_NET;
+
+ /* Should already be checked in sys_landlock_create_ruleset(). */
+ WARN_ON_ONCE(net_access_mask != net_mask);
+ ruleset->access_masks[layer_level] |=
+ (net_mask << LANDLOCK_SHIFT_ACCESS_NET);
+}
+
+static inline access_mask_t
+landlock_get_raw_fs_access_mask(const struct landlock_ruleset *const ruleset,
+ const u16 layer_level)
+{
+ return (ruleset->access_masks[layer_level] >>
+ LANDLOCK_SHIFT_ACCESS_FS) &
+ LANDLOCK_MASK_ACCESS_FS;
+}
+
+static inline access_mask_t
+landlock_get_fs_access_mask(const struct landlock_ruleset *const ruleset,
+ const u16 layer_level)
+{
+ /* Handles all initially denied by default access rights. */
+ return landlock_get_raw_fs_access_mask(ruleset, layer_level) |
+ LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
+}
+
+static inline access_mask_t
+landlock_get_net_access_mask(const struct landlock_ruleset *const ruleset,
+ const u16 layer_level)
+{
+ return (ruleset->access_masks[layer_level] >>
+ LANDLOCK_SHIFT_ACCESS_NET) &
+ LANDLOCK_MASK_ACCESS_NET;
+}
+
+bool landlock_unmask_layers(const struct landlock_rule *const rule,
+ const access_mask_t access_request,
+ layer_mask_t (*const layer_masks)[],
+ const size_t masks_array_size);
+
+access_mask_t
+landlock_init_layer_masks(const struct landlock_ruleset *const domain,
+ const access_mask_t access_request,
+ layer_mask_t (*const layer_masks)[],
+ const enum landlock_key_type key_type);
+
#endif /* _SECURITY_LANDLOCK_RULESET_H */
diff --git a/security/landlock/setup.c b/security/landlock/setup.c
index 0f6113528fa4..3e11d303542f 100644
--- a/security/landlock/setup.c
+++ b/security/landlock/setup.c
@@ -12,6 +12,7 @@
#include "common.h"
#include "cred.h"
#include "fs.h"
+#include "net.h"
#include "ptrace.h"
#include "setup.h"
@@ -29,6 +30,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 245cc650a4dc..898358f57fa0 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"
@@ -74,7 +75,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_port_attr net_port_attr;
+ size_t ruleset_size, path_beneath_size, net_port_size;
/*
* For each user space ABI structures, first checks that there is no
@@ -82,13 +84,19 @@ static void build_check_abi(void)
* struct size.
*/
ruleset_size = sizeof(ruleset_attr.handled_access_fs);
+ ruleset_size += sizeof(ruleset_attr.handled_access_net);
BUILD_BUG_ON(sizeof(ruleset_attr) != ruleset_size);
- BUILD_BUG_ON(sizeof(ruleset_attr) != 8);
+ BUILD_BUG_ON(sizeof(ruleset_attr) != 16);
path_beneath_size = sizeof(path_beneath_attr.allowed_access);
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_port_size = sizeof(net_port_attr.allowed_access);
+ net_port_size += sizeof(net_port_attr.port);
+ BUILD_BUG_ON(sizeof(net_port_attr) != net_port_size);
+ BUILD_BUG_ON(sizeof(net_port_attr) != 16);
}
/* Ruleset handling */
@@ -129,7 +137,7 @@ static const struct file_operations ruleset_fops = {
.write = fop_dummy_write,
};
-#define LANDLOCK_ABI_VERSION 3
+#define LANDLOCK_ABI_VERSION 4
/**
* sys_landlock_create_ruleset - Create a new ruleset
@@ -188,8 +196,14 @@ SYSCALL_DEFINE3(landlock_create_ruleset,
LANDLOCK_MASK_ACCESS_FS)
return -EINVAL;
+ /* Checks network content (and 32-bits cast). */
+ if ((ruleset_attr.handled_access_net | LANDLOCK_MASK_ACCESS_NET) !=
+ LANDLOCK_MASK_ACCESS_NET)
+ return -EINVAL;
+
/* Checks arguments and transforms to kernel struct. */
- ruleset = landlock_create_ruleset(ruleset_attr.handled_access_fs);
+ ruleset = landlock_create_ruleset(ruleset_attr.handled_access_fs,
+ ruleset_attr.handled_access_net);
if (IS_ERR(ruleset))
return PTR_ERR(ruleset);
@@ -274,13 +288,84 @@ out_fdput:
return err;
}
+static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
+ const void __user *const rule_attr)
+{
+ struct landlock_path_beneath_attr path_beneath_attr;
+ struct path path;
+ int res, err;
+ access_mask_t mask;
+
+ /* Copies raw user space buffer. */
+ res = copy_from_user(&path_beneath_attr, rule_attr,
+ sizeof(path_beneath_attr));
+ if (res)
+ return -EFAULT;
+
+ /*
+ * Informs about useless rule: empty allowed_access (i.e. deny rules)
+ * are ignored in path walks.
+ */
+ if (!path_beneath_attr.allowed_access)
+ return -ENOMSG;
+
+ /* Checks that allowed_access matches the @ruleset constraints. */
+ mask = landlock_get_raw_fs_access_mask(ruleset, 0);
+ if ((path_beneath_attr.allowed_access | mask) != mask)
+ return -EINVAL;
+
+ /* Gets and checks the new rule. */
+ err = get_path_from_fd(path_beneath_attr.parent_fd, &path);
+ if (err)
+ return err;
+
+ /* Imports the new rule. */
+ err = landlock_append_fs_rule(ruleset, &path,
+ path_beneath_attr.allowed_access);
+ path_put(&path);
+ return err;
+}
+
+static int add_rule_net_port(struct landlock_ruleset *ruleset,
+ const void __user *const rule_attr)
+{
+ struct landlock_net_port_attr net_port_attr;
+ int res;
+ access_mask_t mask;
+
+ /* Copies raw user space buffer. */
+ res = copy_from_user(&net_port_attr, rule_attr, sizeof(net_port_attr));
+ if (res)
+ return -EFAULT;
+
+ /*
+ * Informs about useless rule: empty allowed_access (i.e. deny rules)
+ * are ignored by network actions.
+ */
+ if (!net_port_attr.allowed_access)
+ return -ENOMSG;
+
+ /* Checks that allowed_access matches the @ruleset constraints. */
+ mask = landlock_get_net_access_mask(ruleset, 0);
+ if ((net_port_attr.allowed_access | mask) != mask)
+ return -EINVAL;
+
+ /* Denies inserting a rule with port greater than 65535. */
+ if (net_port_attr.port > U16_MAX)
+ return -EINVAL;
+
+ /* Imports the new rule. */
+ return landlock_append_net_rule(ruleset, net_port_attr.port,
+ net_port_attr.allowed_access);
+}
+
/**
* sys_landlock_add_rule - Add a new rule to a ruleset
*
* @ruleset_fd: File descriptor tied to the ruleset that should be extended
* with the new rule.
- * @rule_type: Identify the structure type pointed to by @rule_attr (only
- * %LANDLOCK_RULE_PATH_BENEATH for now).
+ * @rule_type: Identify the structure type pointed to by @rule_attr:
+ * %LANDLOCK_RULE_PATH_BENEATH or %LANDLOCK_RULE_NET_PORT.
* @rule_attr: Pointer to a rule (only of type &struct
* landlock_path_beneath_attr for now).
* @flags: Must be 0.
@@ -291,9 +376,13 @@ out_fdput:
* Possible returned errors are:
*
* - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time;
+ * - %EAFNOSUPPORT: @rule_type is %LANDLOCK_RULE_NET_PORT but TCP/IP is not
+ * supported by the running kernel;
* - %EINVAL: @flags is not 0, or inconsistent access in the rule (i.e.
- * &landlock_path_beneath_attr.allowed_access is not a subset of the
- * ruleset handled accesses);
+ * &landlock_path_beneath_attr.allowed_access or
+ * &landlock_net_port_attr.allowed_access is not a subset of the
+ * ruleset handled accesses), or &landlock_net_port_attr.port is
+ * greater than 65535;
* - %ENOMSG: Empty accesses (e.g. &landlock_path_beneath_attr.allowed_access);
* - %EBADF: @ruleset_fd is not a file descriptor for the current thread, or a
* member of @rule_attr is not a file descriptor as expected;
@@ -306,10 +395,8 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
const enum landlock_rule_type, rule_type,
const void __user *const, rule_attr, const __u32, flags)
{
- struct landlock_path_beneath_attr path_beneath_attr;
- struct path path;
struct landlock_ruleset *ruleset;
- int res, err;
+ int err;
if (!landlock_initialized)
return -EOPNOTSUPP;
@@ -323,48 +410,17 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
if (IS_ERR(ruleset))
return PTR_ERR(ruleset);
- if (rule_type != LANDLOCK_RULE_PATH_BENEATH) {
+ switch (rule_type) {
+ case LANDLOCK_RULE_PATH_BENEATH:
+ err = add_rule_path_beneath(ruleset, rule_attr);
+ break;
+ case LANDLOCK_RULE_NET_PORT:
+ err = add_rule_net_port(ruleset, rule_attr);
+ break;
+ default:
err = -EINVAL;
- goto out_put_ruleset;
- }
-
- /* Copies raw user space buffer, only one type for now. */
- res = copy_from_user(&path_beneath_attr, rule_attr,
- sizeof(path_beneath_attr));
- if (res) {
- err = -EFAULT;
- goto out_put_ruleset;
+ break;
}
-
- /*
- * Informs about useless rule: empty allowed_access (i.e. deny rules)
- * are ignored in path walks.
- */
- if (!path_beneath_attr.allowed_access) {
- err = -ENOMSG;
- goto out_put_ruleset;
- }
- /*
- * Checks that allowed_access matches the @ruleset constraints
- * (ruleset->fs_access_masks[0] is automatically upgraded to 64-bits).
- */
- if ((path_beneath_attr.allowed_access | ruleset->fs_access_masks[0]) !=
- ruleset->fs_access_masks[0]) {
- err = -EINVAL;
- goto out_put_ruleset;
- }
-
- /* Gets and checks the new rule. */
- err = get_path_from_fd(path_beneath_attr.parent_fd, &path);
- if (err)
- goto out_put_ruleset;
-
- /* Imports the new rule. */
- err = landlock_append_fs_rule(ruleset, &path,
- path_beneath_attr.allowed_access);
- path_put(&path);
-
-out_put_ruleset:
landlock_put_ruleset(ruleset);
return err;
}