Landlock implements the `LANDLOCK_RULE_NET_PORT` rule type, which provides fine-grained control of actions for a specific protocol. Any action or protocol that is not supported by this rule can not be controlled. As a result, protocols for which fine-grained control is not supported can be used in a sandboxed system and lead to vulnerabilities or unexpected behavior. Controlling the protocols used will allow to use only those that are necessary for the system and/or which have fine-grained Landlock control through others types of rules (e.g. TCP bind/connect control with `LANDLOCK_RULE_NET_PORT`, UNIX bind control with `LANDLOCK_RULE_PATH_BENEATH`). Consider following examples: * Server may want to use only TCP sockets for which there is fine-grained control of bind(2) and connect(2) actions [1]. * System that does not need a network or that may want to disable network for security reasons (e.g. [2]) can achieve this by restricting the use of all possible protocols. This patch implements such control by restricting socket creation in a sandboxed process. Introduced changes are listed below. Add `LANDLOCK_RULE_SOCKET` rule type that restricts actions on sockets. This rule uses values of protocol family, protocol number and socket type (cf. socket(2)) to determine sockets that should be restricted. This is represented in a struct landlock_socket_attr: struct landlock_socket_attr { __u64 allowed_access; __s32 family; /* same as domain in socket(2) */ __s32 type; /* see socket(2) */ __s32 protocol; }; Add `LANDLOCK_ACCESS_SOCKET_CREATE` access right that corresponds to the creation of user space sockets (eg. by calling socket(2) system call). In the case of connection-based socket types, this does not restrict the actions that result in creation of sockets used for messaging between already existing endpoints (e.g. accept(2), SCTP_SOCKOPT_PEELOFF). Also, this does not restrict any other socket-related actions such as bind(2) or send(2). All restricted actions are enlisted in the documentation of this access right. As with all other access rights, using `LANDLOCK_ACCESS_SOCKET_CREATE` does not affect the actions on sockets which were created before sandboxing. In some cases it may be useful to define a single rule that will enable entire protocol family or all protocols which correspond to specified family and type. Add "wildcard" values for type, protocol fields to make it possible to define rule for a set of protocols. Setting wildcard means that rule allows every socket type or protocol inside protocol family. For example, following rule allows creating sockets of each protocol corresponding to INET family (ie. TCP, SCTP, UDP, ..): struct landlock_socket_attr allow_inet { .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE, .family = AF_INET, .type = -1, .protocol = -1, }; It is possible to create sockets of the same protocol with different protocol number values. For example, TCP sockets can be created using one of the following commands: 1. fd = socket(AF_INET, SOCK_STREAM, 0); 2. fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); Whereas IPPROTO_TCP = 6. Protocol number 0 correspond to the default protocol of the given protocol family and can be mapped to another value. Socket rules do not perform such mappings to not increase complexity of rules definition and their maintenance. Add socket.c file that will contain socket rules management and LSM hooks. Support socket rule storage in landlock ruleset. Acceptable ranges for family and type fields are [0, U8_MAX) and for the protocol field it is [0, U16_MAX). Implement helper pack_socket_key() to convert 32-bit family, type and protocol values into uintptr_t. This is possible due to the fact that family and type values are limited to AF_MAX (= 46), SOCK_MAX (= 11) constants. These assumption is checked in build-time. Protocol value is expected to be less than UINT16_MAX. If user tries to define a rule with values outside ranges, landlock_add_rule returns with -EINVAL. Support socket rules in landlock syscalls. Change ABI version to 8. [1] https://lore.kernel.org/all/ZJvy2SViorgc+cZI@google.com/ [2] https://cr.yp.to/unix/disablenetwork.html Closes: https://github.com/landlock-lsm/linux/issues/6 Signed-off-by: Mikhail Ivanov --- Changes since v3: * Changes ABI version from 6 to 8. * landlock_socket_attr changes: * Supports protocol field. * Supports wildcard values for type, protocol fields. * Changes data type of family and type fields from int to __s32 * Rewrites commit message. * Fixes grammar. * Minor fixes. Changes since v2: * Refactors access_mask for `LANDLOCK_RULE_SOCKET`. * Changes type of 'socket_key.packed' from 'uintptr_t' to 'unsigned int' in order to fix UB in pack_socket_key(). * Accepts (AF_INET, SOCK_PACKET) as an alias for (AF_PACKET, SOCK_PACKET) in landlock_append_socket_rule(). * Fixes documentation. * Rewrites commit message. * Fixes grammar. * Minor fixes. Changes since v1: * Reverts landlock_key.data type from u64 to uinptr_t. * Adds helper to pack domain and type values into uintptr_t. * Denies inserting socket rule with invalid family and type. * Renames 'domain' to 'family' in landlock_socket_attr. * Updates ABI version to 6 since ioctl patches changed it to 5. * Formats code with clang-format. * Minor fixes. --- include/uapi/linux/landlock.h | 60 ++++++++++- security/landlock/Makefile | 2 +- security/landlock/access.h | 3 + security/landlock/limits.h | 4 + security/landlock/ruleset.c | 37 +++++-- security/landlock/ruleset.h | 46 ++++++-- security/landlock/socket.c | 105 +++++++++++++++++++ security/landlock/socket.h | 18 ++++ security/landlock/syscalls.c | 61 ++++++++++- tools/testing/selftests/landlock/base_test.c | 2 +- 10 files changed, 319 insertions(+), 19 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 f030adc462ee..030c96cb5d25 100644 --- a/include/uapi/linux/landlock.h +++ b/include/uapi/linux/landlock.h @@ -45,6 +45,11 @@ struct landlock_ruleset_attr { * flags`_). */ __u64 handled_access_net; + /** + * @handled_access_socket: Bitmask of handled actions performed on sockets + * (cf. `Socket flags`). + */ + __u64 handled_access_socket; /** * @scoped: Bitmask of scopes (cf. `Scope flags`_) * restricting a Landlock domain from accessing outside @@ -140,6 +145,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, }; /** @@ -191,6 +201,33 @@ struct landlock_net_port_attr { __u64 port; }; +/** + * struct landlock_socket_attr - Socket protocol definition + * + * Argument of sys_landlock_add_rule(). + */ +struct landlock_socket_attr { + /** + * @allowed_access: Bitmask of allowed access for a socket protocol + * (cf. `Socket flags`_). + */ + __u64 allowed_access; + /** + * @family: Protocol family used for communication + * (cf. include/linux/socket.h). + */ + __s32 family; + /** + * @type: Socket type (cf. include/linux/net.h) + */ + __s32 type; + /** + * @protocol: Communication protocol specific to protocol family set in + * @family field. + */ + __s32 protocol; +} __attribute__((packed)); + /** * DOC: fs_access * @@ -327,7 +364,7 @@ struct landlock_net_port_attr { * DOC: net_access * * Network flags - * ~~~~~~~~~~~~~~~~ + * ~~~~~~~~~~~~~ * * These flags enable to restrict a sandboxed process to a set of network * actions. @@ -345,6 +382,27 @@ struct landlock_net_port_attr { #define LANDLOCK_ACCESS_NET_CONNECT_TCP (1ULL << 1) /* clang-format on */ +/** + * DOC: socket_access + * + * Socket flags + * ~~~~~~~~~~~~ + * + * These flags restrict actions on sockets for a sandboxed process (e.g. creation + * of a socket via socket(2)). Sockets opened before sandboxing are not subject + * to these restrictions. This is supported since the Landlock ABI version 8. + * + * The following access right applies only to sockets: + * + * - %LANDLOCK_ACCESS_SOCKET_CREATE: Create a user space socket. This access + * right restricts the following operations: :manpage:`socket(2)`, + * :manpage:`socketpair(2)`, ``IORING_OP_SOCKET`` (cf. + * :manpage:`io_uring_enter(2)`). + */ +/* clang-format off */ +#define LANDLOCK_ACCESS_SOCKET_CREATE (1ULL << 0) +/* clang-format on */ + /** * DOC: scope * diff --git a/security/landlock/Makefile b/security/landlock/Makefile index 3160c2bdac1d..89f0d12d3af1 100644 --- a/security/landlock/Makefile +++ b/security/landlock/Makefile @@ -1,7 +1,7 @@ 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/access.h b/security/landlock/access.h index 7961c6630a2d..03ccd6fbfe83 100644 --- a/security/landlock/access.h +++ b/security/landlock/access.h @@ -40,6 +40,8 @@ typedef u16 access_mask_t; 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 all socket access rights can be stored. */ +static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_SOCKET); /* Makes sure all scoped rights can be stored. */ static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_SCOPE); /* Makes sure for_each_set_bit() and for_each_clear_bit() calls are OK. */ @@ -49,6 +51,7 @@ static_assert(sizeof(unsigned long) >= sizeof(access_mask_t)); struct access_masks { access_mask_t fs : LANDLOCK_NUM_ACCESS_FS; access_mask_t net : LANDLOCK_NUM_ACCESS_NET; + access_mask_t socket : LANDLOCK_NUM_ACCESS_SOCKET; access_mask_t scope : LANDLOCK_NUM_SCOPE; }; diff --git a/security/landlock/limits.h b/security/landlock/limits.h index 65b5ff051674..f87f2e8f2644 100644 --- a/security/landlock/limits.h +++ b/security/landlock/limits.h @@ -27,6 +27,10 @@ #define LANDLOCK_MASK_ACCESS_NET ((LANDLOCK_LAST_ACCESS_NET << 1) - 1) #define LANDLOCK_NUM_ACCESS_NET __const_hweight64(LANDLOCK_MASK_ACCESS_NET) +#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_LAST_SCOPE LANDLOCK_SCOPE_SIGNAL #define LANDLOCK_MASK_SCOPE ((LANDLOCK_LAST_SCOPE << 1) - 1) #define LANDLOCK_NUM_SCOPE __const_hweight64(LANDLOCK_MASK_SCOPE) diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c index dfcdc19ea268..a34d2dbe3954 100644 --- a/security/landlock/ruleset.c +++ b/security/landlock/ruleset.c @@ -45,6 +45,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; /* @@ -55,15 +56,15 @@ static struct landlock_ruleset *create_ruleset(const u32 num_layers) return new_ruleset; } -struct landlock_ruleset * -landlock_create_ruleset(const access_mask_t fs_access_mask, - const access_mask_t net_access_mask, - const access_mask_t scope_mask) +struct landlock_ruleset *landlock_create_ruleset( + const access_mask_t fs_access_mask, const access_mask_t net_access_mask, + const access_mask_t socket_access_mask, const access_mask_t scope_mask) { struct landlock_ruleset *new_ruleset; /* Informs about useless ruleset. */ - if (!fs_access_mask && !net_access_mask && !scope_mask) + if (!fs_access_mask && !net_access_mask && !socket_access_mask && + !scope_mask) return ERR_PTR(-ENOMSG); new_ruleset = create_ruleset(1); if (IS_ERR(new_ruleset)) @@ -72,6 +73,9 @@ 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); if (scope_mask) landlock_add_scope_mask(new_ruleset, scope_mask, 0); return new_ruleset; @@ -101,6 +105,8 @@ 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; @@ -158,6 +164,8 @@ 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); @@ -396,6 +404,9 @@ static int merge_ruleset(struct landlock_ruleset *const dst, goto out_unlock; #endif /* IS_ENABLED(CONFIG_INET) */ + err = merge_tree(dst, src, LANDLOCK_KEY_SOCKET); + if (err) + goto out_unlock; out_unlock: mutex_unlock(&src->lock); mutex_unlock(&dst->lock); @@ -459,6 +470,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; @@ -495,6 +511,9 @@ 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); landlock_put_hierarchy(ruleset->hierarchy); kfree(ruleset); } @@ -679,8 +698,8 @@ get_access_mask_t(const struct landlock_ruleset *const ruleset, * * @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. + * @layer_masks: It must contain %LANDLOCK_NUM_ACCESS_{FS,NET,SOCKET} + * 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 @@ -709,6 +728,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 1a78cba662b2..a60ede2fc2a5 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -66,6 +66,11 @@ 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, }; /** @@ -135,6 +140,14 @@ 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 protocol definition. 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. @@ -173,8 +186,10 @@ struct landlock_ruleset { */ u32 num_layers; /** - * @access_masks: Contains the subset of filesystem and - * network actions that are restricted by a ruleset. + * @access_masks: Contains the subset of filesystem, + * network and socket 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 @@ -189,10 +204,9 @@ 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 scope_mask); +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_socket, const access_mask_t scope_mask); void landlock_put_ruleset(struct landlock_ruleset *const ruleset); void landlock_put_ruleset_deferred(struct landlock_ruleset *const ruleset); @@ -267,6 +281,19 @@ landlock_add_net_access_mask(struct landlock_ruleset *const ruleset, ruleset->access_masks[layer_level].net |= net_mask; } +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 |= socket_mask; +} + static inline void landlock_add_scope_mask(struct landlock_ruleset *const ruleset, const access_mask_t scope_mask, const u16 layer_level) @@ -294,6 +321,13 @@ landlock_get_net_access_mask(const struct landlock_ruleset *const ruleset, return ruleset->access_masks[layer_level].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].socket; +} + static inline access_mask_t landlock_get_scope_mask(const struct landlock_ruleset *const ruleset, const u16 layer_level) diff --git a/security/landlock/socket.c b/security/landlock/socket.c new file mode 100644 index 000000000000..28a80dcad629 --- /dev/null +++ b/security/landlock/socket.c @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Landlock LSM - Socket management and hooks + * + * Copyright © 2025 Huawei Tech. Co., Ltd. + */ + +#include +#include +#include +#include + +#include "limits.h" +#include "ruleset.h" +#include "socket.h" +#include "cred.h" + +#define TYPE_ALL (-1) +#define PROTOCOL_ALL (-1) + +static int pack_socket_key(const s32 family, const s32 type, const s32 protocol, + uintptr_t *val) +{ + int err = -EINVAL; + union { + struct { + u8 family; + u8 type; + u16 protocol; + } __packed data; + u32 packed; + } socket_key; + + /* Checks that socket_key content can be stored in struct landlock_key. */ + BUILD_BUG_ON(sizeof(socket_key.data) > sizeof(socket_key.packed)); + BUILD_BUG_ON(sizeof(socket_key.packed) > + sizeof_field(union landlock_key, data)); + + /* + * Checks that all supported protocol families and socket types can be + * stored in socket_key fields. + */ + BUILD_BUG_ON(AF_MAX - 1 > U8_MAX); + BUILD_BUG_ON(SOCK_MAX - 1 > U8_MAX); + + /* Checks ranges and handles wildcard type and protocol value mapping. */ + if (family >= 0 && family < U8_MAX) + socket_key.data.family = family; + else + goto out; + + BUILD_BUG_ON(TYPE_ALL != -1); + if (type == TYPE_ALL) + socket_key.data.type = U8_MAX; + else if (type >= 0 && type < U8_MAX) + socket_key.data.type = type; + else + goto out; + + BUILD_BUG_ON(PROTOCOL_ALL != -1); + if (protocol == PROTOCOL_ALL) + socket_key.data.protocol = U16_MAX; + else if (protocol >= 0 && protocol < U16_MAX) + socket_key.data.protocol = protocol; + else + goto out; + + *val = socket_key.packed; + err = 0; +out: + return err; +} + +int landlock_append_socket_rule(struct landlock_ruleset *const ruleset, + s32 family, s32 type, s32 protocol, + access_mask_t access_rights) +{ + int err; + uintptr_t key; + /* + * (AF_INET, SOCK_PACKET) is an alias for (AF_PACKET, SOCK_PACKET) + * (cf. __sock_create). + */ + if (family == AF_INET && type == SOCK_PACKET) + family = AF_PACKET; + + err = pack_socket_key(family, type, protocol, &key); + if (err) + return err; + + const struct landlock_id id = { + .key.data = key, + .type = LANDLOCK_KEY_SOCKET, + }; + + /* Transforms relative access rights to absolute ones. */ + access_rights |= LANDLOCK_MASK_ACCESS_SOCKET & + ~landlock_get_socket_access_mask(ruleset, 0); + + mutex_lock(&ruleset->lock); + err = landlock_insert_rule(ruleset, id, access_rights); + mutex_unlock(&ruleset->lock); + + return err; +} diff --git a/security/landlock/socket.h b/security/landlock/socket.h new file mode 100644 index 000000000000..bd0ac74c39e2 --- /dev/null +++ b/security/landlock/socket.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Landlock LSM - Socket management and hooks + * + * Copyright © 2025 Huawei Tech. Co., Ltd. + */ + +#ifndef _SECURITY_LANDLOCK_SOCKET_H +#define _SECURITY_LANDLOCK_SOCKET_H + +#include "ruleset.h" + +int landlock_append_socket_rule(struct landlock_ruleset *const ruleset, + const s32 family, const s32 type, + const s32 protocol, + access_mask_t access_rights); + +#endif /* _SECURITY_LANDLOCK_SOCKET_H */ diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index 33eafb71e4f3..e9f500f97c86 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include "cred.h" @@ -34,6 +35,7 @@ #include "fs.h" #include "limits.h" #include "net.h" +#include "socket.h" #include "ruleset.h" #include "setup.h" @@ -92,7 +94,8 @@ static void build_check_abi(void) struct landlock_ruleset_attr ruleset_attr; struct landlock_path_beneath_attr path_beneath_attr; struct landlock_net_port_attr net_port_attr; - size_t ruleset_size, path_beneath_size, net_port_size; + struct landlock_socket_attr socket_attr; + size_t ruleset_size, path_beneath_size, net_port_size, socket_size; /* * For each user space ABI structures, first checks that there is no @@ -101,9 +104,10 @@ static void build_check_abi(void) */ ruleset_size = sizeof(ruleset_attr.handled_access_fs); ruleset_size += sizeof(ruleset_attr.handled_access_net); + ruleset_size += sizeof(ruleset_attr.handled_access_socket); ruleset_size += sizeof(ruleset_attr.scoped); BUILD_BUG_ON(sizeof(ruleset_attr) != ruleset_size); - BUILD_BUG_ON(sizeof(ruleset_attr) != 24); + BUILD_BUG_ON(sizeof(ruleset_attr) != 32); path_beneath_size = sizeof(path_beneath_attr.allowed_access); path_beneath_size += sizeof(path_beneath_attr.parent_fd); @@ -114,6 +118,13 @@ static void build_check_abi(void) 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); + + socket_size = sizeof(socket_attr.allowed_access); + socket_size += sizeof(socket_attr.family); + socket_size += sizeof(socket_attr.type); + socket_size += sizeof(socket_attr.protocol); + BUILD_BUG_ON(sizeof(socket_attr) != socket_size); + BUILD_BUG_ON(sizeof(socket_attr) != 20); } /* Ruleset handling */ @@ -161,7 +172,7 @@ static const struct file_operations ruleset_fops = { * Documentation/userspace-api/landlock.rst should be updated to reflect the * UAPI change. */ -const int landlock_abi_version = 7; +const int landlock_abi_version = 8; /** * sys_landlock_create_ruleset - Create a new ruleset @@ -237,6 +248,11 @@ SYSCALL_DEFINE3(landlock_create_ruleset, LANDLOCK_MASK_ACCESS_NET) return -EINVAL; + /* Checks socket content (and 32-bits cast). */ + if ((ruleset_attr.handled_access_socket | + LANDLOCK_MASK_ACCESS_SOCKET) != LANDLOCK_MASK_ACCESS_SOCKET) + return -EINVAL; + /* Checks IPC scoping content (and 32-bits cast). */ if ((ruleset_attr.scoped | LANDLOCK_MASK_SCOPE) != LANDLOCK_MASK_SCOPE) return -EINVAL; @@ -244,6 +260,7 @@ SYSCALL_DEFINE3(landlock_create_ruleset, /* Checks arguments and transforms to kernel struct. */ ruleset = landlock_create_ruleset(ruleset_attr.handled_access_fs, ruleset_attr.handled_access_net, + ruleset_attr.handled_access_socket, ruleset_attr.scoped); if (IS_ERR(ruleset)) return PTR_ERR(ruleset); @@ -383,6 +400,40 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset, net_port_attr.allowed_access); } +static int add_rule_socket(struct landlock_ruleset *ruleset, + const void __user *const rule_attr) +{ + struct landlock_socket_attr socket_attr; + int res; + access_mask_t mask; + s32 family, type, protocol; + + /* Copies raw user space buffer. */ + res = copy_from_user(&socket_attr, rule_attr, sizeof(socket_attr)); + if (res) + return -EFAULT; + + /* + * Informs about useless rule: empty allowed_access (i.e. deny rules) + * are ignored by socket actions. + */ + if (!socket_attr.allowed_access) + return -ENOMSG; + + /* Checks that allowed_access matches the @ruleset constraints. */ + mask = landlock_get_socket_access_mask(ruleset, 0); + if ((socket_attr.allowed_access | mask) != mask) + return -EINVAL; + + family = socket_attr.family; + type = socket_attr.type; + protocol = socket_attr.protocol; + + /* Imports the new rule. */ + return landlock_append_socket_rule(ruleset, family, type, protocol, + socket_attr.allowed_access); +} + /** * sys_landlock_add_rule - Add a new rule to a ruleset * @@ -407,6 +458,8 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset, * &landlock_net_port_attr.allowed_access is not a subset of the ruleset * handled accesses) * - %EINVAL: &landlock_net_port_attr.port is greater than 65535; + * - %EINVAL: &landlock_socket_attr.{family, type} are greater than 254 or + * &landlock_socket_attr.protocol is greater than 65534; * - %ENOMSG: Empty accesses (e.g. &landlock_path_beneath_attr.allowed_access is * 0); * - %EBADF: @ruleset_fd is not a file descriptor for the current thread, or a @@ -439,6 +492,8 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd, return add_rule_path_beneath(ruleset, rule_attr); case LANDLOCK_RULE_NET_PORT: return add_rule_net_port(ruleset, rule_attr); + case LANDLOCK_RULE_SOCKET: + return add_rule_socket(ruleset, rule_attr); default: return -EINVAL; } diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c index 7b69002239d7..f4b1a275d8d9 100644 --- a/tools/testing/selftests/landlock/base_test.c +++ b/tools/testing/selftests/landlock/base_test.c @@ -76,7 +76,7 @@ TEST(abi_version) const struct landlock_ruleset_attr ruleset_attr = { .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, }; - ASSERT_EQ(7, landlock_create_ruleset(NULL, 0, + ASSERT_EQ(8, landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION)); ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0, -- 2.34.1 Add test that validates behaviour of Landlock after ruleset with unknown access is created. Reviewed-by: Günther Noack Signed-off-by: Mikhail Ivanov --- Changes since v3: * Adds fixture `mini`. Socket creation should be tested with capabilities disabled. Changes since v2: * Removes fixture `mini`. Network namespace is not used, so this fixture has become useless. * Changes commit title and message. Changes since v1: * Refactors commit message. --- .../testing/selftests/landlock/socket_test.c | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 tools/testing/selftests/landlock/socket_test.c diff --git a/tools/testing/selftests/landlock/socket_test.c b/tools/testing/selftests/landlock/socket_test.c new file mode 100644 index 000000000000..d5716149d03f --- /dev/null +++ b/tools/testing/selftests/landlock/socket_test.c @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Landlock tests - Socket + * + * Copyright © 2025 Huawei Tech. Co., Ltd. + */ + +#define _GNU_SOURCE + +#include +#include + +#include "common.h" + +#define ACCESS_LAST LANDLOCK_ACCESS_SOCKET_CREATE +#define ACCESS_ALL LANDLOCK_ACCESS_SOCKET_CREATE + +/* clang-format off */ +FIXTURE(mini) {}; +/* clang-format on */ + +FIXTURE_SETUP(mini) +{ + disable_caps(_metadata); +}; + +FIXTURE_TEARDOWN(mini) +{ +} + +TEST_F(mini, ruleset_with_unknown_access) +{ + __u64 access_mask; + + for (access_mask = 1ULL << 63; access_mask != ACCESS_LAST; + access_mask >>= 1) { + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_socket = access_mask, + }; + + ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, + sizeof(ruleset_attr), 0)); + ASSERT_EQ(EINVAL, errno); + } +} + +TEST_HARNESS_MAIN -- 2.34.1 Add test that validates behaviour of Landlock after rule of LANDLOCK_RULE_SOCKET type is added * with all possible access rights; * with unknown access rights; * with unhandled access rights; * with empty access. Signed-off-by: Mikhail Ivanov --- Changes since v3: * Squashes commits related to checking rule adding * Removes "protocol" fixture dependency. Use single TCP protocol for testing instead. * Fixes semantics of test "rule_with_unhandled_access". Changes since v2: * Replaces EXPECT_EQ with ASSERT_EQ for close(). * Refactors commit message and title. Changes since v1: * Formats code with clang-format. * Refactors commit message. --- .../testing/selftests/landlock/socket_test.c | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/tools/testing/selftests/landlock/socket_test.c b/tools/testing/selftests/landlock/socket_test.c index d5716149d03f..fcc0f92075a7 100644 --- a/tools/testing/selftests/landlock/socket_test.c +++ b/tools/testing/selftests/landlock/socket_test.c @@ -44,4 +44,111 @@ TEST_F(mini, ruleset_with_unknown_access) } } +TEST_F(mini, rule_with_supported_access) +{ + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_socket = ACCESS_ALL, + }; + struct landlock_socket_attr protocol = { + .family = AF_INET, + .type = SOCK_STREAM, + .protocol = 0, + }; + int ruleset_fd; + __u64 access; + + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + for (access = 1; access <= ACCESS_LAST; access <<= 1) { + protocol.allowed_access = access; + EXPECT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_SOCKET, + &protocol, 0)) + { + TH_LOG("Failed to add rule with access 0x%llx: %s", + access, strerror(errno)); + } + } + ASSERT_EQ(0, close(ruleset_fd)); +} + +TEST_F(mini, rule_with_unknown_access) +{ + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_socket = ACCESS_ALL, + }; + struct landlock_socket_attr protocol = { .family = AF_INET, + .type = SOCK_STREAM, + .protocol = 0 }; + int ruleset_fd; + __u64 access; + + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + for (access = 1ULL << 63; access != ACCESS_LAST; access >>= 1) { + protocol.allowed_access = access; + EXPECT_EQ(-1, + landlock_add_rule(ruleset_fd, LANDLOCK_RULE_SOCKET, + &protocol, 0)); + EXPECT_EQ(EINVAL, errno); + } + ASSERT_EQ(0, close(ruleset_fd)); +} + +TEST_F(mini, rule_with_unhandled_access) +{ + /* Prepares ruleset that handles network access instead of socket access. */ + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP, + }; + struct landlock_socket_attr protocol = { .family = AF_UNIX, + .type = SOCK_STREAM, + .protocol = 0 }; + int ruleset_fd; + __u64 access; + + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + for (access = ACCESS_LAST; access > 0; access <<= 1) { + int err; + + protocol.allowed_access = access; + err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_SOCKET, + &protocol, 0); + EXPECT_EQ(-1, err); + EXPECT_EQ(EINVAL, errno); + } + + ASSERT_EQ(0, close(ruleset_fd)); +} + +TEST_F(mini, rule_with_empty_access) +{ + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_socket = LANDLOCK_ACCESS_SOCKET_CREATE + }; + const struct landlock_socket_attr protocol_denied = { + .allowed_access = 0, + .family = AF_UNIX, + .type = SOCK_STREAM, + .protocol = 0, + }; + int ruleset_fd; + + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + /* Checks zero access value. */ + EXPECT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_SOCKET, + &protocol_denied, 0)); + EXPECT_EQ(ENOMSG, errno); + ASSERT_EQ(0, close(ruleset_fd)); +} + TEST_HARNESS_MAIN -- 2.34.1 Add test that validates behaviour of Landlock when rule with wildcard value is added. Signed-off-by: Mikhail Ivanov --- .../testing/selftests/landlock/socket_test.c | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tools/testing/selftests/landlock/socket_test.c b/tools/testing/selftests/landlock/socket_test.c index fcc0f92075a7..abcef11aaf39 100644 --- a/tools/testing/selftests/landlock/socket_test.c +++ b/tools/testing/selftests/landlock/socket_test.c @@ -151,4 +151,43 @@ TEST_F(mini, rule_with_empty_access) ASSERT_EQ(0, close(ruleset_fd)); } +/* Validates landlock behaviour when using wildcard (-1) values in socket rules. */ +TEST_F(mini, rule_with_wildcard) +{ + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_socket = LANDLOCK_ACCESS_SOCKET_CREATE, + }; + const struct landlock_socket_attr create_family_attr = { + .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE, + .family = AF_INET, + .type = -1, + .protocol = -1, + }; + const struct landlock_socket_attr create_family_type_attr = { + .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE, + .family = AF_INET, + .type = SOCK_STREAM, + .protocol = -1, + }; + const struct landlock_socket_attr create_family_protocol_attr = { + .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE, + .family = AF_INET, + .type = -1, + .protocol = 0, + }; + int ruleset_fd; + + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + EXPECT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_SOCKET, + &create_family_attr, 0)); + EXPECT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_SOCKET, + &create_family_type_attr, 0)); + EXPECT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_SOCKET, + &create_family_protocol_attr, 0)); + ASSERT_EQ(0, close(ruleset_fd)); +} + TEST_HARNESS_MAIN -- 2.34.1 Create fixture "protocol_inside_range" and "protocol_outside_range" examining acceptable limits of family, type and protocol values supported by Landlock ruleset. Add test validating Landlock behaviour of adding rule with values corresponding to the limits of the acceptable range and with values beyond the acceptable ranges. Signed-off-by: Mikhail Ivanov --- .../testing/selftests/landlock/socket_test.c | 189 ++++++++++++++++++ 1 file changed, 189 insertions(+) diff --git a/tools/testing/selftests/landlock/socket_test.c b/tools/testing/selftests/landlock/socket_test.c index abcef11aaf39..16477614dfed 100644 --- a/tools/testing/selftests/landlock/socket_test.c +++ b/tools/testing/selftests/landlock/socket_test.c @@ -190,4 +190,193 @@ TEST_F(mini, rule_with_wildcard) ASSERT_EQ(0, close(ruleset_fd)); } +/* clang-format off */ +FIXTURE(prot_inside_range) {}; +/* clang-format on */ + +FIXTURE_VARIANT(prot_inside_range) +{ + int family, type, protocol; +}; + +FIXTURE_SETUP(prot_inside_range) +{ + disable_caps(_metadata); +}; + +FIXTURE_TEARDOWN(prot_inside_range) +{ +} + +/* clang-format off */ +FIXTURE_VARIANT_ADD(prot_inside_range, family_upper) { + /* clang-format on */ + .family = UINT8_MAX - 1, + .type = SOCK_STREAM, + .protocol = 0, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(prot_inside_range, type_upper) { + /* clang-format on */ + .family = AF_INET, + .type = UINT8_MAX - 1, + .protocol = 0, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(prot_inside_range, protocol_upper) { + /* clang-format on */ + .family = AF_INET, + .type = SOCK_STREAM, + .protocol = UINT16_MAX - 1, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(prot_inside_range, family_lower) { + /* clang-format on */ + .family = 0, + .type = SOCK_STREAM, + .protocol = 0, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(prot_inside_range, type_lower) { + /* clang-format on */ + .family = AF_INET, + .type = 0, + .protocol = 0, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(prot_inside_range, protocol_lower) { + /* clang-format on */ + .family = AF_INET, + .type = SOCK_STREAM, + .protocol = 0, +}; + +/* + * Verifies acceptable range of family, type and protocol values. Specific + * case of adding rule with masked fields checked in "rule_with_wildcard" + * test. + * + * Acceptable ranges are [0, UINT8_MAX) for family and type, + * [0, UINT16_MAX) for protocol field. + */ +TEST_F(prot_inside_range, add_rule) +{ + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_socket = LANDLOCK_ACCESS_SOCKET_CREATE, + }; + const struct landlock_socket_attr create_socket_attr = { + .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE, + .family = variant->family, + .type = variant->type, + .protocol = variant->protocol, + }; + int ruleset_fd; + + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + EXPECT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_SOCKET, + &create_socket_attr, 0)); + ASSERT_EQ(0, close(ruleset_fd)); +} + +/* clang-format off */ +FIXTURE(prot_outside_range) {}; +/* clang-format on */ + +FIXTURE_VARIANT(prot_outside_range) +{ + int family, type, protocol; +}; + +FIXTURE_SETUP(prot_outside_range) +{ + disable_caps(_metadata); +}; + +FIXTURE_TEARDOWN(prot_outside_range) +{ +} + +/* clang-format off */ +FIXTURE_VARIANT_ADD(prot_outside_range, family_upper) { + /* clang-format on */ + .family = UINT8_MAX, + .type = SOCK_STREAM, + .protocol = 0, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(prot_outside_range, type_upper) { + /* clang-format on */ + .family = AF_INET, + .type = UINT8_MAX, + .protocol = 0, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(prot_outside_range, protocol_upper) { + /* clang-format on */ + .family = AF_INET, + .type = SOCK_STREAM, + .protocol = UINT16_MAX, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(prot_outside_range, family_lower) { + /* clang-format on */ + .family = -1, + .type = SOCK_STREAM, + .protocol = 0, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(prot_outside_range, type_lower) { + /* clang-format on */ + .family = AF_INET, + .type = -2, + .protocol = 0, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(prot_outside_range, protocol_lower) { + /* clang-format on */ + .family = AF_INET, + .type = SOCK_STREAM, + .protocol = -2, +}; + +/* + * Acceptable ranges are [0, UINT8_MAX) for family and type, + * [0, UINT16_MAX) for protocol field. Also type and protocol + * can be set with specific -1 wildcard value. + */ +TEST_F(prot_outside_range, add_rule) +{ + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_socket = LANDLOCK_ACCESS_SOCKET_CREATE, + }; + const struct landlock_socket_attr create_socket_attr = { + .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE, + .family = variant->family, + .type = variant->type, + .protocol = variant->protocol, + }; + int ruleset_fd; + + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + EXPECT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_SOCKET, + &create_socket_attr, 0)); + ASSERT_EQ(0, close(ruleset_fd)); +} + TEST_HARNESS_MAIN -- 2.34.1 Add hook on security_socket_create(), which checks whether the socket of requested protocol is allowed by domain. Due to support of masked protocols Landlock tries to find one of the 4 rules that can allow creation of requested protocol. Signed-off-by: Mikhail Ivanov --- Changes since v3: * Changes LSM hook from socket_post_create to socket_create so creation would be blocked before socket allocation and initialization. * Uses credential instead of domain in hook_socket create. * Removes get_raw_handled_socket_accesses. * Adds checks for rules with wildcard type and protocol values. * Minor refactoring, fixes. Changes since v2: * Adds check in `hook_socket_create()` to not restrict kernel space sockets. * Inlines `current_check_access_socket()` in the `hook_socket_create()`. * Fixes commit message. Changes since v1: * Uses lsm hook arguments instead of struct socket fields as family-type values. * Packs socket family and type using helper. * Fixes commit message. * Formats with clang-format. --- security/landlock/setup.c | 2 + security/landlock/socket.c | 78 ++++++++++++++++++++++++++++++++++++++ security/landlock/socket.h | 2 + 3 files changed, 82 insertions(+) diff --git a/security/landlock/setup.c b/security/landlock/setup.c index bd53c7a56ab9..140a53b022f7 100644 --- a/security/landlock/setup.c +++ b/security/landlock/setup.c @@ -17,6 +17,7 @@ #include "fs.h" #include "id.h" #include "net.h" +#include "socket.h" #include "setup.h" #include "task.h" @@ -68,6 +69,7 @@ static int __init landlock_init(void) landlock_add_task_hooks(); landlock_add_fs_hooks(); landlock_add_net_hooks(); + landlock_add_socket_hooks(); landlock_init_id(); landlock_initialized = true; pr_info("Up and running.\n"); diff --git a/security/landlock/socket.c b/security/landlock/socket.c index 28a80dcad629..d7e6e7b92b7a 100644 --- a/security/landlock/socket.c +++ b/security/landlock/socket.c @@ -103,3 +103,81 @@ int landlock_append_socket_rule(struct landlock_ruleset *const ruleset, return err; } + +static int check_socket_access(const struct landlock_ruleset *dom, + uintptr_t key, + layer_mask_t (*const layer_masks)[], + access_mask_t handled_access) +{ + const struct landlock_rule *rule; + struct landlock_id id = { + .type = LANDLOCK_KEY_SOCKET, + }; + + id.key.data = key; + rule = landlock_find_rule(dom, id); + if (landlock_unmask_layers(rule, handled_access, layer_masks, + LANDLOCK_NUM_ACCESS_SOCKET)) + return 0; + return -EACCES; +} + +static int hook_socket_create(int family, int type, int protocol, int kern) +{ + layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_SOCKET] = {}; + access_mask_t handled_access; + const struct access_masks masks = { + .socket = LANDLOCK_ACCESS_SOCKET_CREATE, + }; + const struct landlock_cred_security *const subject = + landlock_get_applicable_subject(current_cred(), masks, NULL); + uintptr_t key; + + if (!subject) + return 0; + /* Checks only user space sockets. */ + if (kern) + return 0; + + handled_access = landlock_init_layer_masks( + subject->domain, LANDLOCK_ACCESS_SOCKET_CREATE, &layer_masks, + LANDLOCK_KEY_SOCKET); + /* + * Error could happen due to parameters are outside of the allowed range, + * so this combination couldn't be added in ruleset previously. + * Therefore, it's not permitted. + */ + if (pack_socket_key(family, type, protocol, &key) == -EACCES) + return -EACCES; + if (check_socket_access(subject->domain, key, &layer_masks, + handled_access) == 0) + return 0; + + /* Ranges were already checked. */ + (void)pack_socket_key(family, TYPE_ALL, protocol, &key); + if (check_socket_access(subject->domain, key, &layer_masks, + handled_access) == 0) + return 0; + + (void)pack_socket_key(family, type, PROTOCOL_ALL, &key); + if (check_socket_access(subject->domain, key, &layer_masks, + handled_access) == 0) + return 0; + + (void)pack_socket_key(family, TYPE_ALL, PROTOCOL_ALL, &key); + if (check_socket_access(subject->domain, key, &layer_masks, + handled_access) == 0) + return 0; + + return -EACCES; +} + +static struct security_hook_list landlock_hooks[] __ro_after_init = { + LSM_HOOK_INIT(socket_create, hook_socket_create), +}; + +__init void landlock_add_socket_hooks(void) +{ + security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks), + &landlock_lsmid); +} diff --git a/security/landlock/socket.h b/security/landlock/socket.h index bd0ac74c39e2..3980a3d46534 100644 --- a/security/landlock/socket.h +++ b/security/landlock/socket.h @@ -15,4 +15,6 @@ int landlock_append_socket_rule(struct landlock_ruleset *const ruleset, const s32 protocol, access_mask_t access_rights); +__init void landlock_add_socket_hooks(void); + #endif /* _SECURITY_LANDLOCK_SOCKET_H */ -- 2.34.1 Add `protocol` fixture to test biggest part of communication protocol variants. Add config options required to create sockets for each of these protocols. Support CAP_NET_RAW capability which is required by some of the tested protocols. Add protocols_define.h file containing definitions of tested protocols. Add test that validates Landlock ability to control creation of sockets via socket(2) syscall. Signed-off-by: Mikhail Ivanov --- Changes since v3: * Removes some of the protocols from testing (SMC, KMC and XDP). * Rewrites commit description. * Changes test name to "restrict_socket_creation". * Minor changes. Changes since v2: * Extends variants of `protocol` fixture with every socket protocol that can be used to create user space sockets. * Adds `SYS_ADMIN`, `NET_ADMIN` and `NET_RAW` capabilities required for some socket protocols. * Removes network namespace creation in `protocol` fixture setup. Sockets of some protocols can be created only in initial network namespace. This shouldn't cause any issues until `protocol` fixture is used in connection or binding tests. * Extends config file with a set of options required by socket protocols. * Adds CAP_NET_RAW capability to landlock selftests which is required to create sockets of some protocols. * Adds protocol field to the `protocol` fixture. * Adds test_socket_variant() helper and changes the signature of test_socket() helper. * Checks socket(2) when ruleset is not established. * Removes checks for AF_UNSPEC. This is moved to unsupported_af_and_prot test. * Removes `service_fixture` struct. * Minor fixes. * Refactors commit message and title. Changes since v1: * Replaces test_socket_create() and socket_variant() helpers with test_socket(). * Renames domain to family in protocol fixture. * Remove AF_UNSPEC fixture entry and add unspec_srv0 fixture field to check AF_UNSPEC socket creation case. * Formats code with clang-format. * Refactors commit message. --- tools/testing/selftests/landlock/common.h | 1 + tools/testing/selftests/landlock/config | 47 +++++ .../selftests/landlock/protocols_define.h | 169 ++++++++++++++++++ .../testing/selftests/landlock/socket_test.c | 132 ++++++++++++++ 4 files changed, 349 insertions(+) create mode 100644 tools/testing/selftests/landlock/protocols_define.h diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h index 88a3c78f5d98..e9378a229a4c 100644 --- a/tools/testing/selftests/landlock/common.h +++ b/tools/testing/selftests/landlock/common.h @@ -47,6 +47,7 @@ static void _init_caps(struct __test_metadata *const _metadata, bool drop_all) CAP_SETUID, CAP_SYS_ADMIN, CAP_SYS_CHROOT, + CAP_NET_RAW, /* clang-format on */ }; const unsigned int noroot = SECBIT_NOROOT | SECBIT_NOROOT_LOCKED; diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config index 8fe9b461b1fd..98b3996c36a8 100644 --- a/tools/testing/selftests/landlock/config +++ b/tools/testing/selftests/landlock/config @@ -17,3 +17,50 @@ CONFIG_SHMEM=y CONFIG_SYSFS=y CONFIG_TMPFS=y CONFIG_TMPFS_XATTR=y + +# +# Support of socket protocols for socket_test +# +CONFIG_AF_RXRPC=y +CONFIG_ATALK=y +CONFIG_ATM=y +CONFIG_AX25=y +CONFIG_BT=y +CONFIG_CAIF=y +CONFIG_CAN_BCM=y +CONFIG_CAN=y +CONFIG_CRYPTO_USER_API_AEAD=y +CONFIG_CRYPTO=y +CONFIG_HAMRADIO=y +CONFIG_IEEE802154_SOCKET=y +CONFIG_IEEE802154=y +CONFIG_INET=y +CONFIG_INFINIBAND=y +CONFIG_IP_SCTP=y +CONFIG_ISDN=y +CONFIG_LLC2=y +CONFIG_LLC=y +CONFIG_MPTCP=y +CONFIG_MPTCP_IPV6=y +CONFIG_MCTP=y +CONFIG_MISDN=y +CONFIG_NETDEVICES=y +CONFIG_NET_KEY=y +CONFIG_NETROM=y +CONFIG_NFC=y +CONFIG_PACKET=y +CONFIG_PCI=y +CONFIG_PHONET=y +CONFIG_PPPOE=y +CONFIG_PPP=y +CONFIG_QRTR=y +CONFIG_RDS=y +CONFIG_ROSE=y +CONFIG_SMC=y +CONFIG_TIPC=y +CONFIG_UNIX=y +CONFIG_VMWARE_VMCI_VSOCKETS=y +CONFIG_VMWARE_VMCI=y +CONFIG_VSOCKETS=y +CONFIG_X25=y +CONFIG_XDP_SOCKETS=y diff --git a/tools/testing/selftests/landlock/protocols_define.h b/tools/testing/selftests/landlock/protocols_define.h new file mode 100644 index 000000000000..e44d2278d289 --- /dev/null +++ b/tools/testing/selftests/landlock/protocols_define.h @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Landlock tests - Tested socket protocols definitions + * + * Copyright © 2025 Huawei Tech. Co., Ltd. + */ + +/* Almost every protocol that can be used to create socket using create() method + * of net_proto_family structure is tested (e.g. this method is used to + * create socket with socket(2)). + * + * List of address families that are not tested: + * - Protocol families requiring CAP_SYS_ADMIN or CAP_NET_RAW (eg. AF_PACKET, + * AF_CAIF). + * - AF_SMC, AF_KMC, AF_XDP. + * - AF_ASH, AF_SNA, AF_WANPIPE, AF_NETBEUI, AF_IPX, AF_DECNET, AF_ECONET + * and AF_IRDA are not implemented in kernel. + * - AF_BRIDGE, AF_MPLS can't be used for creating sockets. + * - AF_SECURITY - pseudo AF (Cf. socket.h). + * - AF_IB is reserved by infiniband. + */ + +/* Cf. unix_create */ +PROTOCOL_VARIANT_ADD(UNIX, STREAM, 0); +PROTOCOL_VARIANT_ADD(UNIX, RAW, 0); +PROTOCOL_VARIANT_ADD(UNIX, DGRAM, 0); +PROTOCOL_VARIANT_ADD(UNIX, SEQPACKET, 0); + +/* Cf. inet_create */ +PROTOCOL_VARIANT_ADD(INET, STREAM, 0); +PROTOCOL_VARIANT_ADD(INET, STREAM, IPPROTO_TCP); +PROTOCOL_VARIANT_ADD(INET, DGRAM, 0); +PROTOCOL_VARIANT_ADD(INET, DGRAM, IPPROTO_UDP); +PROTOCOL_VARIANT_ADD_CAPS(INET, RAW, IPPROTO_TCP); +PROTOCOL_VARIANT_ADD(INET, SEQPACKET, IPPROTO_SCTP); +PROTOCOL_VARIANT_ADD(INET, STREAM, IPPROTO_MPTCP); + +/* Cf. ax25_create */ +PROTOCOL_VARIANT_ADD(AX25, DGRAM, 0); +PROTOCOL_VARIANT_ADD(AX25, SEQPACKET, 0); +PROTOCOL_VARIANT_ADD_CAPS(AX25, RAW, 0); + +/* Cf. atalk_create */ +PROTOCOL_VARIANT_ADD_CAPS(APPLETALK, RAW, 0); +PROTOCOL_VARIANT_ADD(APPLETALK, DGRAM, 0); + +/* Cf. nr_create */ +PROTOCOL_VARIANT_ADD(NETROM, SEQPACKET, 0); + +/* Cf. pvc_create */ +PROTOCOL_VARIANT_ADD(ATMPVC, DGRAM, 0); +PROTOCOL_VARIANT_ADD(ATMPVC, RAW, 0); +PROTOCOL_VARIANT_ADD(ATMPVC, RDM, 0); +PROTOCOL_VARIANT_ADD(ATMPVC, SEQPACKET, 0); +PROTOCOL_VARIANT_ADD(ATMPVC, DCCP, 0); +PROTOCOL_VARIANT_ADD(ATMPVC, PACKET, 0); + +/* Cf. x25_create */ +PROTOCOL_VARIANT_ADD(X25, SEQPACKET, 0); + +/* Cf. inet6_create */ +PROTOCOL_VARIANT_ADD(INET6, STREAM, 0); +PROTOCOL_VARIANT_ADD(INET6, STREAM, IPPROTO_TCP); +PROTOCOL_VARIANT_ADD(INET6, DGRAM, 0); +PROTOCOL_VARIANT_ADD(INET6, DGRAM, IPPROTO_UDP); +PROTOCOL_VARIANT_ADD_CAPS(INET6, RAW, IPPROTO_TCP); +PROTOCOL_VARIANT_ADD(INET6, SEQPACKET, IPPROTO_SCTP); +PROTOCOL_VARIANT_ADD(INET6, STREAM, IPPROTO_MPTCP); + +/* Cf. rose_create */ +PROTOCOL_VARIANT_ADD(ROSE, SEQPACKET, 0); + +/* Cf. pfkey_create */ +PROTOCOL_VARIANT_ADD_CAPS(KEY, RAW, PF_KEY_V2); + +/* Cf. netlink_create */ +PROTOCOL_VARIANT_ADD(NETLINK, RAW, 0); +PROTOCOL_VARIANT_ADD(NETLINK, DGRAM, 0); + +/* Cf. packet_create */ +PROTOCOL_VARIANT_ADD_CAPS(PACKET, DGRAM, 0); +PROTOCOL_VARIANT_ADD_CAPS(PACKET, RAW, 0); +PROTOCOL_VARIANT_ADD_CAPS(PACKET, PACKET, 0); + +/* Cf. svc_create */ +PROTOCOL_VARIANT_ADD(ATMSVC, DGRAM, 0); +PROTOCOL_VARIANT_ADD(ATMSVC, RAW, 0); +PROTOCOL_VARIANT_ADD(ATMSVC, RDM, 0); +PROTOCOL_VARIANT_ADD(ATMSVC, SEQPACKET, 0); +PROTOCOL_VARIANT_ADD(ATMSVC, DCCP, 0); +PROTOCOL_VARIANT_ADD(ATMSVC, PACKET, 0); + +/* Cf. rds_create */ +PROTOCOL_VARIANT_ADD(RDS, SEQPACKET, 0); + +/* Cf. pppox_create + pppoe_create */ +PROTOCOL_VARIANT_ADD(PPPOX, STREAM, 0); +PROTOCOL_VARIANT_ADD(PPPOX, DGRAM, 0); +PROTOCOL_VARIANT_ADD(PPPOX, RAW, 0); +PROTOCOL_VARIANT_ADD(PPPOX, RDM, 0); +PROTOCOL_VARIANT_ADD(PPPOX, SEQPACKET, 0); +PROTOCOL_VARIANT_ADD(PPPOX, DCCP, 0); +PROTOCOL_VARIANT_ADD(PPPOX, PACKET, 0); + +/* Cf. llc_ui_create */ +PROTOCOL_VARIANT_ADD_CAPS(LLC, DGRAM, 0); +PROTOCOL_VARIANT_ADD_CAPS(LLC, STREAM, 0); + +/* Cf. can_create */ +PROTOCOL_VARIANT_ADD(CAN, DGRAM, CAN_BCM); + +/* Cf. tipc_sk_create */ +PROTOCOL_VARIANT_ADD(TIPC, STREAM, 0); +PROTOCOL_VARIANT_ADD(TIPC, SEQPACKET, 0); +PROTOCOL_VARIANT_ADD(TIPC, DGRAM, 0); +PROTOCOL_VARIANT_ADD(TIPC, RDM, 0); + +/* Cf. l2cap_sock_create */ +#ifndef __s390x__ +PROTOCOL_VARIANT_ADD(BLUETOOTH, SEQPACKET, 0); +PROTOCOL_VARIANT_ADD(BLUETOOTH, STREAM, 0); +PROTOCOL_VARIANT_ADD(BLUETOOTH, DGRAM, 0); +PROTOCOL_VARIANT_ADD_CAPS(BLUETOOTH, RAW, 0); +#endif + +/* Cf. iucv_sock_create */ +#ifdef __s390x__ +PROTOCOL_VARIANT_ADD(IUCV, STREAM, 0); +PROTOCOL_VARIANT_ADD(IUCV, SEQPACKET, 0); +#endif + +/* Cf. rxrpc_create */ +PROTOCOL_VARIANT_ADD(RXRPC, DGRAM, PF_INET); + +/* Cf. mISDN_sock_create */ +#define ISDN_P_BASE 0 /* Cf. linux/mISDNif.h */ +#define ISDN_P_TE_S0 0x01 /* Cf. linux/mISDNif.h */ +PROTOCOL_VARIANT_ADD_CAPS(ISDN, RAW, ISDN_P_BASE); +PROTOCOL_VARIANT_ADD(ISDN, DGRAM, ISDN_P_TE_S0); + +/* Cf. pn_socket_create */ +PROTOCOL_VARIANT_ADD_CAPS(PHONET, DGRAM, 0); +PROTOCOL_VARIANT_ADD_CAPS(PHONET, SEQPACKET, 0); + +/* Cf. ieee802154_create */ +PROTOCOL_VARIANT_ADD_CAPS(IEEE802154, RAW, 0); +PROTOCOL_VARIANT_ADD(IEEE802154, DGRAM, 0); + +/* Cf. caif_create */ +PROTOCOL_VARIANT_ADD_CAPS(CAIF, SEQPACKET, 0); +PROTOCOL_VARIANT_ADD_CAPS(CAIF, STREAM, 0); + +/* Cf. alg_create */ +PROTOCOL_VARIANT_ADD(ALG, SEQPACKET, 0); + +/* Cf. nfc_sock_create + rawsock_create */ +PROTOCOL_VARIANT_ADD(NFC, SEQPACKET, 0); + +/* Cf. vsock_create */ +#if defined(__x86_64__) || defined(__aarch64__) +PROTOCOL_VARIANT_ADD(VSOCK, STREAM, 0); +PROTOCOL_VARIANT_ADD(VSOCK, SEQPACKET, 0); +#endif + +/* Cf. qrtr_create */ +PROTOCOL_VARIANT_ADD(QIPCRTR, DGRAM, 0); + +/* Cf. mctp_pf_create */ +PROTOCOL_VARIANT_ADD(MCTP, DGRAM, 0); diff --git a/tools/testing/selftests/landlock/socket_test.c b/tools/testing/selftests/landlock/socket_test.c index 16477614dfed..1b6c709d2893 100644 --- a/tools/testing/selftests/landlock/socket_test.c +++ b/tools/testing/selftests/landlock/socket_test.c @@ -9,6 +9,9 @@ #include #include +#include +#include +#include #include "common.h" @@ -379,4 +382,133 @@ TEST_F(prot_outside_range, add_rule) ASSERT_EQ(0, close(ruleset_fd)); } +FIXTURE(protocol) +{ + struct protocol_variant prot; + bool requires_caps; +}; + +FIXTURE_VARIANT(protocol) +{ + struct protocol_variant prot; + bool requires_caps; +}; + +FIXTURE_SETUP(protocol) +{ + disable_caps(_metadata); + + self->prot = variant->prot; + self->requires_caps = variant->requires_caps; +}; + +FIXTURE_TEARDOWN(protocol) +{ +} + +#define _PROTOCOL_VARIANT_ADD(family_, type_, protocol_, caps_) \ + FIXTURE_VARIANT_ADD(protocol, family_##_##type_##_##protocol_) \ + { \ + .prot = { \ + .domain = AF_##family_, \ + .type = SOCK_##type_, \ + .protocol = protocol_, \ + }, \ + .requires_caps = caps_, \ + } + +#define PROTOCOL_VARIANT_ADD(family, type, protocol) \ + _PROTOCOL_VARIANT_ADD(family, type, protocol, false) + +#define PROTOCOL_VARIANT_ADD_CAPS(family, type, protocol) \ + _PROTOCOL_VARIANT_ADD(family, type, protocol, true) + +#include "protocols_define.h" + +#undef _PROTOCOL_VARIANT_ADD +#undef PROTOCOL_VARIANT_ADD +#undef PROTOCOL_VARIANT_ADD_CAPS + +static int test_socket(int family, int type, int protocol) +{ + int fd; + + fd = socket(family, type | SOCK_CLOEXEC, protocol); + if (fd < 0) + return errno; + /* + * Mixing error codes from close(2) and socket(2) should not lead to + * any (access type) confusion for this tests. + */ + if (close(fd) != 0) + return errno; + return 0; +} + +static int test_socket_variant(struct __test_metadata *const _metadata, + const struct protocol_variant *const prot, + bool requires_caps) +{ + int err; + + if (requires_caps) { + set_cap(_metadata, CAP_NET_RAW); + set_cap(_metadata, CAP_SYS_ADMIN); + set_cap(_metadata, CAP_NET_ADMIN); + } + + err = test_socket(prot->domain, prot->type, prot->protocol); + + if (requires_caps) { + clear_cap(_metadata, CAP_NET_RAW); + clear_cap(_metadata, CAP_SYS_ADMIN); + clear_cap(_metadata, CAP_NET_ADMIN); + } + + return err; +} + +TEST_F(protocol, restrict_socket) +{ + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_socket = LANDLOCK_ACCESS_SOCKET_CREATE, + }; + int ruleset_fd; + const struct landlock_socket_attr create_socket_attr = { + .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE, + .family = self->prot.domain, + .type = self->prot.type, + .protocol = self->prot.protocol, + }; + + /* Verifies default socket creation. */ + ASSERT_EQ(0, test_socket_variant(_metadata, &self->prot, + self->requires_caps)); + + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_SOCKET, + &create_socket_attr, 0)); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + /* Tries to create socket when protocol is allowed. */ + EXPECT_EQ(0, test_socket_variant(_metadata, &self->prot, + self->requires_caps)); + + /* Denies creation. */ + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + /* Tries to create a socket when protocol is restricted. */ + EXPECT_EQ(EACCES, test_socket_variant(_metadata, &self->prot, + self->requires_caps)); +} + TEST_HARNESS_MAIN -- 2.34.1 Add test validating that Landlock returns EACCES for unsupported address family and protocol. Signed-off-by: Mikhail Ivanov --- Changes since v3: * Access check doesn't handle error consistency due to change to socket_create from socket_post_create LSM hook. * Renames commit. * Minor changes. --- .../testing/selftests/landlock/socket_test.c | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/tools/testing/selftests/landlock/socket_test.c b/tools/testing/selftests/landlock/socket_test.c index 1b6c709d2893..ebb39cbf9211 100644 --- a/tools/testing/selftests/landlock/socket_test.c +++ b/tools/testing/selftests/landlock/socket_test.c @@ -511,4 +511,71 @@ TEST_F(protocol, restrict_socket) self->requires_caps)); } +/* + * Errors related to AF internal validation of supported protocol attributes + * are not consistent in sandboxed mode. + */ +TEST_F(mini, unsupported_af_and_prot) +{ + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_socket = LANDLOCK_ACCESS_SOCKET_CREATE, + }; + const struct landlock_socket_attr socket_af_unsupported = { + .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE, + .family = AF_UNSPEC, /* cf __sock_create */ + .type = SOCK_STREAM, + .protocol = 0, + }; + const struct landlock_socket_attr socket_type_unsupported = { + .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE, + .family = AF_UNIX, + .type = SOCK_PACKET, /* cf. unix_create */ + .protocol = 0, + }; + const struct landlock_socket_attr socket_proto_unsupported = { + .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE, + .family = AF_UNIX, + .type = SOCK_STREAM, + .protocol = PF_UNIX + 1, /* cf. unix_create */ + }; + int ruleset_fd; + + /* Tries to create a socket when ruleset is not established. */ + ASSERT_EQ(EAFNOSUPPORT, test_socket(AF_UNSPEC, SOCK_STREAM, 0)); + ASSERT_EQ(ESOCKTNOSUPPORT, test_socket(AF_UNIX, SOCK_PACKET, 0)); + ASSERT_EQ(EPROTONOSUPPORT, + test_socket(AF_UNIX, SOCK_STREAM, PF_UNIX + 1)); + + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + /* Landlock allows creating rules for meaningless protocols. */ + EXPECT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_SOCKET, + &socket_af_unsupported, 0)); + EXPECT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_SOCKET, + &socket_type_unsupported, 0)); + EXPECT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_SOCKET, + &socket_proto_unsupported, 0)); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + /* Tries to create a socket when protocols are allowed. */ + EXPECT_EQ(EAFNOSUPPORT, test_socket(AF_UNSPEC, SOCK_STREAM, 0)); + EXPECT_EQ(ESOCKTNOSUPPORT, test_socket(AF_UNIX, SOCK_PACKET, 0)); + EXPECT_EQ(EPROTONOSUPPORT, + test_socket(AF_UNIX, SOCK_STREAM, PF_UNIX + 1)); + + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + /* Tries to create a socket when protocols are restricted. */ + EXPECT_EQ(EACCES, test_socket(AF_UNSPEC, SOCK_STREAM, 0)); + EXPECT_EQ(EACCES, test_socket(AF_UNIX, SOCK_PACKET, 0)); + EXPECT_EQ(EACCES, test_socket(AF_UNIX, SOCK_STREAM, PF_UNIX + 1)); +} + TEST_HARNESS_MAIN -- 2.34.1 Add test that validates Landlock behaviour with overlapped socket restriction. Add test that validates behaviour of using multiple layers that define access for protocol ranges using wildcard values. Signed-off-by: Mikhail Ivanov --- Changes since v3: * Adds test "ruleset_with_wildcards_overlap". Changes since v2: * Removes `tcp_layers` fixture and replaces it with `protocol` fixture for this test. protocol.ruleset_overlap tests every layers depth in a single run. * Adds add_ruleset_layer() helper that enforces ruleset and allows access if such is given. * Replaces EXPECT_EQ with ASSERT_EQ for close(). * Refactors commit message and title. Changes since v1: * Replaces test_socket_create() with test_socket(). * Formats code with clang-format. * Refactors commit message. * Minor fixes. --- .../testing/selftests/landlock/socket_test.c | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/tools/testing/selftests/landlock/socket_test.c b/tools/testing/selftests/landlock/socket_test.c index ebb39cbf9211..8b8913290a64 100644 --- a/tools/testing/selftests/landlock/socket_test.c +++ b/tools/testing/selftests/landlock/socket_test.c @@ -578,4 +578,96 @@ TEST_F(mini, unsupported_af_and_prot) EXPECT_EQ(EACCES, test_socket(AF_UNIX, SOCK_STREAM, PF_UNIX + 1)); } +static void add_ruleset_layer(struct __test_metadata *const _metadata, + const struct landlock_socket_attr *socket_attr) +{ + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_socket = LANDLOCK_ACCESS_SOCKET_CREATE, + }; + int ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + if (socket_attr) { + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_SOCKET, + socket_attr, 0)); + } + + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); +} + +TEST_F(mini, ruleset_overlap) +{ + const struct landlock_socket_attr create_socket_attr = { + .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE, + .family = AF_INET, + .type = SOCK_STREAM, + .protocol = 0, + }; + + /* socket(2) is allowed if there are no restrictions. */ + ASSERT_EQ(0, test_socket(AF_INET, SOCK_STREAM, 0)); + + /* Creates ruleset with socket(2) allowed. */ + add_ruleset_layer(_metadata, &create_socket_attr); + EXPECT_EQ(0, test_socket(AF_INET, SOCK_STREAM, 0)); + + /* Adds ruleset layer with socket(2) restricted. */ + add_ruleset_layer(_metadata, NULL); + EXPECT_EQ(EACCES, test_socket(AF_INET, SOCK_STREAM, 0)); + + /* + * Adds ruleset layer with socket(2) allowed. socket(2) is restricted + * by second layer of the ruleset. + */ + add_ruleset_layer(_metadata, &create_socket_attr); + EXPECT_EQ(EACCES, test_socket(AF_INET, SOCK_STREAM, 0)); +} + +TEST_F(mini, ruleset_with_wildcards_overlap) +{ + const struct landlock_socket_attr create_socket_attr = { + .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE, + .family = AF_INET, + .type = (-1), + .protocol = (-1), + }; + + /* socket(2) is allowed if there are no restrictions. */ + ASSERT_EQ(0, test_socket(AF_INET, SOCK_STREAM, 0)); + ASSERT_EQ(0, test_socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP)); + ASSERT_EQ(0, test_socket(AF_INET, SOCK_DGRAM, 0)); + + /* Creates ruleset with AF_INET allowed. */ + add_ruleset_layer(_metadata, &create_socket_attr); + EXPECT_EQ(0, test_socket(AF_INET, SOCK_STREAM, 0)); + EXPECT_EQ(0, test_socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP)); + EXPECT_EQ(0, test_socket(AF_INET, SOCK_DGRAM, 0)); + + const struct landlock_socket_attr create_socket_attr2 = { + .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE, + .family = AF_INET, + .type = SOCK_STREAM, + .protocol = (-1), + }; + /* Creates layer with AF_INET + SOCK_STREAM allowed. */ + add_ruleset_layer(_metadata, &create_socket_attr2); + EXPECT_EQ(0, test_socket(AF_INET, SOCK_STREAM, 0)); + EXPECT_EQ(0, test_socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP)); + EXPECT_EQ(EACCES, test_socket(AF_INET, SOCK_DGRAM, 0)); + + const struct landlock_socket_attr create_socket_attr3 = { + .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE, + .family = AF_INET, + .type = SOCK_STREAM, + .protocol = 0, + }; + /* Creates layer with AF_INET + SOCK_STREAM + 0 allowed. */ + add_ruleset_layer(_metadata, &create_socket_attr3); + EXPECT_EQ(0, test_socket(AF_INET, SOCK_STREAM, 0)); + EXPECT_EQ(EACCES, test_socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP)); + EXPECT_EQ(EACCES, test_socket(AF_INET, SOCK_DGRAM, 0)); +} + TEST_HARNESS_MAIN -- 2.34.1 Add test validating that Landlock provides restriction of user space sockets only. Reviewed-by: Günther Noack Signed-off-by: Mikhail Ivanov --- Changes since v3: * Grammar fixes. * Adds mini fixture. --- .../testing/selftests/landlock/socket_test.c | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tools/testing/selftests/landlock/socket_test.c b/tools/testing/selftests/landlock/socket_test.c index 8b8913290a64..ce9a6e283be6 100644 --- a/tools/testing/selftests/landlock/socket_test.c +++ b/tools/testing/selftests/landlock/socket_test.c @@ -670,4 +670,43 @@ TEST_F(mini, ruleset_with_wildcards_overlap) EXPECT_EQ(EACCES, test_socket(AF_INET, SOCK_DGRAM, 0)); } +/* mini.kernel_socket will fail with EAFNOSUPPORT if SMC is not supported. */ +TEST_F(mini, kernel_socket) +{ + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_socket = LANDLOCK_ACCESS_SOCKET_CREATE, + }; + const struct landlock_socket_attr smc_socket_create = { + .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE, + .family = AF_SMC, + .type = SOCK_STREAM, + .protocol = 0, + }; + int ruleset_fd; + + /* + * Checks that SMC socket is created successfully without + * landlock restrictions. + */ + ASSERT_EQ(0, test_socket(AF_SMC, SOCK_STREAM, 0)); + + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_SOCKET, + &smc_socket_create, 0)); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + /* + * During the creation of an SMC socket, an internal service TCP socket + * is also created (Cf. smc_create_clcsk). + * + * Checks that Landlock does not restrict creation of the kernel space + * socket. + */ + EXPECT_EQ(0, test_socket(AF_SMC, SOCK_STREAM, 0)); +} + TEST_HARNESS_MAIN -- 2.34.1 It is possible to create sockets of the same protocol with different protocol number (cf. socket(2)) values. For example, TCP sockets can be created using one of the following commands: 1. fd = socket(AF_INET, SOCK_STREAM, 0); 2. fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); Whereas IPPROTO_TCP = 6. Protocol number 0 correspond to the default protocol of the given protocol family and can be mapped to another value. Mapping is handled on the protocol family level. Socket rules should not perform such mappings to not increase complexity of rules definition and their maintenance. (AF_INET, SOCK_PACKET) is an alias for (AF_PACKET, SOCK_PACKET) (cf. __sock_create) handled due to compatibility reasons. Compared to TCP network stack performs mapping before calling LSM hook related to socket creation. Therefore Landlock should not restrict one pair if the other was allowed. Add `packet_protocol` and `tcp_protocol` fixtures and tests to validate these scenarios. Signed-off-by: Mikhail Ivanov --- Changes since v3: * Adds verification of TCP mapping. * Changes commit message. --- .../testing/selftests/landlock/socket_test.c | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/tools/testing/selftests/landlock/socket_test.c b/tools/testing/selftests/landlock/socket_test.c index ce9a6e283be6..e22e10edb103 100644 --- a/tools/testing/selftests/landlock/socket_test.c +++ b/tools/testing/selftests/landlock/socket_test.c @@ -709,4 +709,161 @@ TEST_F(mini, kernel_socket) EXPECT_EQ(0, test_socket(AF_SMC, SOCK_STREAM, 0)); } +/* clang-format off */ +FIXTURE(packet_protocol) {}; +/* clang-format on */ + +FIXTURE_VARIANT(packet_protocol) +{ + int family, type, protocol; +}; + +/* clang-format off */ +FIXTURE_SETUP(packet_protocol) {}; +/* clang-format on */ + +FIXTURE_TEARDOWN(packet_protocol) +{ +} + +/* clang-format off */ +FIXTURE_VARIANT_ADD(packet_protocol, pf_inet) { + /* clang-format on */ + .family = AF_INET, + .type = SOCK_PACKET, + .protocol = 0, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(packet_protocol, pf_packet) { + /* clang-format on */ + .family = AF_PACKET, + .type = SOCK_PACKET, + .protocol = 0, +}; + +/* + * (AF_INET, SOCK_PACKET) is an alias for the (AF_PACKET, SOCK_PACKET) + * handled in socket layer (cf. __sock_create) due to compatibility reasons. + * + * Checks that Landlock does not restrict one pair if the other was allowed. + */ +TEST_F(packet_protocol, alias_restriction) +{ + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_socket = LANDLOCK_ACCESS_SOCKET_CREATE, + }; + const int family = variant->family; + const int type = variant->type; + const int protocol = variant->protocol; + const struct landlock_socket_attr packet_socket_create = { + .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE, + .family = family, + .type = type, + .protocol = protocol, + }; + int ruleset_fd; + + /* + * Checks that packet socket is created successfully without + * landlock restrictions. + * + * Packet sockets require CAP_NET_RAW capability. + */ + set_cap(_metadata, CAP_NET_RAW); + ASSERT_EQ(0, test_socket(AF_INET, SOCK_PACKET, 0)); + ASSERT_EQ(0, test_socket(AF_PACKET, SOCK_PACKET, 0)); + clear_cap(_metadata, CAP_NET_RAW); + + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_SOCKET, + &packet_socket_create, 0)); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + set_cap(_metadata, CAP_NET_RAW); + EXPECT_EQ(0, test_socket(AF_INET, SOCK_PACKET, 0)); + EXPECT_EQ(0, test_socket(AF_PACKET, SOCK_PACKET, 0)); + clear_cap(_metadata, CAP_NET_RAW); +} + +/* clang-format off */ +FIXTURE(tcp_protocol) {}; +/* clang-format on */ + +FIXTURE_VARIANT(tcp_protocol) +{ + int family, type, protocol; +}; + +/* clang-format off */ +FIXTURE_SETUP(tcp_protocol) {}; +/* clang-format on */ + +FIXTURE_TEARDOWN(tcp_protocol) +{ +} + +/* clang-format off */ +FIXTURE_VARIANT_ADD(tcp_protocol, variant1) { + /* clang-format on */ + .family = AF_INET, + .type = SOCK_STREAM, + .protocol = 0, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(tcp_protocol, variant2) { + /* clang-format on */ + .family = AF_INET, + .type = SOCK_STREAM, + .protocol = IPPROTO_TCP, /* = 6 */ +}; + +/* + * Landlock doesn't perform protocol mappings handled by network stack on + * protocol family level. Test verifies that if only one definition is + * allowed another becomes restricted. + */ +TEST_F(tcp_protocol, alias_restriction) +{ + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_socket = LANDLOCK_ACCESS_SOCKET_CREATE, + }; + const int family = variant->family; + const int type = variant->type; + const int protocol = variant->protocol; + const struct landlock_socket_attr tcp_socket_create = { + .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE, + .family = family, + .type = type, + .protocol = protocol, + }; + int ruleset_fd; + + ASSERT_EQ(0, test_socket(AF_INET, SOCK_STREAM, 0)); + ASSERT_EQ(0, test_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)); + + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_SOCKET, + &tcp_socket_create, 0)); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + if (protocol == 0) { + EXPECT_EQ(0, test_socket(AF_INET, SOCK_STREAM, 0)); + EXPECT_EQ(EACCES, + test_socket(AF_PACKET, SOCK_STREAM, IPPROTO_TCP)); + } else if (protocol == IPPROTO_TCP) { + EXPECT_EQ(EACCES, test_socket(AF_INET, SOCK_STREAM, 0)); + EXPECT_EQ(0, test_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)); + } +} + TEST_HARNESS_MAIN -- 2.34.1 Add test that checks the restriction on socket creation using socketpair(2). Add `socket_creation` fixture to configure sandboxing in tests in which different socket creation actions are tested. Signed-off-by: Mikhail Ivanov --- Changes since v3: * Removes socket_creation fixture. --- .../testing/selftests/landlock/socket_test.c | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/tools/testing/selftests/landlock/socket_test.c b/tools/testing/selftests/landlock/socket_test.c index e22e10edb103..d1a004c2e0f5 100644 --- a/tools/testing/selftests/landlock/socket_test.c +++ b/tools/testing/selftests/landlock/socket_test.c @@ -866,4 +866,59 @@ TEST_F(tcp_protocol, alias_restriction) } } +static int test_socketpair(int family, int type, int protocol) +{ + int fds[2]; + int err; + + err = socketpair(family, type | SOCK_CLOEXEC, protocol, fds); + if (err) + return errno; + /* + * Mixing error codes from close(2) and socketpair(2) should not lead to + * any (access type) confusion for this test. + */ + if (close(fds[0]) != 0) + return errno; + if (close(fds[1]) != 0) + return errno; + return 0; +} + +TEST_F(mini, socketpair) +{ + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_socket = LANDLOCK_ACCESS_SOCKET_CREATE, + }; + const struct landlock_socket_attr unix_socket_create = { + .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE, + .family = AF_UNIX, + .type = SOCK_STREAM, + .protocol = 0, + }; + int ruleset_fd; + + /* Tries to create socket when ruleset is not established. */ + ASSERT_EQ(0, test_socketpair(AF_UNIX, SOCK_STREAM, 0)); + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_SOCKET, + &unix_socket_create, 0)); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + /* Tries to create socket when protocol is allowed */ + EXPECT_EQ(0, test_socketpair(AF_UNIX, SOCK_STREAM, 0)); + + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + /* Tries to create socket when protocol is restricted. */ + EXPECT_EQ(EACCES, test_socketpair(AF_UNIX, SOCK_STREAM, 0)); +} + TEST_HARNESS_MAIN -- 2.34.1 It is possible to branch off an SCTP UDP association into a separate user space UDP socket. Add test validating that such scenario is restricted by Landlock. Move setup_loopback() helper from net_test to common.h to use it to enable connection in this test. Signed-off-by: Mikhail Ivanov --- Changes since v3: * Restricts branching off SCTP association. * Changes commit message. * Chages fixture from socket_create to mini. --- tools/testing/selftests/landlock/common.h | 13 ++ tools/testing/selftests/landlock/net_test.c | 11 -- .../testing/selftests/landlock/socket_test.c | 128 ++++++++++++++++++ 3 files changed, 141 insertions(+), 11 deletions(-) diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h index e9378a229a4c..ff1b7fc94a5b 100644 --- a/tools/testing/selftests/landlock/common.h +++ b/tools/testing/selftests/landlock/common.h @@ -16,6 +16,7 @@ #include #include #include +#include #include "../kselftest_harness.h" #include "wrappers.h" @@ -255,3 +256,15 @@ static void __maybe_unused set_unix_address(struct service_fixture *const srv, srv->unix_addr_len = SUN_LEN(&srv->unix_addr); srv->unix_addr.sun_path[0] = '\0'; } + +static void __maybe_unused +setup_loopback(struct __test_metadata *const _metadata) +{ + set_cap(_metadata, CAP_SYS_ADMIN); + ASSERT_EQ(0, unshare(CLONE_NEWNET)); + clear_cap(_metadata, CAP_SYS_ADMIN); + + set_ambient_cap(_metadata, CAP_NET_ADMIN); + ASSERT_EQ(0, system("ip link set dev lo up")); + clear_ambient_cap(_metadata, CAP_NET_ADMIN); +} diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c index 2a45208551e6..a71ea275cf10 100644 --- a/tools/testing/selftests/landlock/net_test.c +++ b/tools/testing/selftests/landlock/net_test.c @@ -75,17 +75,6 @@ static int set_service(struct service_fixture *const srv, return 1; } -static void setup_loopback(struct __test_metadata *const _metadata) -{ - set_cap(_metadata, CAP_SYS_ADMIN); - ASSERT_EQ(0, unshare(CLONE_NEWNET)); - clear_cap(_metadata, CAP_SYS_ADMIN); - - set_ambient_cap(_metadata, CAP_NET_ADMIN); - ASSERT_EQ(0, system("ip link set dev lo up")); - clear_ambient_cap(_metadata, CAP_NET_ADMIN); -} - static bool prot_is_tcp(const struct protocol_variant *const prot) { return (prot->domain == AF_INET || prot->domain == AF_INET6) && diff --git a/tools/testing/selftests/landlock/socket_test.c b/tools/testing/selftests/landlock/socket_test.c index d1a004c2e0f5..e9f56a86f456 100644 --- a/tools/testing/selftests/landlock/socket_test.c +++ b/tools/testing/selftests/landlock/socket_test.c @@ -12,6 +12,10 @@ #include #include #include +#include +#include +#include +#include #include "common.h" @@ -921,4 +925,128 @@ TEST_F(mini, socketpair) EXPECT_EQ(EACCES, test_socketpair(AF_UNIX, SOCK_STREAM, 0)); } +/* clang-format off */ +FIXTURE(connection_restriction) {}; +/* clang-format on */ + +FIXTURE_VARIANT(connection_restriction) +{ + bool sandboxed; +}; + +FIXTURE_SETUP(connection_restriction) +{ + disable_caps(_metadata); + setup_loopback(_metadata); +}; + +FIXTURE_TEARDOWN(connection_restriction) +{ +} + +/* clang-format off */ +FIXTURE_VARIANT_ADD(connection_restriction, allowed) { + /* clang-format on */ + .sandboxed = false, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(connection_restriction, sandboxed) { + /* clang-format on */ + .sandboxed = true, +}; + +static const char loopback_ipv4[] = "127.0.0.1"; +static const int backlog = 10; +static const int loopback_port = 1024; + +TEST_F(connection_restriction, sctp_peeloff) +{ + int status, ret; + pid_t child; + struct sockaddr_in addr; + int server_fd; + + server_fd = + socket(AF_INET, SOCK_SEQPACKET | SOCK_CLOEXEC, IPPROTO_SCTP); + ASSERT_LE(0, server_fd); + + addr.sin_family = AF_INET; + addr.sin_port = htons(loopback_port); + addr.sin_addr.s_addr = inet_addr(loopback_ipv4); + + ASSERT_EQ(0, bind(server_fd, &addr, sizeof(addr))); + ASSERT_EQ(0, listen(server_fd, backlog)); + + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + int client_fd; + sctp_peeloff_flags_arg_t peeloff; + socklen_t peeloff_size = sizeof(peeloff); + + /* Closes listening socket for the child. */ + ASSERT_EQ(0, close(server_fd)); + + client_fd = socket(AF_INET, SOCK_SEQPACKET | SOCK_CLOEXEC, + IPPROTO_SCTP); + ASSERT_LE(0, client_fd); + + /* + * Establishes connection between sockets and + * gets SCTP association id. + */ + ret = setsockopt(client_fd, IPPROTO_SCTP, SCTP_SOCKOPT_CONNECTX, + &addr, sizeof(addr)); + ASSERT_LE(0, ret); + + if (variant->sandboxed) { + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_socket = + LANDLOCK_ACCESS_SOCKET_CREATE, + }; + /* Denies creation of SCTP sockets. */ + int ruleset_fd = landlock_create_ruleset( + &ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + } + /* + * Branches off current SCTP association into a separate socket + * and returns it to user space. + */ + peeloff.p_arg.associd = ret; + ret = getsockopt(client_fd, IPPROTO_SCTP, SCTP_SOCKOPT_PEELOFF, + &peeloff, &peeloff_size); + + /* + * Branching off existing SCTP association leads to creation of user space + * SCTP UDP socket and should be restricted by Landlock. + */ + if (variant->sandboxed) { + EXPECT_EQ(-1, ret); + EXPECT_EQ(EACCES, errno); + } else { + ASSERT_LE(0, ret); + } + + /* getsockopt(2) returns 0 on success. */ + if (ret == 0) { + /* Closes peeloff socket if such was created. */ + ASSERT_EQ(0, close(peeloff.p_arg.sd)); + } + ASSERT_EQ(0, close(client_fd)); + _exit(_metadata->exit_code); + return; + } + + ASSERT_EQ(child, waitpid(child, &status, 0)); + ASSERT_EQ(1, WIFEXITED(status)); + ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); + + ASSERT_EQ(0, close(server_fd)); +} + TEST_HARNESS_MAIN -- 2.34.1 Add test validating that socket creation with accept(2) is not restricted by Landlock. Signed-off-by: Mikhail Ivanov --- Changes since v3: * Minor fixes. --- .../testing/selftests/landlock/socket_test.c | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/tools/testing/selftests/landlock/socket_test.c b/tools/testing/selftests/landlock/socket_test.c index e9f56a86f456..ea1590e555b7 100644 --- a/tools/testing/selftests/landlock/socket_test.c +++ b/tools/testing/selftests/landlock/socket_test.c @@ -1049,4 +1049,66 @@ TEST_F(connection_restriction, sctp_peeloff) ASSERT_EQ(0, close(server_fd)); } +TEST_F(connection_restriction, accept) +{ + int status; + pid_t child; + struct sockaddr_in addr; + int server_fd, client_fd; + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_socket = LANDLOCK_ACCESS_SOCKET_CREATE, + }; + + server_fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); + ASSERT_LE(0, server_fd); + + addr.sin_family = AF_INET; + addr.sin_port = htons(loopback_port); + addr.sin_addr.s_addr = inet_addr(loopback_ipv4); + + ASSERT_EQ(0, bind(server_fd, &addr, sizeof(addr))); + ASSERT_EQ(0, listen(server_fd, backlog)); + + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + /* Connects to server once and exits. */ + + /* Closes listening socket for the child. */ + ASSERT_EQ(0, close(server_fd)); + + client_fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); + ASSERT_LE(0, client_fd); + + ASSERT_EQ(0, connect(client_fd, &addr, sizeof(addr))); + + ASSERT_EQ(0, close(client_fd)); + _exit(_metadata->exit_code); + return; + } + + if (variant->sandboxed) { + int ruleset_fd; + + ruleset_fd = landlock_create_ruleset(&ruleset_attr, + sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + } + + client_fd = accept(server_fd, NULL, 0); + + /* accept(2) should not be restricted by Landlock. */ + ASSERT_LE(0, client_fd); + + ASSERT_EQ(child, waitpid(child, &status, 0)); + ASSERT_EQ(1, WIFEXITED(status)); + ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); + + ASSERT_EQ(0, close(server_fd)); + ASSERT_EQ(0, close(client_fd)); +} + TEST_HARNESS_MAIN -- 2.34.1 Add LSM_AUDIT_DATA_SOCKET type to log socket-related data in audit_log_lsm_data(). This may be useful (for example) to log socket creation denials. Signed-off-by: Mikhail Ivanov --- include/linux/lsm_audit.h | 8 ++++++++ security/lsm_audit.c | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h index 382c56a97bba..7c7617df41b5 100644 --- a/include/linux/lsm_audit.h +++ b/include/linux/lsm_audit.h @@ -57,6 +57,12 @@ struct lsm_ibendport_audit { u8 port; }; +struct lsm_socket_audit { + s32 family; + s32 type; + s32 protocol; +}; + /* Auxiliary data to use in generating the audit record. */ struct common_audit_data { char type; @@ -78,6 +84,7 @@ struct common_audit_data { #define LSM_AUDIT_DATA_NOTIFICATION 16 #define LSM_AUDIT_DATA_ANONINODE 17 #define LSM_AUDIT_DATA_NLMSGTYPE 18 +#define LSM_AUDIT_DATA_SOCKET 19 union { struct path path; struct dentry *dentry; @@ -97,6 +104,7 @@ struct common_audit_data { struct file *file; struct lsm_ibpkey_audit *ibpkey; struct lsm_ibendport_audit *ibendport; + struct lsm_socket_audit *socket; int reason; const char *anonclass; u16 nlmsg_type; diff --git a/security/lsm_audit.c b/security/lsm_audit.c index 7d623b00495c..7e18241290ce 100644 --- a/security/lsm_audit.c +++ b/security/lsm_audit.c @@ -403,6 +403,10 @@ void audit_log_lsm_data(struct audit_buffer *ab, case LSM_AUDIT_DATA_NLMSGTYPE: audit_log_format(ab, " nl-msgtype=%hu", a->u.nlmsg_type); break; + case LSM_AUDIT_DATA_SOCKET: + audit_log_format(ab, " family=%d sock_type=%d protocol=%d", + a->u.socket->family, a->u.socket->type, a->u.socket->protocol); + break; } /* switch (a->type) */ } -- 2.34.1 Add new type in landlock_requet_type related to socket access checks auditing. Print blocker related to socket access in get_blocker() and log socket creation denials in hook_socket_create(). Signed-off-by: Mikhail Ivanov --- security/landlock/audit.c | 12 ++++++++++++ security/landlock/audit.h | 1 + security/landlock/socket.c | 15 +++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/security/landlock/audit.c b/security/landlock/audit.c index c52d079cdb77..c2c0e8fd38cb 100644 --- a/security/landlock/audit.c +++ b/security/landlock/audit.c @@ -48,6 +48,12 @@ static const char *const net_access_strings[] = { static_assert(ARRAY_SIZE(net_access_strings) == LANDLOCK_NUM_ACCESS_NET); +static const char *const socket_access_strings[] = { + [BIT_INDEX(LANDLOCK_ACCESS_SOCKET_CREATE)] = "socket.create", +}; + +static_assert(ARRAY_SIZE(socket_access_strings) == LANDLOCK_NUM_ACCESS_SOCKET); + static __attribute_const__ const char * get_blocker(const enum landlock_request_type type, const unsigned long access_bit) @@ -71,6 +77,12 @@ get_blocker(const enum landlock_request_type type, return "unknown"; return net_access_strings[access_bit]; + case LANDLOCK_REQUEST_SOCKET_ACCESS: + if (WARN_ON_ONCE(access_bit >= + ARRAY_SIZE(socket_access_strings))) + return "unknown"; + return socket_access_strings[access_bit]; + case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET: WARN_ON_ONCE(access_bit != -1); return "scope.abstract_unix_socket"; diff --git a/security/landlock/audit.h b/security/landlock/audit.h index 92428b7fc4d8..b78d4503b0a5 100644 --- a/security/landlock/audit.h +++ b/security/landlock/audit.h @@ -19,6 +19,7 @@ enum landlock_request_type { LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY, LANDLOCK_REQUEST_FS_ACCESS, LANDLOCK_REQUEST_NET_ACCESS, + LANDLOCK_REQUEST_SOCKET_ACCESS, LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET, LANDLOCK_REQUEST_SCOPE_SIGNAL, }; diff --git a/security/landlock/socket.c b/security/landlock/socket.c index d7e6e7b92b7a..6afd5a0ac6d7 100644 --- a/security/landlock/socket.c +++ b/security/landlock/socket.c @@ -10,6 +10,7 @@ #include #include +#include "audit.h" #include "limits.h" #include "ruleset.h" #include "socket.h" @@ -132,6 +133,11 @@ static int hook_socket_create(int family, int type, int protocol, int kern) const struct landlock_cred_security *const subject = landlock_get_applicable_subject(current_cred(), masks, NULL); uintptr_t key; + struct lsm_socket_audit audit_socket = { + .family = family, + .type = type, + .protocol = protocol, + }; if (!subject) return 0; @@ -169,6 +175,15 @@ static int hook_socket_create(int family, int type, int protocol, int kern) handled_access) == 0) return 0; + landlock_log_denial(subject, + &(struct landlock_request){ + .type = LANDLOCK_REQUEST_SOCKET_ACCESS, + .audit.type = LSM_AUDIT_DATA_SOCKET, + .audit.u.socket = &audit_socket, + .access = LANDLOCK_ACCESS_SOCKET_CREATE, + .layer_masks = &layer_masks, + .layer_masks_size = ARRAY_SIZE(layer_masks), + }); return -EACCES; } -- 2.34.1 Test single socket blocker: socket.create. Signed-off-by: Mikhail Ivanov --- .../testing/selftests/landlock/socket_test.c | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/tools/testing/selftests/landlock/socket_test.c b/tools/testing/selftests/landlock/socket_test.c index ea1590e555b7..a091b8a883c8 100644 --- a/tools/testing/selftests/landlock/socket_test.c +++ b/tools/testing/selftests/landlock/socket_test.c @@ -17,6 +17,7 @@ #include #include +#include "audit.h" #include "common.h" #define ACCESS_LAST LANDLOCK_ACCESS_SOCKET_CREATE @@ -1111,4 +1112,58 @@ TEST_F(connection_restriction, accept) ASSERT_EQ(0, close(client_fd)); } +FIXTURE(audit) +{ + struct audit_filter audit_filter; + int audit_fd; +}; + +FIXTURE_SETUP(audit) +{ + set_cap(_metadata, CAP_AUDIT_CONTROL); + self->audit_fd = audit_init_with_exe_filter(&self->audit_filter); + EXPECT_LE(0, self->audit_fd); + disable_caps(_metadata); +}; + +FIXTURE_TEARDOWN(audit) +{ + set_cap(_metadata, CAP_AUDIT_CONTROL); + EXPECT_EQ(0, audit_cleanup(self->audit_fd, &self->audit_filter)); + clear_cap(_metadata, CAP_AUDIT_CONTROL); +} + +TEST_F(audit, socket_create) +{ + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_socket = LANDLOCK_ACCESS_SOCKET_CREATE, + }; + struct audit_records records; + int ruleset_fd; + const char log_template[] = REGEX_LANDLOCK_PREFIX + " blockers=socket.create family=%d sock_type=%d protocol=0$"; + /* Family and type should not exceed 2-digit number. */ + char log_match[sizeof(log_template) + 4]; + int log_match_len; + + log_match_len = snprintf(log_match, sizeof(log_match), log_template, + AF_INET, SOCK_STREAM); + ASSERT_LT(log_match_len, sizeof(log_match)); + + ruleset_fd = + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); + ASSERT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + ASSERT_EQ(EACCES, test_socket(AF_INET, SOCK_STREAM, 0)); + + EXPECT_EQ(0, audit_match_record(self->audit_fd, AUDIT_LANDLOCK_ACCESS, + log_match, NULL)); + + EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); + EXPECT_EQ(0, records.access); + EXPECT_EQ(1, records.domain); +} + TEST_HARNESS_MAIN -- 2.34.1 Add socket protocol control support in sandboxer demo. It's possible to allow a sandboxer to create sockets with specified family and type values. This is controlled with the new LL_SOCKET_CREATE environment variable. Single token in this variable looks like this: '{family}.{type}.{protocol}', where {family}, {type} and {protocol} are integers corresponding to requested protocol definition. Change LANDLOCK_ABI_LAST to 8. Signed-off-by: Mikhail Ivanov --- Changes since v3: * Changes ABI from 6 to 8. * Adds protocol field support. * Fixes commit message. * Minor fixes. Changes since v2: * Changes representation of socket protocol in LL_SOCKET_CREATE into pair of integer values. * Changes commit message. * Minor fixes. Changes since v1: * Refactors get_socket_protocol(). Rename it to parse_socket_protocol(). * Changes LANDLOCK_ABI_LAST to 6 since ioctl patchlist updated it to 5. * Refactors commit message. * Formats with clang-format. * Minor changes. --- samples/landlock/sandboxer.c | 118 ++++++++++++++++++++++++++++++++--- 1 file changed, 109 insertions(+), 9 deletions(-) diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c index e7af02f98208..96930c505807 100644 --- a/samples/landlock/sandboxer.c +++ b/samples/landlock/sandboxer.c @@ -60,9 +60,11 @@ static inline int landlock_restrict_self(const int ruleset_fd, #define ENV_FS_RW_NAME "LL_FS_RW" #define ENV_TCP_BIND_NAME "LL_TCP_BIND" #define ENV_TCP_CONNECT_NAME "LL_TCP_CONNECT" +#define ENV_SOCKET_CREATE_NAME "LL_SOCKET_CREATE" #define ENV_SCOPED_NAME "LL_SCOPED" #define ENV_FORCE_LOG_NAME "LL_FORCE_LOG" #define ENV_DELIMITER ":" +#define ENV_TOKEN_INTERNAL_DELIMITER "." static int str2num(const char *numstr, __u64 *num_dst) { @@ -226,6 +228,83 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd, return ret; } +static int populate_ruleset_socket(const char *const env_var, + const int ruleset_fd, + const __u64 allowed_access) +{ + int ret = 1; + char *env_protocol_name, *strprotocol, *strfamily, *strtype, *strproto; + unsigned long long family_ull, type_ull, proto_ull; + struct landlock_socket_attr protocol = { + .allowed_access = allowed_access, + }; + + env_protocol_name = getenv(env_var); + if (!env_protocol_name) + return 0; + env_protocol_name = strdup(env_protocol_name); + unsetenv(env_var); + + while ((strprotocol = strsep(&env_protocol_name, ENV_DELIMITER))) { + strfamily = strsep(&strprotocol, ENV_TOKEN_INTERNAL_DELIMITER); + strtype = strsep(&strprotocol, ENV_TOKEN_INTERNAL_DELIMITER); + strproto = strsep(&strprotocol, ENV_TOKEN_INTERNAL_DELIMITER); + + /* strsep should make this NULL if it had less than two delimiters. */ + if (strprotocol) { + fprintf(stderr, "Invalid format of socket protocol\n"); + goto out_free_name; + } + if (!strtype) { + fprintf(stderr, + "Failed to extract socket protocol with " + "unspecified type value\n"); + goto out_free_name; + } else if (!strproto) { + fprintf(stderr, + "Failed to extract socket protocol with " + "unspecified protocol value\n"); + goto out_free_name; + } + + if (str2num(strfamily, &family_ull)) { + fprintf(stderr, + "Failed to convert \"%s\" into a number\n", + strfamily); + goto out_free_name; + } + if (str2num(strtype, &type_ull)) { + fprintf(stderr, + "Failed to convert \"%s\" into a number\n", + strtype); + goto out_free_name; + } + if (str2num(strproto, &proto_ull)) { + fprintf(stderr, + "Failed to convert \"%s\" into a number\n", + strproto); + goto out_free_name; + } + protocol.family = (int)family_ull; + protocol.type = (int)type_ull; + protocol.protocol = (int)proto_ull; + + if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_SOCKET, + &protocol, 0)) { + fprintf(stderr, + "Failed to update the ruleset with " + "family \"%s\" and type \"%s\": %s\n", + strfamily, strtype, strerror(errno)); + goto out_free_name; + } + } + ret = 0; + +out_free_name: + free(env_protocol_name); + return ret; +} + /* Returns true on error, false otherwise. */ static bool check_ruleset_scope(const char *const env_var, struct landlock_ruleset_attr *ruleset_attr) @@ -299,7 +378,7 @@ static bool check_ruleset_scope(const char *const env_var, /* clang-format on */ -#define LANDLOCK_ABI_LAST 7 +#define LANDLOCK_ABI_LAST 8 #define XSTR(s) #s #define STR(s) XSTR(s) @@ -311,7 +390,7 @@ static const char help[] = "[other environment variables] %1$s [args]...\n" "\n" "Execute the given command in a restricted environment.\n" - "Multi-valued settings (lists of ports, paths, scopes) are colon-delimited.\n" + "Multi-valued settings (lists of ports, paths, protocols, scopes) are colon-delimited.\n" "\n" "Mandatory settings:\n" "* " ENV_FS_RO_NAME ": paths allowed to be used in a read-only way\n" @@ -322,6 +401,9 @@ static const char help[] = "means an empty list):\n" "* " ENV_TCP_BIND_NAME ": ports allowed to bind (server)\n" "* " ENV_TCP_CONNECT_NAME ": ports allowed to connect (client)\n" + "* " ENV_SOCKET_CREATE_NAME ": list of socket protocols allowed to be created\n" + " To define protocol format \"{family}.{type}.{protocol}\" is used\n" + " with numerical values of family, type and protocol (eg. 2.1.0 for TCP/IP)\n" "* " ENV_SCOPED_NAME ": actions denied on the outside of the landlock domain\n" " - \"a\" to restrict opening abstract unix sockets\n" " - \"s\" to restrict sending signals\n" @@ -334,6 +416,7 @@ static const char help[] = ENV_FS_RW_NAME "=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" " ENV_TCP_BIND_NAME "=\"9418\" " ENV_TCP_CONNECT_NAME "=\"80:443\" " + ENV_SOCKET_CREATE_NAME "=\"2.1.0\" " ENV_SCOPED_NAME "=\"a:s\" " "%1$s bash -i\n" "\n" @@ -347,7 +430,7 @@ int main(const int argc, char *const argv[], char *const *const envp) const char *cmd_path; char *const *cmd_argv; int ruleset_fd, abi; - char *env_port_name, *env_force_log; + char *env_opt_name, *env_force_log; __u64 access_fs_ro = ACCESS_FS_ROUGHLY_READ, access_fs_rw = ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_WRITE; @@ -355,6 +438,7 @@ int main(const int argc, char *const argv[], char *const *const envp) .handled_access_fs = access_fs_rw, .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | LANDLOCK_ACCESS_NET_CONNECT_TCP, + .handled_access_socket = LANDLOCK_ACCESS_SOCKET_CREATE, .scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | LANDLOCK_SCOPE_SIGNAL, }; @@ -437,6 +521,12 @@ int main(const int argc, char *const argv[], char *const *const envp) supported_restrict_flags &= ~LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON; + __attribute__((fallthrough)); + case 7: + /* Removes LANDLOCK_ACCESS_SOCKET_CREATE for ABI < 8 */ + ruleset_attr.handled_access_socket &= + ~LANDLOCK_ACCESS_SOCKET_CREATE; + /* Must be printed for any ABI < LANDLOCK_ABI_LAST. */ fprintf(stderr, "Hint: You should update the running kernel " @@ -456,18 +546,24 @@ int main(const int argc, char *const argv[], char *const *const envp) access_fs_ro &= ruleset_attr.handled_access_fs; access_fs_rw &= ruleset_attr.handled_access_fs; - /* Removes bind access attribute if not supported by a user. */ - env_port_name = getenv(ENV_TCP_BIND_NAME); - if (!env_port_name) { + /* Removes bind access attribute if not requested by a user. */ + env_opt_name = getenv(ENV_TCP_BIND_NAME); + if (!env_opt_name) { ruleset_attr.handled_access_net &= ~LANDLOCK_ACCESS_NET_BIND_TCP; } - /* Removes connect access attribute if not supported by a user. */ - env_port_name = getenv(ENV_TCP_CONNECT_NAME); - if (!env_port_name) { + /* Removes connect access attribute if not requested by a user. */ + env_opt_name = getenv(ENV_TCP_CONNECT_NAME); + if (!env_opt_name) { ruleset_attr.handled_access_net &= ~LANDLOCK_ACCESS_NET_CONNECT_TCP; } + /* Removes socket creation access attribute if not requested by a user. */ + env_opt_name = getenv(ENV_SOCKET_CREATE_NAME); + if (!env_opt_name) { + ruleset_attr.handled_access_socket &= + ~LANDLOCK_ACCESS_SOCKET_CREATE; + } if (check_ruleset_scope(ENV_SCOPED_NAME, &ruleset_attr)) return 1; @@ -512,6 +608,10 @@ int main(const int argc, char *const argv[], char *const *const envp) LANDLOCK_ACCESS_NET_CONNECT_TCP)) { goto err_close_ruleset; } + if (populate_ruleset_socket(ENV_SOCKET_CREATE_NAME, ruleset_fd, + LANDLOCK_ACCESS_SOCKET_CREATE)) { + goto err_close_ruleset; + } if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { perror("Failed to restrict privileges"); -- 2.34.1 Extend documentation with socket rule type description. Signed-off-by: Mikhail Ivanov --- Changes since v3: * Fixes identantion. --- Documentation/userspace-api/landlock.rst | 48 ++++++++++++++++++++---- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst index 1d0c2c15c22e..49fdc897db24 100644 --- a/Documentation/userspace-api/landlock.rst +++ b/Documentation/userspace-api/landlock.rst @@ -8,7 +8,7 @@ Landlock: unprivileged access control ===================================== :Author: Mickaël Salaün -:Date: March 2025 +:Date: November 2025 The goal of Landlock is to enable restriction of ambient rights (e.g. global filesystem or network access) for a set of processes. Because Landlock @@ -33,7 +33,7 @@ A Landlock rule describes an action on an object which the process intends to perform. A set of rules is aggregated in a ruleset, which can then restrict the thread enforcing it, and its future children. -The two existing types of rules are: +The three existing types of rules are: Filesystem rules For these rules, the object is a file hierarchy, @@ -44,14 +44,18 @@ Network rules (since ABI v4) For these rules, the object is a TCP port, and the related actions are defined with `network access rights`. +Socket rules (since ABI v8) + For these rules, the object is a pair of an address family and a socket type, + and the related actions are defined with `socket access rights`. + Defining and enforcing a security policy ---------------------------------------- We first need to define the ruleset that will contain our rules. For this example, the ruleset will contain rules that only allow filesystem -read actions and establish a specific TCP connection. Filesystem write -actions and other TCP actions will be denied. +read actions, create TCP sockets and establish a specific TCP connection. +Filesystem write actions, non-TCP sockets creation other TCP actions will be denied. The ruleset then needs to handle both these kinds of actions. This is required for backward and forward compatibility (i.e. the kernel and user @@ -81,6 +85,8 @@ to be explicit about the denied-by-default access rights. .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP | LANDLOCK_ACCESS_NET_CONNECT_TCP, + .handled_access_socket = + LANDLOCK_ACCESS_SOCKET_CREATE, .scoped = LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | LANDLOCK_SCOPE_SIGNAL, @@ -127,6 +133,11 @@ version, and only use the available subset of access rights: /* Removes LANDLOCK_SCOPE_* for ABI < 6 */ ruleset_attr.scoped &= ~(LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | LANDLOCK_SCOPE_SIGNAL); + case 6: + case 7: + /* Removes LANDLOCK_ACCESS_SOCKET for ABI < 8 */ + ruleset_attr.handled_access_socket &= + ~LANDLOCK_ACCESS_SOCKET_CREATE; } This enables the creation of an inclusive ruleset that will contain our rules. @@ -178,6 +189,21 @@ for the ruleset creation, by filtering access rights according to the Landlock ABI version. In this example, this is not required because all of the requested ``allowed_access`` rights are already available in ABI 1. +For socket access-control, we can add a rule to allow TCP sockets creation. UNIX, +UDP/IP and other protocols will be denied by the ruleset. + +.. code-block:: c + + struct landlock_net_port_attr tcp_socket = { + .allowed_access = LANDLOCK_ACCESS_SOCKET_CREATE, + .family = AF_INET, + .type = SOCK_STREAM, + .protocol = 0, + }; + + err = landlock_add_rule(ruleset_fd, LANDLOCK_RULE_SOCKET, + &tcp_socket, 0); + For network access-control, we can add a set of rules that allow to use a port number for a specific action: HTTPS connections. @@ -194,7 +220,8 @@ number for a specific action: HTTPS connections. The next step is to restrict the current thread from gaining more privileges (e.g. through a SUID binary). We now have a ruleset with the first rule allowing read access to ``/usr`` while denying all other handled accesses for -the filesystem, and a second rule allowing HTTPS connections. +the filesystem, a second rule allowing TCP sockets and a third rule allowing +HTTPS connections. .. code-block:: c @@ -442,7 +469,7 @@ Access rights ------------- .. kernel-doc:: include/uapi/linux/landlock.h - :identifiers: fs_access net_access scope + :identifiers: fs_access net_access socket_access scope Creating a new ruleset ---------------------- @@ -461,7 +488,7 @@ Extending a ruleset .. kernel-doc:: include/uapi/linux/landlock.h :identifiers: landlock_rule_type landlock_path_beneath_attr - landlock_net_port_attr + landlock_net_port_attr landlock_socket_attr Enforcing a ruleset ------------------- @@ -604,6 +631,13 @@ Landlock audit events with the ``LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF``, sys_landlock_restrict_self(). See Documentation/admin-guide/LSM/landlock.rst for more details on audit. +Socket support (ABI < 8) +------------------------- + +Starting with the Landlock ABI version 8, it is now possible to restrict +creation of user space sockets to only a set of allowed protocols thanks +to the new ``LANDLOCK_ACCESS_SOCKET_CREATE`` access right. + .. _kernel_support: Kernel support -- 2.34.1