* [RFC PATCH v2 1/9] landlock: Refactor current_check_access_socket() access right check
2024-08-14 3:01 [RFC PATCH v2 0/9] Support TCP listen access-control Mikhail Ivanov
@ 2024-08-14 3:01 ` Mikhail Ivanov
2024-08-19 21:37 ` Günther Noack
2024-08-14 3:01 ` [RFC PATCH v2 2/9] landlock: Support TCP listen access-control Mikhail Ivanov
` (8 subsequent siblings)
9 siblings, 1 reply; 40+ messages in thread
From: Mikhail Ivanov @ 2024-08-14 3:01 UTC (permalink / raw)
To: mic
Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
netfilter-devel, yusongping, artem.kuzin, konstantin.meskhidze
The current_check_access_socket() function contains a set of address
validation checks for bind(2) and connect(2) hooks. Separate them from
an actual port access right checking. It is required for the (future)
hooks that do not perform address validation.
Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
---
security/landlock/net.c | 41 ++++++++++++++++++++++++-----------------
1 file changed, 24 insertions(+), 17 deletions(-)
diff --git a/security/landlock/net.c b/security/landlock/net.c
index c8bcd29bde09..669ba260342f 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -2,7 +2,7 @@
/*
* Landlock LSM - Network management and hooks
*
- * Copyright © 2022-2023 Huawei Tech. Co., Ltd.
+ * Copyright © 2022-2024 Huawei Tech. Co., Ltd.
* Copyright © 2022-2023 Microsoft Corporation
*/
@@ -61,17 +61,34 @@ static const struct landlock_ruleset *get_current_net_domain(void)
return dom;
}
-static int current_check_access_socket(struct socket *const sock,
- struct sockaddr *const address,
- const int addrlen,
- access_mask_t access_request)
+static int check_access_socket(const struct landlock_ruleset *const dom,
+ __be16 port, access_mask_t access_request)
{
- __be16 port;
layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {};
const struct landlock_rule *rule;
struct landlock_id id = {
.type = LANDLOCK_KEY_NET_PORT,
};
+
+ id.key.data = (__force uintptr_t)port;
+ BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
+
+ rule = landlock_find_rule(dom, id);
+ access_request = landlock_init_layer_masks(
+ dom, access_request, &layer_masks, LANDLOCK_KEY_NET_PORT);
+ if (landlock_unmask_layers(rule, access_request, &layer_masks,
+ ARRAY_SIZE(layer_masks)))
+ return 0;
+
+ return -EACCES;
+}
+
+static int current_check_access_socket(struct socket *const sock,
+ struct sockaddr *const address,
+ const int addrlen,
+ access_mask_t access_request)
+{
+ __be16 port;
const struct landlock_ruleset *const dom = get_current_net_domain();
if (!dom)
@@ -159,17 +176,7 @@ static int current_check_access_socket(struct socket *const sock,
return -EINVAL;
}
- id.key.data = (__force uintptr_t)port;
- BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
-
- rule = landlock_find_rule(dom, id);
- access_request = landlock_init_layer_masks(
- dom, access_request, &layer_masks, LANDLOCK_KEY_NET_PORT);
- if (landlock_unmask_layers(rule, access_request, &layer_masks,
- ARRAY_SIZE(layer_masks)))
- return 0;
-
- return -EACCES;
+ return check_access_socket(dom, port, access_request);
}
static int hook_socket_bind(struct socket *const sock,
--
2.34.1
^ permalink raw reply related [flat|nested] 40+ messages in thread
* Re: [RFC PATCH v2 1/9] landlock: Refactor current_check_access_socket() access right check
2024-08-14 3:01 ` [RFC PATCH v2 1/9] landlock: Refactor current_check_access_socket() access right check Mikhail Ivanov
@ 2024-08-19 21:37 ` Günther Noack
2024-08-20 11:20 ` Mikhail Ivanov
0 siblings, 1 reply; 40+ messages in thread
From: Günther Noack @ 2024-08-19 21:37 UTC (permalink / raw)
To: Mikhail Ivanov
Cc: mic, willemdebruijn.kernel, gnoack3000, linux-security-module,
netdev, netfilter-devel, yusongping, artem.kuzin,
konstantin.meskhidze
Hello!
Thanks for sending round 2 of this patch set!
On Wed, Aug 14, 2024 at 11:01:43AM +0800, Mikhail Ivanov wrote:
> The current_check_access_socket() function contains a set of address
> validation checks for bind(2) and connect(2) hooks. Separate them from
> an actual port access right checking. It is required for the (future)
> hooks that do not perform address validation.
>
> Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
> ---
> security/landlock/net.c | 41 ++++++++++++++++++++++++-----------------
> 1 file changed, 24 insertions(+), 17 deletions(-)
>
> diff --git a/security/landlock/net.c b/security/landlock/net.c
> index c8bcd29bde09..669ba260342f 100644
> --- a/security/landlock/net.c
> +++ b/security/landlock/net.c
> @@ -2,7 +2,7 @@
> /*
> * Landlock LSM - Network management and hooks
> *
> - * Copyright © 2022-2023 Huawei Tech. Co., Ltd.
> + * Copyright © 2022-2024 Huawei Tech. Co., Ltd.
> * Copyright © 2022-2023 Microsoft Corporation
> */
>
> @@ -61,17 +61,34 @@ static const struct landlock_ruleset *get_current_net_domain(void)
> return dom;
> }
>
> -static int current_check_access_socket(struct socket *const sock,
> - struct sockaddr *const address,
> - const int addrlen,
> - access_mask_t access_request)
> +static int check_access_socket(const struct landlock_ruleset *const dom,
> + __be16 port, access_mask_t access_request)
It might be worth briefly spelling out in documentation that access_request in
current_check_access_socket() may only have a single bit set. This is different
to other places where access_mask_t is used, where combinations of these flags
are possible.
These functions do checks for special cases using "if (access_request ==
LANDLOCK_ACCESS_NET_CONNECT_TCP)" and the same for "bind". I think it's a
reasonable way to simplify the implementation here, but we have to be careful to
not accidentally use it differently.
It is a preexisting issue, so I don't consider it a blocker, but it might be
worth fixing while we are at it?
> {
> - __be16 port;
> layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {};
> const struct landlock_rule *rule;
> struct landlock_id id = {
> .type = LANDLOCK_KEY_NET_PORT,
> };
> +
> + id.key.data = (__force uintptr_t)port;
> + BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
> +
> + rule = landlock_find_rule(dom, id);
> + access_request = landlock_init_layer_masks(
> + dom, access_request, &layer_masks, LANDLOCK_KEY_NET_PORT);
> + if (landlock_unmask_layers(rule, access_request, &layer_masks,
> + ARRAY_SIZE(layer_masks)))
> + return 0;
> +
> + return -EACCES;
> +}
> +
> +static int current_check_access_socket(struct socket *const sock,
Re-reading the implementation of this function, it was surprised how specialized
it is towards the "connect" and "bind" use cases, which it has specific code
paths for. This does not look like it would extend naturally to additional
operations.
After your refactoring, current_check_access_socket() is now (a) checking that
we are looking at a TCP address, and extracting the port, and then (b) doing
connect- and bind-specific logic, and then (c) calling check_access_socket().
Would it maybe be possible to turn the code logic around by creating a
"get_tcp_port()" helper function for step (a), and then doing all of (a), (b)
and (c) directly from hook_socket_bind() and hook_socket_connect()? It would
have the upside that in step (b) you don't need to distinguish between bind and
connect because it would be clear from the context which of the two cases we are
in. It would also remove the need for a function that only supports one bit in
the access_mask_t, which is potentially surprising.
Thanks,
—Günther
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [RFC PATCH v2 1/9] landlock: Refactor current_check_access_socket() access right check
2024-08-19 21:37 ` Günther Noack
@ 2024-08-20 11:20 ` Mikhail Ivanov
0 siblings, 0 replies; 40+ messages in thread
From: Mikhail Ivanov @ 2024-08-20 11:20 UTC (permalink / raw)
To: Günther Noack
Cc: mic, willemdebruijn.kernel, gnoack3000, linux-security-module,
netdev, netfilter-devel, yusongping, artem.kuzin,
konstantin.meskhidze
8/20/2024 12:37 AM, Günther Noack wrote:
> Hello!
>
> Thanks for sending round 2 of this patch set!
>
> On Wed, Aug 14, 2024 at 11:01:43AM +0800, Mikhail Ivanov wrote:
>> The current_check_access_socket() function contains a set of address
>> validation checks for bind(2) and connect(2) hooks. Separate them from
>> an actual port access right checking. It is required for the (future)
>> hooks that do not perform address validation.
>>
>> Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
>> ---
>> security/landlock/net.c | 41 ++++++++++++++++++++++++-----------------
>> 1 file changed, 24 insertions(+), 17 deletions(-)
>>
>> diff --git a/security/landlock/net.c b/security/landlock/net.c
>> index c8bcd29bde09..669ba260342f 100644
>> --- a/security/landlock/net.c
>> +++ b/security/landlock/net.c
>> @@ -2,7 +2,7 @@
>> /*
>> * Landlock LSM - Network management and hooks
>> *
>> - * Copyright © 2022-2023 Huawei Tech. Co., Ltd.
>> + * Copyright © 2022-2024 Huawei Tech. Co., Ltd.
>> * Copyright © 2022-2023 Microsoft Corporation
>> */
>>
>> @@ -61,17 +61,34 @@ static const struct landlock_ruleset *get_current_net_domain(void)
>> return dom;
>> }
>>
>> -static int current_check_access_socket(struct socket *const sock,
>> - struct sockaddr *const address,
>> - const int addrlen,
>> - access_mask_t access_request)
>> +static int check_access_socket(const struct landlock_ruleset *const dom,
>> + __be16 port, access_mask_t access_request)
>
> It might be worth briefly spelling out in documentation that access_request in
> current_check_access_socket() may only have a single bit set. This is different
> to other places where access_mask_t is used, where combinations of these flags
> are possible.
>
> These functions do checks for special cases using "if (access_request ==
> LANDLOCK_ACCESS_NET_CONNECT_TCP)" and the same for "bind". I think it's a
> reasonable way to simplify the implementation here, but we have to be careful to
> not accidentally use it differently.
>
> It is a preexisting issue, so I don't consider it a blocker, but it might be
> worth fixing while we are at it?
I think such comment is not required if we remove
"current_check_access_socket()" as you suggest? In this function
access_request can be a mask with multiple access rights included.
>
>
>> {
>> - __be16 port;
>> layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET] = {};
>> const struct landlock_rule *rule;
>> struct landlock_id id = {
>> .type = LANDLOCK_KEY_NET_PORT,
>> };
>> +
>> + id.key.data = (__force uintptr_t)port;
>> + BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data));
>> +
>> + rule = landlock_find_rule(dom, id);
>> + access_request = landlock_init_layer_masks(
>> + dom, access_request, &layer_masks, LANDLOCK_KEY_NET_PORT);
>> + if (landlock_unmask_layers(rule, access_request, &layer_masks,
>> + ARRAY_SIZE(layer_masks)))
>> + return 0;
>> +
>> + return -EACCES;
>> +}
>> +
>> +static int current_check_access_socket(struct socket *const sock,
>
> Re-reading the implementation of this function, it was surprised how specialized
> it is towards the "connect" and "bind" use cases, which it has specific code
> paths for. This does not look like it would extend naturally to additional
> operations.
>
> After your refactoring, current_check_access_socket() is now (a) checking that
> we are looking at a TCP address, and extracting the port, and then (b) doing
> connect- and bind-specific logic, and then (c) calling check_access_socket().
>
> Would it maybe be possible to turn the code logic around by creating a
> "get_tcp_port()" helper function for step (a), and then doing all of (a), (b)
> and (c) directly from hook_socket_bind() and hook_socket_connect()? It would
> have the upside that in step (b) you don't need to distinguish between bind and
> connect because it would be clear from the context which of the two cases we are
> in. It would also remove the need for a function that only supports one bit in
> the access_mask_t, which is potentially surprising.
>
> Thanks,
> —Günther
>
Good idea! But I suggest using "get_port_from_addr_tcp()" naming to
distinguish between extracting a port from an address structure and
from a socket (as performed in hook_listen() in the next patch).
^ permalink raw reply [flat|nested] 40+ messages in thread
* [RFC PATCH v2 2/9] landlock: Support TCP listen access-control
2024-08-14 3:01 [RFC PATCH v2 0/9] Support TCP listen access-control Mikhail Ivanov
2024-08-14 3:01 ` [RFC PATCH v2 1/9] landlock: Refactor current_check_access_socket() access right check Mikhail Ivanov
@ 2024-08-14 3:01 ` Mikhail Ivanov
2024-10-05 16:56 ` Günther Noack
2024-08-14 3:01 ` [RFC PATCH v2 3/9] selftests/landlock: Support LANDLOCK_ACCESS_NET_LISTEN_TCP Mikhail Ivanov
` (7 subsequent siblings)
9 siblings, 1 reply; 40+ messages in thread
From: Mikhail Ivanov @ 2024-08-14 3:01 UTC (permalink / raw)
To: mic
Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
netfilter-devel, yusongping, artem.kuzin, konstantin.meskhidze
LANDLOCK_ACCESS_NET_BIND_TCP is useful to limit the scope of "bindable"
ports to forbid a malicious sandboxed process to impersonate a legitimate
server process. However, bind(2) might be used by (TCP) clients to set the
source port to a (legitimate) value. Controlling the ports that can be
used for listening would allow (TCP) clients to explicitly bind to ports
that are forbidden for listening.
Such control is implemented with a new LANDLOCK_ACCESS_NET_LISTEN_TCP
access right that restricts listening on undesired ports with listen(2).
It's worth noticing that this access right doesn't affect changing
backlog value using listen(2) on already listening socket.
* Create new LANDLOCK_ACCESS_NET_LISTEN_TCP flag.
* Add hook to socket_listen(), which checks whether the socket is allowed
to listen on a binded local port.
* Add check_tcp_socket_can_listen() helper, which validates socket
attributes before the actual access right check.
* Update `struct landlock_net_port_attr` documentation with control of
binding to ephemeral port with listen(2) description.
* Change ABI version to 6.
Closes: https://github.com/landlock-lsm/linux/issues/15
Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
---
Changes since v1:
* Refactors 'struct landlock_net_port_attr' documentation.
* Fixes check_tcp_socket_can_listen() description.
* Adds lockdep_assert_held() into check_tcp_socket_can_listen().
* Minor fixes.
---
include/uapi/linux/landlock.h | 26 ++++--
security/landlock/limits.h | 2 +-
security/landlock/net.c | 98 ++++++++++++++++++++
security/landlock/syscalls.c | 2 +-
tools/testing/selftests/landlock/base_test.c | 2 +-
5 files changed, 119 insertions(+), 11 deletions(-)
diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
index 2c8dbc74b955..f7dd6949c50b 100644
--- a/include/uapi/linux/landlock.h
+++ b/include/uapi/linux/landlock.h
@@ -111,14 +111,20 @@ struct landlock_net_port_attr {
/**
* @port: Network port in host endianness.
*
- * It should be noted that port 0 passed to :manpage:`bind(2)` will bind
- * to an available port from the ephemeral port range. This can be
- * configured with the ``/proc/sys/net/ipv4/ip_local_port_range`` sysctl
- * (also used for IPv6).
+ * Some socket operations will fall back to using a port from the ephemeral port
+ * range, if no specific port is requested by the caller. Among others, this
+ * happens in the following cases:
*
- * A Landlock rule with port 0 and the ``LANDLOCK_ACCESS_NET_BIND_TCP``
- * right means that requesting to bind on port 0 is allowed and it will
- * automatically translate to binding on the related port range.
+ * - :manpage:`bind(2)` is invoked with a socket address that uses port 0.
+ * - :manpage:`listen(2)` is invoked on a socket without previously calling
+ * :manpage:`bind(2)`.
+ *
+ * These two actions, which implicitly use an ephemeral port, can be allowed with
+ * a Landlock rule with port 0 and the ``LANDLOCK_ACCESS_NET_BIND_TCP`` /
+ * ``LANDLOCK_ACCESS_NET_LISTEN_TCP`` right.
+ *
+ * The ephemeral port range is configured in the
+ * ``/proc/sys/net/ipv4/ip_local_port_range`` sysctl (also used for IPv6).
*/
__u64 port;
};
@@ -259,7 +265,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. This is supported since the Landlock ABI version 4.
@@ -269,9 +275,13 @@ struct landlock_net_port_attr {
* - %LANDLOCK_ACCESS_NET_BIND_TCP: Bind a TCP socket to a local port.
* - %LANDLOCK_ACCESS_NET_CONNECT_TCP: Connect an active TCP socket to
* a remote port.
+ * - %LANDLOCK_ACCESS_NET_LISTEN_TCP: Listen for TCP socket connections on
+ * a local port. This access right is available since the sixth version
+ * of the Landlock ABI.
*/
/* clang-format off */
#define LANDLOCK_ACCESS_NET_BIND_TCP (1ULL << 0)
#define LANDLOCK_ACCESS_NET_CONNECT_TCP (1ULL << 1)
+#define LANDLOCK_ACCESS_NET_LISTEN_TCP (1ULL << 2)
/* clang-format on */
#endif /* _UAPI_LINUX_LANDLOCK_H */
diff --git a/security/landlock/limits.h b/security/landlock/limits.h
index 4eb643077a2a..2ef147389474 100644
--- a/security/landlock/limits.h
+++ b/security/landlock/limits.h
@@ -22,7 +22,7 @@
#define LANDLOCK_MASK_ACCESS_FS ((LANDLOCK_LAST_ACCESS_FS << 1) - 1)
#define LANDLOCK_NUM_ACCESS_FS __const_hweight64(LANDLOCK_MASK_ACCESS_FS)
-#define LANDLOCK_LAST_ACCESS_NET LANDLOCK_ACCESS_NET_CONNECT_TCP
+#define LANDLOCK_LAST_ACCESS_NET LANDLOCK_ACCESS_NET_LISTEN_TCP
#define LANDLOCK_MASK_ACCESS_NET ((LANDLOCK_LAST_ACCESS_NET << 1) - 1)
#define LANDLOCK_NUM_ACCESS_NET __const_hweight64(LANDLOCK_MASK_ACCESS_NET)
diff --git a/security/landlock/net.c b/security/landlock/net.c
index 669ba260342f..0e494b46d086 100644
--- a/security/landlock/net.c
+++ b/security/landlock/net.c
@@ -6,10 +6,12 @@
* Copyright © 2022-2023 Microsoft Corporation
*/
+#include <net/sock.h>
#include <linux/in.h>
#include <linux/net.h>
#include <linux/socket.h>
#include <net/ipv6.h>
+#include <net/tcp.h>
#include "common.h"
#include "cred.h"
@@ -194,9 +196,105 @@ static int hook_socket_connect(struct socket *const sock,
LANDLOCK_ACCESS_NET_CONNECT_TCP);
}
+/*
+ * Checks that socket state and attributes are correct for listen.
+ * Returns 0 on success and -EINVAL otherwise.
+ *
+ * This checker requires sock->sk to be locked.
+ */
+static int check_tcp_socket_can_listen(struct socket *const sock)
+{
+ struct sock *sk = sock->sk;
+ unsigned char cur_sk_state;
+ const struct tcp_ulp_ops *icsk_ulp_ops;
+
+ lockdep_assert_held(&sk->sk_lock.slock);
+
+ /* Allows only unconnected TCP socket to listen (cf. inet_listen). */
+ if (sock->state != SS_UNCONNECTED)
+ return -EINVAL;
+
+ cur_sk_state = sk->sk_state;
+ /*
+ * Checks sock state. This is needed to ensure consistency with inet stack
+ * error handling (cf. __inet_listen_sk).
+ */
+ if (WARN_ON_ONCE(!((1 << cur_sk_state) & (TCPF_CLOSE | TCPF_LISTEN))))
+ return -EINVAL;
+
+ icsk_ulp_ops = inet_csk(sk)->icsk_ulp_ops;
+
+ /*
+ * ULP (Upper Layer Protocol) stands for protocols which are higher than
+ * transport protocol in OSI model. Linux has an infrastructure that
+ * allows TCP sockets to support logic of some ULP (e.g. TLS ULP).
+ *
+ * Sockets can listen only if ULP control hook has clone method
+ * (cf. inet_csk_listen_start)
+ */
+ if (icsk_ulp_ops && !icsk_ulp_ops->clone)
+ return -EINVAL;
+ return 0;
+}
+
+static int hook_socket_listen(struct socket *const sock, const int backlog)
+{
+ int err = 0;
+ int family;
+ __be16 port;
+ struct sock *sk;
+ const struct landlock_ruleset *const dom = get_current_net_domain();
+
+ if (!dom)
+ return 0;
+ if (WARN_ON_ONCE(dom->num_layers < 1))
+ return -EACCES;
+
+ /* Checks if it's a (potential) TCP socket. */
+ if (sock->type != SOCK_STREAM)
+ return 0;
+
+ sk = sock->sk;
+ family = sk->__sk_common.skc_family;
+ /*
+ * Socket cannot be assigned AF_UNSPEC because this type is used only
+ * in the context of addresses.
+ *
+ * Doesn't restrict listening for non-TCP sockets.
+ */
+ if (family != AF_INET && family != AF_INET6)
+ return 0;
+
+ lock_sock(sk);
+ /*
+ * Calling listen(2) for a listening socket does nothing with its state and
+ * only changes backlog value (cf. __inet_listen_sk). Checking of listen
+ * access right is not required.
+ */
+ if (sk->sk_state == TCP_LISTEN)
+ goto release_nocheck;
+
+ /*
+ * Checks socket state to not wrongfully return -EACCES instead
+ * of -EINVAL.
+ */
+ err = check_tcp_socket_can_listen(sock);
+ if (unlikely(err))
+ goto release_nocheck;
+
+ port = htons(inet_sk(sk)->inet_num);
+ release_sock(sk);
+ return check_access_socket(dom, port, LANDLOCK_ACCESS_NET_LISTEN_TCP);
+
+release_nocheck:
+ release_sock(sk);
+ return err;
+}
+
static struct security_hook_list landlock_hooks[] __ro_after_init = {
LSM_HOOK_INIT(socket_bind, hook_socket_bind),
LSM_HOOK_INIT(socket_connect, hook_socket_connect),
+ LSM_HOOK_INIT(socket_listen, hook_socket_listen),
};
__init void landlock_add_net_hooks(void)
diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
index ccc8bc6c1584..328198e8a9f5 100644
--- a/security/landlock/syscalls.c
+++ b/security/landlock/syscalls.c
@@ -149,7 +149,7 @@ static const struct file_operations ruleset_fops = {
.write = fop_dummy_write,
};
-#define LANDLOCK_ABI_VERSION 5
+#define LANDLOCK_ABI_VERSION 6
/**
* sys_landlock_create_ruleset - Create a new ruleset
diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
index 3b26bf3cf5b9..1bc16fde2e8a 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(5, landlock_create_ruleset(NULL, 0,
+ ASSERT_EQ(6, landlock_create_ruleset(NULL, 0,
LANDLOCK_CREATE_RULESET_VERSION));
ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
--
2.34.1
^ permalink raw reply related [flat|nested] 40+ messages in thread
* Re: [RFC PATCH v2 2/9] landlock: Support TCP listen access-control
2024-08-14 3:01 ` [RFC PATCH v2 2/9] landlock: Support TCP listen access-control Mikhail Ivanov
@ 2024-10-05 16:56 ` Günther Noack
2024-10-05 17:53 ` Mikhail Ivanov
0 siblings, 1 reply; 40+ messages in thread
From: Günther Noack @ 2024-10-05 16:56 UTC (permalink / raw)
To: Mikhail Ivanov
Cc: mic, willemdebruijn.kernel, linux-security-module, netdev,
netfilter-devel, yusongping, artem.kuzin, konstantin.meskhidze
Hello!
On Wed, Aug 14, 2024 at 11:01:44AM +0800, Mikhail Ivanov wrote:
> LANDLOCK_ACCESS_NET_BIND_TCP is useful to limit the scope of "bindable"
> ports to forbid a malicious sandboxed process to impersonate a legitimate
> server process. However, bind(2) might be used by (TCP) clients to set the
> source port to a (legitimate) value. Controlling the ports that can be
> used for listening would allow (TCP) clients to explicitly bind to ports
> that are forbidden for listening.
>
> Such control is implemented with a new LANDLOCK_ACCESS_NET_LISTEN_TCP
> access right that restricts listening on undesired ports with listen(2).
>
> It's worth noticing that this access right doesn't affect changing
> backlog value using listen(2) on already listening socket.
>
> * Create new LANDLOCK_ACCESS_NET_LISTEN_TCP flag.
> * Add hook to socket_listen(), which checks whether the socket is allowed
> to listen on a binded local port.
> * Add check_tcp_socket_can_listen() helper, which validates socket
> attributes before the actual access right check.
> * Update `struct landlock_net_port_attr` documentation with control of
> binding to ephemeral port with listen(2) description.
> * Change ABI version to 6.
>
> Closes: https://github.com/landlock-lsm/linux/issues/15
> Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
> ---
>
> Changes since v1:
> * Refactors 'struct landlock_net_port_attr' documentation.
> * Fixes check_tcp_socket_can_listen() description.
> * Adds lockdep_assert_held() into check_tcp_socket_can_listen().
> * Minor fixes.
> ---
> include/uapi/linux/landlock.h | 26 ++++--
> security/landlock/limits.h | 2 +-
> security/landlock/net.c | 98 ++++++++++++++++++++
> security/landlock/syscalls.c | 2 +-
> tools/testing/selftests/landlock/base_test.c | 2 +-
> 5 files changed, 119 insertions(+), 11 deletions(-)
>
> diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
> index 2c8dbc74b955..f7dd6949c50b 100644
> --- a/include/uapi/linux/landlock.h
> +++ b/include/uapi/linux/landlock.h
> @@ -111,14 +111,20 @@ struct landlock_net_port_attr {
> /**
> * @port: Network port in host endianness.
> *
> - * It should be noted that port 0 passed to :manpage:`bind(2)` will bind
> - * to an available port from the ephemeral port range. This can be
> - * configured with the ``/proc/sys/net/ipv4/ip_local_port_range`` sysctl
> - * (also used for IPv6).
> + * Some socket operations will fall back to using a port from the ephemeral port
> + * range, if no specific port is requested by the caller. Among others, this
> + * happens in the following cases:
> *
> - * A Landlock rule with port 0 and the ``LANDLOCK_ACCESS_NET_BIND_TCP``
> - * right means that requesting to bind on port 0 is allowed and it will
> - * automatically translate to binding on the related port range.
> + * - :manpage:`bind(2)` is invoked with a socket address that uses port 0.
> + * - :manpage:`listen(2)` is invoked on a socket without previously calling
> + * :manpage:`bind(2)`.
> + *
> + * These two actions, which implicitly use an ephemeral port, can be allowed with
> + * a Landlock rule with port 0 and the ``LANDLOCK_ACCESS_NET_BIND_TCP`` /
> + * ``LANDLOCK_ACCESS_NET_LISTEN_TCP`` right.
> + *
> + * The ephemeral port range is configured in the
> + * ``/proc/sys/net/ipv4/ip_local_port_range`` sysctl (also used for IPv6).
> */
> __u64 port;
> };
> @@ -259,7 +265,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. This is supported since the Landlock ABI version 4.
> @@ -269,9 +275,13 @@ struct landlock_net_port_attr {
> * - %LANDLOCK_ACCESS_NET_BIND_TCP: Bind a TCP socket to a local port.
> * - %LANDLOCK_ACCESS_NET_CONNECT_TCP: Connect an active TCP socket to
> * a remote port.
> + * - %LANDLOCK_ACCESS_NET_LISTEN_TCP: Listen for TCP socket connections on
> + * a local port. This access right is available since the sixth version
> + * of the Landlock ABI.
> */
> /* clang-format off */
> #define LANDLOCK_ACCESS_NET_BIND_TCP (1ULL << 0)
> #define LANDLOCK_ACCESS_NET_CONNECT_TCP (1ULL << 1)
> +#define LANDLOCK_ACCESS_NET_LISTEN_TCP (1ULL << 2)
> /* clang-format on */
> #endif /* _UAPI_LINUX_LANDLOCK_H */
> diff --git a/security/landlock/limits.h b/security/landlock/limits.h
> index 4eb643077a2a..2ef147389474 100644
> --- a/security/landlock/limits.h
> +++ b/security/landlock/limits.h
> @@ -22,7 +22,7 @@
> #define LANDLOCK_MASK_ACCESS_FS ((LANDLOCK_LAST_ACCESS_FS << 1) - 1)
> #define LANDLOCK_NUM_ACCESS_FS __const_hweight64(LANDLOCK_MASK_ACCESS_FS)
>
> -#define LANDLOCK_LAST_ACCESS_NET LANDLOCK_ACCESS_NET_CONNECT_TCP
> +#define LANDLOCK_LAST_ACCESS_NET LANDLOCK_ACCESS_NET_LISTEN_TCP
> #define LANDLOCK_MASK_ACCESS_NET ((LANDLOCK_LAST_ACCESS_NET << 1) - 1)
> #define LANDLOCK_NUM_ACCESS_NET __const_hweight64(LANDLOCK_MASK_ACCESS_NET)
>
> diff --git a/security/landlock/net.c b/security/landlock/net.c
> index 669ba260342f..0e494b46d086 100644
> --- a/security/landlock/net.c
> +++ b/security/landlock/net.c
> @@ -6,10 +6,12 @@
> * Copyright © 2022-2023 Microsoft Corporation
> */
>
> +#include <net/sock.h>
> #include <linux/in.h>
> #include <linux/net.h>
> #include <linux/socket.h>
> #include <net/ipv6.h>
> +#include <net/tcp.h>
>
> #include "common.h"
> #include "cred.h"
> @@ -194,9 +196,105 @@ static int hook_socket_connect(struct socket *const sock,
> LANDLOCK_ACCESS_NET_CONNECT_TCP);
> }
>
> +/*
> + * Checks that socket state and attributes are correct for listen.
> + * Returns 0 on success and -EINVAL otherwise.
> + *
> + * This checker requires sock->sk to be locked.
> + */
> +static int check_tcp_socket_can_listen(struct socket *const sock)
> +{
> + struct sock *sk = sock->sk;
> + unsigned char cur_sk_state;
> + const struct tcp_ulp_ops *icsk_ulp_ops;
> +
> + lockdep_assert_held(&sk->sk_lock.slock);
> +
> + /* Allows only unconnected TCP socket to listen (cf. inet_listen). */
> + if (sock->state != SS_UNCONNECTED)
> + return -EINVAL;
> +
> + cur_sk_state = sk->sk_state;
> + /*
> + * Checks sock state. This is needed to ensure consistency with inet stack
> + * error handling (cf. __inet_listen_sk).
> + */
> + if (WARN_ON_ONCE(!((1 << cur_sk_state) & (TCPF_CLOSE | TCPF_LISTEN))))
> + return -EINVAL;
> +
> + icsk_ulp_ops = inet_csk(sk)->icsk_ulp_ops;
> +
> + /*
> + * ULP (Upper Layer Protocol) stands for protocols which are higher than
> + * transport protocol in OSI model. Linux has an infrastructure that
> + * allows TCP sockets to support logic of some ULP (e.g. TLS ULP).
> + *
> + * Sockets can listen only if ULP control hook has clone method
> + * (cf. inet_csk_listen_start)
> + */
> + if (icsk_ulp_ops && !icsk_ulp_ops->clone)
> + return -EINVAL;
> + return 0;
> +}
> +
> +static int hook_socket_listen(struct socket *const sock, const int backlog)
> +{
> + int err = 0;
> + int family;
> + __be16 port;
> + struct sock *sk;
> + const struct landlock_ruleset *const dom = get_current_net_domain();
> +
> + if (!dom)
> + return 0;
> + if (WARN_ON_ONCE(dom->num_layers < 1))
> + return -EACCES;
> +
> + /* Checks if it's a (potential) TCP socket. */
> + if (sock->type != SOCK_STREAM)
> + return 0;
> +
> + sk = sock->sk;
> + family = sk->__sk_common.skc_family;
> + /*
> + * Socket cannot be assigned AF_UNSPEC because this type is used only
> + * in the context of addresses.
> + *
> + * Doesn't restrict listening for non-TCP sockets.
> + */
> + if (family != AF_INET && family != AF_INET6)
> + return 0;
I imagine that we'll need the "protocol" comparison as well, in line
with your fix for the bind() and connect() functionality at
https://lore.kernel.org/all/20241003143932.2431249-1-ivanov.mikhail1@huawei-partners.com/
?
> +
> + lock_sock(sk);
The socket gets locked twice when doing listen() -- first it is locked
by the security hook, then released again, then locked again by the
actual listen() implementation and then released again.
What if the protected values change in between the two times when the
lock is held? What is the reasoning for why this is safe? (This
might be worth a comment in the code to explain, IMHO.)
> + /*
> + * Calling listen(2) for a listening socket does nothing with its state and
> + * only changes backlog value (cf. __inet_listen_sk). Checking of listen
> + * access right is not required.
> + */
> + if (sk->sk_state == TCP_LISTEN)
> + goto release_nocheck;
> +
> + /*
> + * Checks socket state to not wrongfully return -EACCES instead
> + * of -EINVAL.
> + */
> + err = check_tcp_socket_can_listen(sock);
> + if (unlikely(err))
> + goto release_nocheck;
> +
> + port = htons(inet_sk(sk)->inet_num);
> + release_sock(sk);
> + return check_access_socket(dom, port, LANDLOCK_ACCESS_NET_LISTEN_TCP);
Nit: The last two lines could just be
err = check_access_socket(...);
and then you would only need the release_sock(sk) call in one place.
(And maybe rename the goto label accordingly.)
> +
> +release_nocheck:
> + release_sock(sk);
> + return err;
> +}
> +
> static struct security_hook_list landlock_hooks[] __ro_after_init = {
> LSM_HOOK_INIT(socket_bind, hook_socket_bind),
> LSM_HOOK_INIT(socket_connect, hook_socket_connect),
> + LSM_HOOK_INIT(socket_listen, hook_socket_listen),
> };
>
> __init void landlock_add_net_hooks(void)
> diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
> index ccc8bc6c1584..328198e8a9f5 100644
> --- a/security/landlock/syscalls.c
> +++ b/security/landlock/syscalls.c
> @@ -149,7 +149,7 @@ static const struct file_operations ruleset_fops = {
> .write = fop_dummy_write,
> };
>
> -#define LANDLOCK_ABI_VERSION 5
> +#define LANDLOCK_ABI_VERSION 6
>
> /**
> * sys_landlock_create_ruleset - Create a new ruleset
> diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
> index 3b26bf3cf5b9..1bc16fde2e8a 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(5, landlock_create_ruleset(NULL, 0,
> + ASSERT_EQ(6, landlock_create_ruleset(NULL, 0,
> LANDLOCK_CREATE_RULESET_VERSION));
>
> ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
> --
> 2.34.1
>
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [RFC PATCH v2 2/9] landlock: Support TCP listen access-control
2024-10-05 16:56 ` Günther Noack
@ 2024-10-05 17:53 ` Mikhail Ivanov
2024-10-05 18:22 ` Günther Noack
0 siblings, 1 reply; 40+ messages in thread
From: Mikhail Ivanov @ 2024-10-05 17:53 UTC (permalink / raw)
To: Günther Noack
Cc: mic, willemdebruijn.kernel, linux-security-module, netdev,
netfilter-devel, yusongping, artem.kuzin, konstantin.meskhidze
On 10/5/2024 7:56 PM, Günther Noack wrote:
> Hello!
>
> On Wed, Aug 14, 2024 at 11:01:44AM +0800, Mikhail Ivanov wrote:
>> LANDLOCK_ACCESS_NET_BIND_TCP is useful to limit the scope of "bindable"
>> ports to forbid a malicious sandboxed process to impersonate a legitimate
>> server process. However, bind(2) might be used by (TCP) clients to set the
>> source port to a (legitimate) value. Controlling the ports that can be
>> used for listening would allow (TCP) clients to explicitly bind to ports
>> that are forbidden for listening.
>>
>> Such control is implemented with a new LANDLOCK_ACCESS_NET_LISTEN_TCP
>> access right that restricts listening on undesired ports with listen(2).
>>
>> It's worth noticing that this access right doesn't affect changing
>> backlog value using listen(2) on already listening socket.
>>
>> * Create new LANDLOCK_ACCESS_NET_LISTEN_TCP flag.
>> * Add hook to socket_listen(), which checks whether the socket is allowed
>> to listen on a binded local port.
>> * Add check_tcp_socket_can_listen() helper, which validates socket
>> attributes before the actual access right check.
>> * Update `struct landlock_net_port_attr` documentation with control of
>> binding to ephemeral port with listen(2) description.
>> * Change ABI version to 6.
>>
>> Closes: https://github.com/landlock-lsm/linux/issues/15
>> Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
>> ---
>>
>> Changes since v1:
>> * Refactors 'struct landlock_net_port_attr' documentation.
>> * Fixes check_tcp_socket_can_listen() description.
>> * Adds lockdep_assert_held() into check_tcp_socket_can_listen().
>> * Minor fixes.
>> ---
>> include/uapi/linux/landlock.h | 26 ++++--
>> security/landlock/limits.h | 2 +-
>> security/landlock/net.c | 98 ++++++++++++++++++++
>> security/landlock/syscalls.c | 2 +-
>> tools/testing/selftests/landlock/base_test.c | 2 +-
>> 5 files changed, 119 insertions(+), 11 deletions(-)
>>
>> diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h
>> index 2c8dbc74b955..f7dd6949c50b 100644
>> --- a/include/uapi/linux/landlock.h
>> +++ b/include/uapi/linux/landlock.h
>> @@ -111,14 +111,20 @@ struct landlock_net_port_attr {
>> /**
>> * @port: Network port in host endianness.
>> *
>> - * It should be noted that port 0 passed to :manpage:`bind(2)` will bind
>> - * to an available port from the ephemeral port range. This can be
>> - * configured with the ``/proc/sys/net/ipv4/ip_local_port_range`` sysctl
>> - * (also used for IPv6).
>> + * Some socket operations will fall back to using a port from the ephemeral port
>> + * range, if no specific port is requested by the caller. Among others, this
>> + * happens in the following cases:
>> *
>> - * A Landlock rule with port 0 and the ``LANDLOCK_ACCESS_NET_BIND_TCP``
>> - * right means that requesting to bind on port 0 is allowed and it will
>> - * automatically translate to binding on the related port range.
>> + * - :manpage:`bind(2)` is invoked with a socket address that uses port 0.
>> + * - :manpage:`listen(2)` is invoked on a socket without previously calling
>> + * :manpage:`bind(2)`.
>> + *
>> + * These two actions, which implicitly use an ephemeral port, can be allowed with
>> + * a Landlock rule with port 0 and the ``LANDLOCK_ACCESS_NET_BIND_TCP`` /
>> + * ``LANDLOCK_ACCESS_NET_LISTEN_TCP`` right.
>> + *
>> + * The ephemeral port range is configured in the
>> + * ``/proc/sys/net/ipv4/ip_local_port_range`` sysctl (also used for IPv6).
>> */
>> __u64 port;
>> };
>> @@ -259,7 +265,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. This is supported since the Landlock ABI version 4.
>> @@ -269,9 +275,13 @@ struct landlock_net_port_attr {
>> * - %LANDLOCK_ACCESS_NET_BIND_TCP: Bind a TCP socket to a local port.
>> * - %LANDLOCK_ACCESS_NET_CONNECT_TCP: Connect an active TCP socket to
>> * a remote port.
>> + * - %LANDLOCK_ACCESS_NET_LISTEN_TCP: Listen for TCP socket connections on
>> + * a local port. This access right is available since the sixth version
>> + * of the Landlock ABI.
>> */
>> /* clang-format off */
>> #define LANDLOCK_ACCESS_NET_BIND_TCP (1ULL << 0)
>> #define LANDLOCK_ACCESS_NET_CONNECT_TCP (1ULL << 1)
>> +#define LANDLOCK_ACCESS_NET_LISTEN_TCP (1ULL << 2)
>> /* clang-format on */
>> #endif /* _UAPI_LINUX_LANDLOCK_H */
>> diff --git a/security/landlock/limits.h b/security/landlock/limits.h
>> index 4eb643077a2a..2ef147389474 100644
>> --- a/security/landlock/limits.h
>> +++ b/security/landlock/limits.h
>> @@ -22,7 +22,7 @@
>> #define LANDLOCK_MASK_ACCESS_FS ((LANDLOCK_LAST_ACCESS_FS << 1) - 1)
>> #define LANDLOCK_NUM_ACCESS_FS __const_hweight64(LANDLOCK_MASK_ACCESS_FS)
>>
>> -#define LANDLOCK_LAST_ACCESS_NET LANDLOCK_ACCESS_NET_CONNECT_TCP
>> +#define LANDLOCK_LAST_ACCESS_NET LANDLOCK_ACCESS_NET_LISTEN_TCP
>> #define LANDLOCK_MASK_ACCESS_NET ((LANDLOCK_LAST_ACCESS_NET << 1) - 1)
>> #define LANDLOCK_NUM_ACCESS_NET __const_hweight64(LANDLOCK_MASK_ACCESS_NET)
>>
>> diff --git a/security/landlock/net.c b/security/landlock/net.c
>> index 669ba260342f..0e494b46d086 100644
>> --- a/security/landlock/net.c
>> +++ b/security/landlock/net.c
>> @@ -6,10 +6,12 @@
>> * Copyright © 2022-2023 Microsoft Corporation
>> */
>>
>> +#include <net/sock.h>
>> #include <linux/in.h>
>> #include <linux/net.h>
>> #include <linux/socket.h>
>> #include <net/ipv6.h>
>> +#include <net/tcp.h>
>>
>> #include "common.h"
>> #include "cred.h"
>> @@ -194,9 +196,105 @@ static int hook_socket_connect(struct socket *const sock,
>> LANDLOCK_ACCESS_NET_CONNECT_TCP);
>> }
>>
>> +/*
>> + * Checks that socket state and attributes are correct for listen.
>> + * Returns 0 on success and -EINVAL otherwise.
>> + *
>> + * This checker requires sock->sk to be locked.
>> + */
>> +static int check_tcp_socket_can_listen(struct socket *const sock)
>> +{
>> + struct sock *sk = sock->sk;
>> + unsigned char cur_sk_state;
>> + const struct tcp_ulp_ops *icsk_ulp_ops;
>> +
>> + lockdep_assert_held(&sk->sk_lock.slock);
>> +
>> + /* Allows only unconnected TCP socket to listen (cf. inet_listen). */
>> + if (sock->state != SS_UNCONNECTED)
>> + return -EINVAL;
>> +
>> + cur_sk_state = sk->sk_state;
>> + /*
>> + * Checks sock state. This is needed to ensure consistency with inet stack
>> + * error handling (cf. __inet_listen_sk).
>> + */
>> + if (WARN_ON_ONCE(!((1 << cur_sk_state) & (TCPF_CLOSE | TCPF_LISTEN))))
>> + return -EINVAL;
>> +
>> + icsk_ulp_ops = inet_csk(sk)->icsk_ulp_ops;
>> +
>> + /*
>> + * ULP (Upper Layer Protocol) stands for protocols which are higher than
>> + * transport protocol in OSI model. Linux has an infrastructure that
>> + * allows TCP sockets to support logic of some ULP (e.g. TLS ULP).
>> + *
>> + * Sockets can listen only if ULP control hook has clone method
>> + * (cf. inet_csk_listen_start)
>> + */
>> + if (icsk_ulp_ops && !icsk_ulp_ops->clone)
>> + return -EINVAL;
>> + return 0;
>> +}
>> +
>> +static int hook_socket_listen(struct socket *const sock, const int backlog)
>> +{
>> + int err = 0;
>> + int family;
>> + __be16 port;
>> + struct sock *sk;
>> + const struct landlock_ruleset *const dom = get_current_net_domain();
>> +
>> + if (!dom)
>> + return 0;
>> + if (WARN_ON_ONCE(dom->num_layers < 1))
>> + return -EACCES;
>> +
>> + /* Checks if it's a (potential) TCP socket. */
>> + if (sock->type != SOCK_STREAM)
>> + return 0;
>> +
>> + sk = sock->sk;
>> + family = sk->__sk_common.skc_family;
>> + /*
>> + * Socket cannot be assigned AF_UNSPEC because this type is used only
>> + * in the context of addresses.
>> + *
>> + * Doesn't restrict listening for non-TCP sockets.
>> + */
>> + if (family != AF_INET && family != AF_INET6)
>> + return 0;
>
> I imagine that we'll need the "protocol" comparison as well, in line
> with your fix for the bind() and connect() functionality at
> https://lore.kernel.org/all/20241003143932.2431249-1-ivanov.mikhail1@huawei-partners.com/
> ?
Yes, this check (and one above for SOCK_STREAM) should be replaced with
sk_is_tcp() [1].
[1]
https://lore.kernel.org/all/0774e9f1-994f-1131-17f9-7dd8eb96738f@huawei-partners.com/
>
>> +
>> + lock_sock(sk);
>
> The socket gets locked twice when doing listen() -- first it is locked
> by the security hook, then released again, then locked again by the
> actual listen() implementation and then released again.
>
> What if the protected values change in between the two times when the
> lock is held? What is the reasoning for why this is safe? (This
> might be worth a comment in the code to explain, IMHO.)
If some of these values change, inet_listen() will simply return
the appropriate error code (consistent with these checks). Since
hook_socket_listen() does not cause any socket changes, this scenario is
equivalent to a normal listen(2) call.
I'll add an appropriate comment, thanks!
>
>> + /*
>> + * Calling listen(2) for a listening socket does nothing with its state and
>> + * only changes backlog value (cf. __inet_listen_sk). Checking of listen
>> + * access right is not required.
>> + */
>> + if (sk->sk_state == TCP_LISTEN)
>> + goto release_nocheck;
>> +
>> + /*
>> + * Checks socket state to not wrongfully return -EACCES instead
>> + * of -EINVAL.
>> + */
>> + err = check_tcp_socket_can_listen(sock);
>> + if (unlikely(err))
>> + goto release_nocheck;
>> +
>> + port = htons(inet_sk(sk)->inet_num);
>> + release_sock(sk);
>> + return check_access_socket(dom, port, LANDLOCK_ACCESS_NET_LISTEN_TCP);
>
> Nit: The last two lines could just be
>
> err = check_access_socket(...);
>
> and then you would only need the release_sock(sk) call in one place.
> (And maybe rename the goto label accordingly.)
This split was done in order to not hold socket lock while doing some
Landlock-specific logic. It might be identical in performance to
your suggestion, but I thought that (1) security module should have as
little impact on network stack as possible and (2) it is more
clear that locking is performed only for a few socket state checks which
are not related to the access control.
I'll add this explanation with a comment if you agree that everything is
correct.
>
>> +
>> +release_nocheck:
>> + release_sock(sk);
>> + return err;
>> +}
>> +
>> static struct security_hook_list landlock_hooks[] __ro_after_init = {
>> LSM_HOOK_INIT(socket_bind, hook_socket_bind),
>> LSM_HOOK_INIT(socket_connect, hook_socket_connect),
>> + LSM_HOOK_INIT(socket_listen, hook_socket_listen),
>> };
>>
>> __init void landlock_add_net_hooks(void)
>> diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c
>> index ccc8bc6c1584..328198e8a9f5 100644
>> --- a/security/landlock/syscalls.c
>> +++ b/security/landlock/syscalls.c
>> @@ -149,7 +149,7 @@ static const struct file_operations ruleset_fops = {
>> .write = fop_dummy_write,
>> };
>>
>> -#define LANDLOCK_ABI_VERSION 5
>> +#define LANDLOCK_ABI_VERSION 6
>>
>> /**
>> * sys_landlock_create_ruleset - Create a new ruleset
>> diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c
>> index 3b26bf3cf5b9..1bc16fde2e8a 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(5, landlock_create_ruleset(NULL, 0,
>> + ASSERT_EQ(6, landlock_create_ruleset(NULL, 0,
>> LANDLOCK_CREATE_RULESET_VERSION));
>>
>> ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0,
>> --
>> 2.34.1
>>
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [RFC PATCH v2 2/9] landlock: Support TCP listen access-control
2024-10-05 17:53 ` Mikhail Ivanov
@ 2024-10-05 18:22 ` Günther Noack
2024-10-05 18:32 ` Mikhail Ivanov
0 siblings, 1 reply; 40+ messages in thread
From: Günther Noack @ 2024-10-05 18:22 UTC (permalink / raw)
To: Mikhail Ivanov
Cc: mic, willemdebruijn.kernel, linux-security-module, netdev,
netfilter-devel, yusongping, artem.kuzin, konstantin.meskhidze
On Sat, Oct 05, 2024 at 08:53:55PM +0300, Mikhail Ivanov wrote:
> On 10/5/2024 7:56 PM, Günther Noack wrote:
> > On Wed, Aug 14, 2024 at 11:01:44AM +0800, Mikhail Ivanov wrote:
> > > + port = htons(inet_sk(sk)->inet_num);
> > > + release_sock(sk);
> > > + return check_access_socket(dom, port, LANDLOCK_ACCESS_NET_LISTEN_TCP);
> >
> > Nit: The last two lines could just be
> >
> > err = check_access_socket(...);
> >
> > and then you would only need the release_sock(sk) call in one place.
> > (And maybe rename the goto label accordingly.)
> This split was done in order to not hold socket lock while doing some
> Landlock-specific logic. It might be identical in performance to
> your suggestion, but I thought that (1) security module should have as
> little impact on network stack as possible and (2) it is more
> clear that locking is performed only for a few socket state checks which
> are not related to the access control.
>
> I'll add this explanation with a comment if you agree that everything is
> correct.
IMHO, when you grab a lock in this function, it is clear that you'd
unconditionally want to release it before you return from the
function, and that in C, the normal way to guarantee unconditional
cleanup work would be to apply the "single exit point" rule.
That being said, the scenario is simple enough here that it's not a
big issue in my eyes. It was more of a minor nit about having more
than one place where the lock has to be released. Either way is fine
(and also should not require excessive comments :)).
> > > +
> > > +release_nocheck:
> > > + release_sock(sk);
> > > + return err;
> > > +}
–Günther
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [RFC PATCH v2 2/9] landlock: Support TCP listen access-control
2024-10-05 18:22 ` Günther Noack
@ 2024-10-05 18:32 ` Mikhail Ivanov
0 siblings, 0 replies; 40+ messages in thread
From: Mikhail Ivanov @ 2024-10-05 18:32 UTC (permalink / raw)
To: Günther Noack
Cc: mic, willemdebruijn.kernel, linux-security-module, netdev,
netfilter-devel, yusongping, artem.kuzin, konstantin.meskhidze
On 10/5/2024 9:22 PM, Günther Noack wrote:
> On Sat, Oct 05, 2024 at 08:53:55PM +0300, Mikhail Ivanov wrote:
>> On 10/5/2024 7:56 PM, Günther Noack wrote:
>>> On Wed, Aug 14, 2024 at 11:01:44AM +0800, Mikhail Ivanov wrote:
>>>> + port = htons(inet_sk(sk)->inet_num);
>>>> + release_sock(sk);
>>>> + return check_access_socket(dom, port, LANDLOCK_ACCESS_NET_LISTEN_TCP);
>>>
>>> Nit: The last two lines could just be
>>>
>>> err = check_access_socket(...);
>>>
>>> and then you would only need the release_sock(sk) call in one place.
>>> (And maybe rename the goto label accordingly.)
>> This split was done in order to not hold socket lock while doing some
>> Landlock-specific logic. It might be identical in performance to
>> your suggestion, but I thought that (1) security module should have as
>> little impact on network stack as possible and (2) it is more
>> clear that locking is performed only for a few socket state checks which
>> are not related to the access control.
>>
>> I'll add this explanation with a comment if you agree that everything is
>> correct.
>
>
> IMHO, when you grab a lock in this function, it is clear that you'd
> unconditionally want to release it before you return from the
> function, and that in C, the normal way to guarantee unconditional
> cleanup work would be to apply the "single exit point" rule.
Yes, these 2 release_lock()s can really raise questions when reading.
>
> That being said, the scenario is simple enough here that it's not a
> big issue in my eyes. It was more of a minor nit about having more
> than one place where the lock has to be released. Either way is fine
> (and also should not require excessive comments :)).
Ok
>
>>>> +
>>>> +release_nocheck:
>>>> + release_sock(sk);
>>>> + return err;
>>>> +}
>
> –Günther
^ permalink raw reply [flat|nested] 40+ messages in thread
* [RFC PATCH v2 3/9] selftests/landlock: Support LANDLOCK_ACCESS_NET_LISTEN_TCP
2024-08-14 3:01 [RFC PATCH v2 0/9] Support TCP listen access-control Mikhail Ivanov
2024-08-14 3:01 ` [RFC PATCH v2 1/9] landlock: Refactor current_check_access_socket() access right check Mikhail Ivanov
2024-08-14 3:01 ` [RFC PATCH v2 2/9] landlock: Support TCP listen access-control Mikhail Ivanov
@ 2024-08-14 3:01 ` Mikhail Ivanov
2024-08-19 21:52 ` Günther Noack
2024-08-19 21:53 ` Günther Noack
2024-08-14 3:01 ` [RFC PATCH v2 4/9] selftests/landlock: Test listening restriction Mikhail Ivanov
` (6 subsequent siblings)
9 siblings, 2 replies; 40+ messages in thread
From: Mikhail Ivanov @ 2024-08-14 3:01 UTC (permalink / raw)
To: mic
Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
netfilter-devel, yusongping, artem.kuzin, konstantin.meskhidze
* Add listen_variant() to simplify listen(2) return code checking.
* Rename test_bind_and_connect() to test_restricted_net_fixture().
* Extend current net rules with LANDLOCK_ACCESS_NET_LISTEN_TCP access.
* Rename test port_specific.bind_connect_1023 to
port_specific.port_1023.
* Check little endian port restriction for listen in
ipv4_tcp.port_endianness.
* Some local renames and comment changes.
Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
---
tools/testing/selftests/landlock/net_test.c | 198 +++++++++++---------
1 file changed, 107 insertions(+), 91 deletions(-)
diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
index f21cfbbc3638..8126f5c0160f 100644
--- a/tools/testing/selftests/landlock/net_test.c
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -2,7 +2,7 @@
/*
* Landlock tests - Network
*
- * Copyright © 2022-2023 Huawei Tech. Co., Ltd.
+ * Copyright © 2022-2024 Huawei Tech. Co., Ltd.
* Copyright © 2023 Microsoft Corporation
*/
@@ -22,6 +22,17 @@
#include "common.h"
+/* clang-format off */
+
+#define ACCESS_LAST LANDLOCK_ACCESS_NET_LISTEN_TCP
+
+#define ACCESS_ALL ( \
+ LANDLOCK_ACCESS_NET_BIND_TCP | \
+ LANDLOCK_ACCESS_NET_CONNECT_TCP | \
+ LANDLOCK_ACCESS_NET_LISTEN_TCP)
+
+/* clang-format on */
+
const short sock_port_start = (1 << 10);
static const char loopback_ipv4[] = "127.0.0.1";
@@ -282,6 +293,16 @@ static int connect_variant(const int sock_fd,
return connect_variant_addrlen(sock_fd, srv, get_addrlen(srv, false));
}
+static int listen_variant(const int sock_fd, const int backlog)
+{
+ int ret;
+
+ ret = listen(sock_fd, backlog);
+ if (ret < 0)
+ return -errno;
+ return ret;
+}
+
FIXTURE(protocol)
{
struct service_fixture srv0, srv1, srv2, unspec_any0, unspec_srv0;
@@ -438,9 +459,11 @@ FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_unix_datagram) {
},
};
-static void test_bind_and_connect(struct __test_metadata *const _metadata,
- const struct service_fixture *const srv,
- const bool deny_bind, const bool deny_connect)
+static void test_restricted_net_fixture(struct __test_metadata *const _metadata,
+ const struct service_fixture *const srv,
+ const bool deny_bind,
+ const bool deny_connect,
+ const bool deny_listen)
{
char buf = '\0';
int inval_fd, bind_fd, client_fd, status, ret;
@@ -512,8 +535,14 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
EXPECT_EQ(0, ret);
/* Creates a listening socket. */
- if (srv->protocol.type == SOCK_STREAM)
- EXPECT_EQ(0, listen(bind_fd, backlog));
+ if (srv->protocol.type == SOCK_STREAM) {
+ ret = listen_variant(bind_fd, backlog);
+ if (deny_listen) {
+ EXPECT_EQ(-EACCES, ret);
+ } else {
+ EXPECT_EQ(0, ret);
+ }
+ }
}
child = fork();
@@ -530,7 +559,7 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
ret = connect_variant(connect_fd, srv);
if (deny_connect) {
EXPECT_EQ(-EACCES, ret);
- } else if (deny_bind) {
+ } else if (deny_bind || deny_listen) {
/* No listening server. */
EXPECT_EQ(-ECONNREFUSED, ret);
} else {
@@ -545,7 +574,7 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
/* Accepts connection from the child. */
client_fd = bind_fd;
- if (!deny_bind && !deny_connect) {
+ if (!deny_bind && !deny_connect && !deny_listen) {
if (srv->protocol.type == SOCK_STREAM) {
client_fd = accept(bind_fd, NULL, 0);
ASSERT_LE(0, client_fd);
@@ -571,16 +600,15 @@ TEST_F(protocol, bind)
{
if (variant->sandbox == TCP_SANDBOX) {
const struct landlock_ruleset_attr ruleset_attr = {
- .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
- LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ .handled_access_net = ACCESS_ALL,
};
- const struct landlock_net_port_attr tcp_bind_connect_p0 = {
- .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
- LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ const struct landlock_net_port_attr tcp_not_restricted_p0 = {
+ .allowed_access = ACCESS_ALL,
.port = self->srv0.port,
};
- const struct landlock_net_port_attr tcp_connect_p1 = {
- .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ const struct landlock_net_port_attr tcp_denied_bind_p1 = {
+ .allowed_access = ACCESS_ALL &
+ ~LANDLOCK_ACCESS_NET_BIND_TCP,
.port = self->srv1.port,
};
int ruleset_fd;
@@ -589,48 +617,47 @@ TEST_F(protocol, bind)
sizeof(ruleset_attr), 0);
ASSERT_LE(0, ruleset_fd);
- /* Allows connect and bind for the first port. */
+ /* Allows all actions for the first port. */
ASSERT_EQ(0,
landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
- &tcp_bind_connect_p0, 0));
+ &tcp_not_restricted_p0, 0));
- /* Allows connect and denies bind for the second port. */
+ /* Allows all actions despite bind. */
ASSERT_EQ(0,
landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
- &tcp_connect_p1, 0));
+ &tcp_denied_bind_p1, 0));
enforce_ruleset(_metadata, ruleset_fd);
EXPECT_EQ(0, close(ruleset_fd));
}
+ bool restricted = is_restricted(&variant->prot, variant->sandbox);
/* Binds a socket to the first port. */
- test_bind_and_connect(_metadata, &self->srv0, false, false);
+ test_restricted_net_fixture(_metadata, &self->srv0, false, false,
+ false);
/* Binds a socket to the second port. */
- test_bind_and_connect(_metadata, &self->srv1,
- is_restricted(&variant->prot, variant->sandbox),
- false);
+ test_restricted_net_fixture(_metadata, &self->srv1, restricted, false,
+ false);
/* Binds a socket to the third port. */
- test_bind_and_connect(_metadata, &self->srv2,
- is_restricted(&variant->prot, variant->sandbox),
- is_restricted(&variant->prot, variant->sandbox));
+ test_restricted_net_fixture(_metadata, &self->srv2, restricted,
+ restricted, restricted);
}
TEST_F(protocol, connect)
{
if (variant->sandbox == TCP_SANDBOX) {
const struct landlock_ruleset_attr ruleset_attr = {
- .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
- LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ .handled_access_net = ACCESS_ALL,
};
- const struct landlock_net_port_attr tcp_bind_connect_p0 = {
- .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
- LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ const struct landlock_net_port_attr tcp_not_restricted_p0 = {
+ .allowed_access = ACCESS_ALL,
.port = self->srv0.port,
};
- const struct landlock_net_port_attr tcp_bind_p1 = {
- .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
+ const struct landlock_net_port_attr tcp_denied_connect_p1 = {
+ .allowed_access = ACCESS_ALL &
+ ~LANDLOCK_ACCESS_NET_CONNECT_TCP,
.port = self->srv1.port,
};
int ruleset_fd;
@@ -639,28 +666,27 @@ TEST_F(protocol, connect)
sizeof(ruleset_attr), 0);
ASSERT_LE(0, ruleset_fd);
- /* Allows connect and bind for the first port. */
+ /* Allows all actions for the first port. */
ASSERT_EQ(0,
landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
- &tcp_bind_connect_p0, 0));
+ &tcp_not_restricted_p0, 0));
- /* Allows bind and denies connect for the second port. */
+ /* Allows all actions despite connect. */
ASSERT_EQ(0,
landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
- &tcp_bind_p1, 0));
+ &tcp_denied_connect_p1, 0));
enforce_ruleset(_metadata, ruleset_fd);
EXPECT_EQ(0, close(ruleset_fd));
}
-
- test_bind_and_connect(_metadata, &self->srv0, false, false);
-
- test_bind_and_connect(_metadata, &self->srv1, false,
- is_restricted(&variant->prot, variant->sandbox));
-
- test_bind_and_connect(_metadata, &self->srv2,
- is_restricted(&variant->prot, variant->sandbox),
- is_restricted(&variant->prot, variant->sandbox));
+ bool restricted = is_restricted(&variant->prot, variant->sandbox);
+
+ test_restricted_net_fixture(_metadata, &self->srv0, false, false,
+ false);
+ test_restricted_net_fixture(_metadata, &self->srv1, false, restricted,
+ false);
+ test_restricted_net_fixture(_metadata, &self->srv2, restricted,
+ restricted, restricted);
}
TEST_F(protocol, bind_unspec)
@@ -761,7 +787,7 @@ TEST_F(protocol, connect_unspec)
ASSERT_LE(0, bind_fd);
EXPECT_EQ(0, bind_variant(bind_fd, &self->srv0));
if (self->srv0.protocol.type == SOCK_STREAM)
- EXPECT_EQ(0, listen(bind_fd, backlog));
+ EXPECT_EQ(0, listen_variant(bind_fd, backlog));
child = fork();
ASSERT_LE(0, child);
@@ -1127,8 +1153,8 @@ TEST_F(tcp_layers, ruleset_overlap)
* Forbids to connect to the socket because only one ruleset layer
* allows connect.
*/
- test_bind_and_connect(_metadata, &self->srv0, false,
- variant->num_layers >= 2);
+ test_restricted_net_fixture(_metadata, &self->srv0, false,
+ variant->num_layers >= 2, false);
}
TEST_F(tcp_layers, ruleset_expand)
@@ -1208,11 +1234,12 @@ TEST_F(tcp_layers, ruleset_expand)
EXPECT_EQ(0, close(ruleset_fd));
}
- test_bind_and_connect(_metadata, &self->srv0, false,
- variant->num_layers >= 3);
+ test_restricted_net_fixture(_metadata, &self->srv0, false,
+ variant->num_layers >= 3, false);
- test_bind_and_connect(_metadata, &self->srv1, variant->num_layers >= 1,
- variant->num_layers >= 2);
+ test_restricted_net_fixture(_metadata, &self->srv1,
+ variant->num_layers >= 1,
+ variant->num_layers >= 2, false);
}
/* clang-format off */
@@ -1230,16 +1257,6 @@ FIXTURE_TEARDOWN(mini)
{
}
-/* clang-format off */
-
-#define ACCESS_LAST LANDLOCK_ACCESS_NET_CONNECT_TCP
-
-#define ACCESS_ALL ( \
- LANDLOCK_ACCESS_NET_BIND_TCP | \
- LANDLOCK_ACCESS_NET_CONNECT_TCP)
-
-/* clang-format on */
-
TEST_F(mini, network_access_rights)
{
const struct landlock_ruleset_attr ruleset_attr = {
@@ -1454,8 +1471,9 @@ TEST_F(mini, tcp_port_overflow)
enforce_ruleset(_metadata, ruleset_fd);
- test_bind_and_connect(_metadata, &srv_denied, true, true);
- test_bind_and_connect(_metadata, &srv_max_allowed, false, false);
+ test_restricted_net_fixture(_metadata, &srv_denied, true, true, false);
+ test_restricted_net_fixture(_metadata, &srv_max_allowed, false, false,
+ false);
}
FIXTURE(ipv4_tcp)
@@ -1485,22 +1503,21 @@ FIXTURE_TEARDOWN(ipv4_tcp)
TEST_F(ipv4_tcp, port_endianness)
{
const struct landlock_ruleset_attr ruleset_attr = {
- .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
- LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ .handled_access_net = ACCESS_ALL,
};
const struct landlock_net_port_attr bind_host_endian_p0 = {
.allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
/* Host port format. */
.port = self->srv0.port,
};
- const struct landlock_net_port_attr connect_big_endian_p0 = {
- .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ const struct landlock_net_port_attr connect_listen_big_endian_p0 = {
+ .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP |
+ LANDLOCK_ACCESS_NET_LISTEN_TCP,
/* Big endian port format. */
.port = htons(self->srv0.port),
};
- const struct landlock_net_port_attr bind_connect_host_endian_p1 = {
- .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
- LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ const struct landlock_net_port_attr not_restricted_host_endian_p1 = {
+ .allowed_access = ACCESS_ALL,
/* Host port format. */
.port = self->srv1.port,
};
@@ -1514,16 +1531,18 @@ TEST_F(ipv4_tcp, port_endianness)
ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
&bind_host_endian_p0, 0));
ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
- &connect_big_endian_p0, 0));
+ &connect_listen_big_endian_p0, 0));
ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
- &bind_connect_host_endian_p1, 0));
+ ¬_restricted_host_endian_p1, 0));
enforce_ruleset(_metadata, ruleset_fd);
/* No restriction for big endinan CPU. */
- test_bind_and_connect(_metadata, &self->srv0, false, little_endian);
+ test_restricted_net_fixture(_metadata, &self->srv0, false,
+ little_endian, little_endian);
/* No restriction for any CPU. */
- test_bind_and_connect(_metadata, &self->srv1, false, false);
+ test_restricted_net_fixture(_metadata, &self->srv1, false, false,
+ false);
}
TEST_F(ipv4_tcp, with_fs)
@@ -1691,7 +1710,7 @@ TEST_F(port_specific, bind_connect_zero)
ret = bind_variant(bind_fd, &self->srv0);
EXPECT_EQ(0, ret);
- EXPECT_EQ(0, listen(bind_fd, backlog));
+ EXPECT_EQ(0, listen_variant(bind_fd, backlog));
/* Connects on port 0. */
ret = connect_variant(connect_fd, &self->srv0);
@@ -1714,26 +1733,23 @@ TEST_F(port_specific, bind_connect_zero)
EXPECT_EQ(0, close(bind_fd));
}
-TEST_F(port_specific, bind_connect_1023)
+TEST_F(port_specific, port_1023)
{
int bind_fd, connect_fd, ret;
- /* Adds a rule layer with bind and connect actions. */
+ /* Adds a rule layer with all actions. */
if (variant->sandbox == TCP_SANDBOX) {
const struct landlock_ruleset_attr ruleset_attr = {
- .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
- LANDLOCK_ACCESS_NET_CONNECT_TCP
+ .handled_access_net = ACCESS_ALL
};
/* A rule with port value less than 1024. */
- const struct landlock_net_port_attr tcp_bind_connect_low_range = {
- .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
- LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ const struct landlock_net_port_attr tcp_low_range_port = {
+ .allowed_access = ACCESS_ALL,
.port = 1023,
};
/* A rule with 1024 port. */
- const struct landlock_net_port_attr tcp_bind_connect = {
- .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
- LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ const struct landlock_net_port_attr tcp_port_1024 = {
+ .allowed_access = ACCESS_ALL,
.port = 1024,
};
int ruleset_fd;
@@ -1744,10 +1760,10 @@ TEST_F(port_specific, bind_connect_1023)
ASSERT_EQ(0,
landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
- &tcp_bind_connect_low_range, 0));
+ &tcp_low_range_port, 0));
ASSERT_EQ(0,
landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
- &tcp_bind_connect, 0));
+ &tcp_port_1024, 0));
enforce_ruleset(_metadata, ruleset_fd);
EXPECT_EQ(0, close(ruleset_fd));
@@ -1771,7 +1787,7 @@ TEST_F(port_specific, bind_connect_1023)
ret = bind_variant(bind_fd, &self->srv0);
clear_cap(_metadata, CAP_NET_BIND_SERVICE);
EXPECT_EQ(0, ret);
- EXPECT_EQ(0, listen(bind_fd, backlog));
+ EXPECT_EQ(0, listen_variant(bind_fd, backlog));
/* Connects on the binded port 1023. */
ret = connect_variant(connect_fd, &self->srv0);
@@ -1791,7 +1807,7 @@ TEST_F(port_specific, bind_connect_1023)
/* Binds on port 1024. */
ret = bind_variant(bind_fd, &self->srv0);
EXPECT_EQ(0, ret);
- EXPECT_EQ(0, listen(bind_fd, backlog));
+ EXPECT_EQ(0, listen_variant(bind_fd, backlog));
/* Connects on the binded port 1024. */
ret = connect_variant(connect_fd, &self->srv0);
--
2.34.1
^ permalink raw reply related [flat|nested] 40+ messages in thread
* Re: [RFC PATCH v2 3/9] selftests/landlock: Support LANDLOCK_ACCESS_NET_LISTEN_TCP
2024-08-14 3:01 ` [RFC PATCH v2 3/9] selftests/landlock: Support LANDLOCK_ACCESS_NET_LISTEN_TCP Mikhail Ivanov
@ 2024-08-19 21:52 ` Günther Noack
2024-08-20 12:32 ` Mikhail Ivanov
2024-08-20 13:14 ` Günther Noack
2024-08-19 21:53 ` Günther Noack
1 sibling, 2 replies; 40+ messages in thread
From: Günther Noack @ 2024-08-19 21:52 UTC (permalink / raw)
To: Mikhail Ivanov
Cc: mic, willemdebruijn.kernel, gnoack3000, linux-security-module,
netdev, netfilter-devel, yusongping, artem.kuzin,
konstantin.meskhidze
On Wed, Aug 14, 2024 at 11:01:45AM +0800, Mikhail Ivanov wrote:
> * Add listen_variant() to simplify listen(2) return code checking.
> * Rename test_bind_and_connect() to test_restricted_net_fixture().
> * Extend current net rules with LANDLOCK_ACCESS_NET_LISTEN_TCP access.
> * Rename test port_specific.bind_connect_1023 to
> port_specific.port_1023.
> * Check little endian port restriction for listen in
> ipv4_tcp.port_endianness.
> * Some local renames and comment changes.
>
> Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
> ---
> tools/testing/selftests/landlock/net_test.c | 198 +++++++++++---------
> 1 file changed, 107 insertions(+), 91 deletions(-)
>
> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
> index f21cfbbc3638..8126f5c0160f 100644
> --- a/tools/testing/selftests/landlock/net_test.c
> +++ b/tools/testing/selftests/landlock/net_test.c
> @@ -2,7 +2,7 @@
> /*
> * Landlock tests - Network
> *
> - * Copyright © 2022-2023 Huawei Tech. Co., Ltd.
> + * Copyright © 2022-2024 Huawei Tech. Co., Ltd.
> * Copyright © 2023 Microsoft Corporation
> */
>
> @@ -22,6 +22,17 @@
>
> #include "common.h"
>
> +/* clang-format off */
> +
> +#define ACCESS_LAST LANDLOCK_ACCESS_NET_LISTEN_TCP
> +
> +#define ACCESS_ALL ( \
> + LANDLOCK_ACCESS_NET_BIND_TCP | \
> + LANDLOCK_ACCESS_NET_CONNECT_TCP | \
> + LANDLOCK_ACCESS_NET_LISTEN_TCP)
> +
> +/* clang-format on */
> +
> const short sock_port_start = (1 << 10);
>
> static const char loopback_ipv4[] = "127.0.0.1";
> @@ -282,6 +293,16 @@ static int connect_variant(const int sock_fd,
> return connect_variant_addrlen(sock_fd, srv, get_addrlen(srv, false));
> }
>
> +static int listen_variant(const int sock_fd, const int backlog)
I believe socket_variant(), connect_variant() and bind_variant() were called
like that because they got an instance of a service_fixture as an argument. The
fixture instances are called variants. But we don't use these fixtures here.
In fs_test.c, we also have some functions that behave much like system calls,
but clean up after themselves and return errno, for easier use in assert. The
naming scheme we have used there is "test_foo" (e.g. test_open()). I think this
would be more appropriate here as a name?
> +{
> + int ret;
> +
> + ret = listen(sock_fd, backlog);
> + if (ret < 0)
> + return -errno;
> + return ret;
> +}
> +
> FIXTURE(protocol)
> {
> struct service_fixture srv0, srv1, srv2, unspec_any0, unspec_srv0;
> @@ -438,9 +459,11 @@ FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_unix_datagram) {
> },
> };
>
> -static void test_bind_and_connect(struct __test_metadata *const _metadata,
> - const struct service_fixture *const srv,
> - const bool deny_bind, const bool deny_connect)
> +static void test_restricted_net_fixture(struct __test_metadata *const _metadata,
> + const struct service_fixture *const srv,
> + const bool deny_bind,
> + const bool deny_connect,
> + const bool deny_listen)
> {
> char buf = '\0';
> int inval_fd, bind_fd, client_fd, status, ret;
> @@ -512,8 +535,14 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
> EXPECT_EQ(0, ret);
>
> /* Creates a listening socket. */
> - if (srv->protocol.type == SOCK_STREAM)
> - EXPECT_EQ(0, listen(bind_fd, backlog));
> + if (srv->protocol.type == SOCK_STREAM) {
> + ret = listen_variant(bind_fd, backlog);
> + if (deny_listen) {
> + EXPECT_EQ(-EACCES, ret);
> + } else {
> + EXPECT_EQ(0, ret);
> + }
Hmm, passing the expected error code instead of a boolean to this function was not possible?
Then you could just write
EXPECT_EQ(expected_listen_error, listen_variant(bind_fd, backlog));
? (Apologies if this was discussed already.)
> + }
> }
>
> child = fork();
> @@ -530,7 +559,7 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
> ret = connect_variant(connect_fd, srv);
> if (deny_connect) {
> EXPECT_EQ(-EACCES, ret);
> - } else if (deny_bind) {
> + } else if (deny_bind || deny_listen) {
> /* No listening server. */
> EXPECT_EQ(-ECONNREFUSED, ret);
> } else {
> @@ -545,7 +574,7 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
>
> /* Accepts connection from the child. */
> client_fd = bind_fd;
> - if (!deny_bind && !deny_connect) {
> + if (!deny_bind && !deny_connect && !deny_listen) {
> if (srv->protocol.type == SOCK_STREAM) {
> client_fd = accept(bind_fd, NULL, 0);
> ASSERT_LE(0, client_fd);
> @@ -571,16 +600,15 @@ TEST_F(protocol, bind)
> {
> if (variant->sandbox == TCP_SANDBOX) {
> const struct landlock_ruleset_attr ruleset_attr = {
> - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> + .handled_access_net = ACCESS_ALL,
> };
> - const struct landlock_net_port_attr tcp_bind_connect_p0 = {
> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> + const struct landlock_net_port_attr tcp_not_restricted_p0 = {
> + .allowed_access = ACCESS_ALL,
> .port = self->srv0.port,
> };
> - const struct landlock_net_port_attr tcp_connect_p1 = {
> - .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
> + const struct landlock_net_port_attr tcp_denied_bind_p1 = {
> + .allowed_access = ACCESS_ALL &
> + ~LANDLOCK_ACCESS_NET_BIND_TCP,
> .port = self->srv1.port,
> };
> int ruleset_fd;
> @@ -589,48 +617,47 @@ TEST_F(protocol, bind)
> sizeof(ruleset_attr), 0);
> ASSERT_LE(0, ruleset_fd);
>
> - /* Allows connect and bind for the first port. */
> + /* Allows all actions for the first port. */
> ASSERT_EQ(0,
> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> - &tcp_bind_connect_p0, 0));
> + &tcp_not_restricted_p0, 0));
>
> - /* Allows connect and denies bind for the second port. */
> + /* Allows all actions despite bind. */
> ASSERT_EQ(0,
> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> - &tcp_connect_p1, 0));
> + &tcp_denied_bind_p1, 0));
>
> enforce_ruleset(_metadata, ruleset_fd);
> EXPECT_EQ(0, close(ruleset_fd));
> }
> + bool restricted = is_restricted(&variant->prot, variant->sandbox);
>
> /* Binds a socket to the first port. */
> - test_bind_and_connect(_metadata, &self->srv0, false, false);
> + test_restricted_net_fixture(_metadata, &self->srv0, false, false,
> + false);
>
> /* Binds a socket to the second port. */
> - test_bind_and_connect(_metadata, &self->srv1,
> - is_restricted(&variant->prot, variant->sandbox),
> - false);
> + test_restricted_net_fixture(_metadata, &self->srv1, restricted, false,
> + false);
>
> /* Binds a socket to the third port. */
> - test_bind_and_connect(_metadata, &self->srv2,
> - is_restricted(&variant->prot, variant->sandbox),
> - is_restricted(&variant->prot, variant->sandbox));
> + test_restricted_net_fixture(_metadata, &self->srv2, restricted,
> + restricted, restricted);
> }
>
> TEST_F(protocol, connect)
> {
> if (variant->sandbox == TCP_SANDBOX) {
> const struct landlock_ruleset_attr ruleset_attr = {
> - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> + .handled_access_net = ACCESS_ALL,
> };
> - const struct landlock_net_port_attr tcp_bind_connect_p0 = {
> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> + const struct landlock_net_port_attr tcp_not_restricted_p0 = {
> + .allowed_access = ACCESS_ALL,
> .port = self->srv0.port,
> };
> - const struct landlock_net_port_attr tcp_bind_p1 = {
> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
> + const struct landlock_net_port_attr tcp_denied_connect_p1 = {
> + .allowed_access = ACCESS_ALL &
> + ~LANDLOCK_ACCESS_NET_CONNECT_TCP,
> .port = self->srv1.port,
> };
> int ruleset_fd;
> @@ -639,28 +666,27 @@ TEST_F(protocol, connect)
> sizeof(ruleset_attr), 0);
> ASSERT_LE(0, ruleset_fd);
>
> - /* Allows connect and bind for the first port. */
> + /* Allows all actions for the first port. */
> ASSERT_EQ(0,
> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> - &tcp_bind_connect_p0, 0));
> + &tcp_not_restricted_p0, 0));
>
> - /* Allows bind and denies connect for the second port. */
> + /* Allows all actions despite connect. */
> ASSERT_EQ(0,
> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> - &tcp_bind_p1, 0));
> + &tcp_denied_connect_p1, 0));
>
> enforce_ruleset(_metadata, ruleset_fd);
> EXPECT_EQ(0, close(ruleset_fd));
> }
> -
> - test_bind_and_connect(_metadata, &self->srv0, false, false);
> -
> - test_bind_and_connect(_metadata, &self->srv1, false,
> - is_restricted(&variant->prot, variant->sandbox));
> -
> - test_bind_and_connect(_metadata, &self->srv2,
> - is_restricted(&variant->prot, variant->sandbox),
> - is_restricted(&variant->prot, variant->sandbox));
> + bool restricted = is_restricted(&variant->prot, variant->sandbox);
> +
> + test_restricted_net_fixture(_metadata, &self->srv0, false, false,
> + false);
> + test_restricted_net_fixture(_metadata, &self->srv1, false, restricted,
> + false);
> + test_restricted_net_fixture(_metadata, &self->srv2, restricted,
> + restricted, restricted);
> }
>
> TEST_F(protocol, bind_unspec)
> @@ -761,7 +787,7 @@ TEST_F(protocol, connect_unspec)
> ASSERT_LE(0, bind_fd);
> EXPECT_EQ(0, bind_variant(bind_fd, &self->srv0));
> if (self->srv0.protocol.type == SOCK_STREAM)
> - EXPECT_EQ(0, listen(bind_fd, backlog));
> + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
>
> child = fork();
> ASSERT_LE(0, child);
> @@ -1127,8 +1153,8 @@ TEST_F(tcp_layers, ruleset_overlap)
> * Forbids to connect to the socket because only one ruleset layer
> * allows connect.
> */
> - test_bind_and_connect(_metadata, &self->srv0, false,
> - variant->num_layers >= 2);
> + test_restricted_net_fixture(_metadata, &self->srv0, false,
> + variant->num_layers >= 2, false);
> }
>
> TEST_F(tcp_layers, ruleset_expand)
> @@ -1208,11 +1234,12 @@ TEST_F(tcp_layers, ruleset_expand)
> EXPECT_EQ(0, close(ruleset_fd));
> }
>
> - test_bind_and_connect(_metadata, &self->srv0, false,
> - variant->num_layers >= 3);
> + test_restricted_net_fixture(_metadata, &self->srv0, false,
> + variant->num_layers >= 3, false);
>
> - test_bind_and_connect(_metadata, &self->srv1, variant->num_layers >= 1,
> - variant->num_layers >= 2);
> + test_restricted_net_fixture(_metadata, &self->srv1,
> + variant->num_layers >= 1,
> + variant->num_layers >= 2, false);
> }
>
> /* clang-format off */
> @@ -1230,16 +1257,6 @@ FIXTURE_TEARDOWN(mini)
> {
> }
>
> -/* clang-format off */
> -
> -#define ACCESS_LAST LANDLOCK_ACCESS_NET_CONNECT_TCP
> -
> -#define ACCESS_ALL ( \
> - LANDLOCK_ACCESS_NET_BIND_TCP | \
> - LANDLOCK_ACCESS_NET_CONNECT_TCP)
> -
> -/* clang-format on */
> -
> TEST_F(mini, network_access_rights)
> {
> const struct landlock_ruleset_attr ruleset_attr = {
> @@ -1454,8 +1471,9 @@ TEST_F(mini, tcp_port_overflow)
>
> enforce_ruleset(_metadata, ruleset_fd);
>
> - test_bind_and_connect(_metadata, &srv_denied, true, true);
> - test_bind_and_connect(_metadata, &srv_max_allowed, false, false);
> + test_restricted_net_fixture(_metadata, &srv_denied, true, true, false);
> + test_restricted_net_fixture(_metadata, &srv_max_allowed, false, false,
> + false);
> }
>
> FIXTURE(ipv4_tcp)
> @@ -1485,22 +1503,21 @@ FIXTURE_TEARDOWN(ipv4_tcp)
> TEST_F(ipv4_tcp, port_endianness)
> {
> const struct landlock_ruleset_attr ruleset_attr = {
> - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> + .handled_access_net = ACCESS_ALL,
> };
> const struct landlock_net_port_attr bind_host_endian_p0 = {
> .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
> /* Host port format. */
> .port = self->srv0.port,
> };
> - const struct landlock_net_port_attr connect_big_endian_p0 = {
> - .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
> + const struct landlock_net_port_attr connect_listen_big_endian_p0 = {
> + .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP |
> + LANDLOCK_ACCESS_NET_LISTEN_TCP,
> /* Big endian port format. */
> .port = htons(self->srv0.port),
> };
> - const struct landlock_net_port_attr bind_connect_host_endian_p1 = {
> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> + const struct landlock_net_port_attr not_restricted_host_endian_p1 = {
> + .allowed_access = ACCESS_ALL,
> /* Host port format. */
> .port = self->srv1.port,
> };
> @@ -1514,16 +1531,18 @@ TEST_F(ipv4_tcp, port_endianness)
> ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> &bind_host_endian_p0, 0));
> ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> - &connect_big_endian_p0, 0));
> + &connect_listen_big_endian_p0, 0));
> ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> - &bind_connect_host_endian_p1, 0));
> + ¬_restricted_host_endian_p1, 0));
> enforce_ruleset(_metadata, ruleset_fd);
>
> /* No restriction for big endinan CPU. */
> - test_bind_and_connect(_metadata, &self->srv0, false, little_endian);
> + test_restricted_net_fixture(_metadata, &self->srv0, false,
> + little_endian, little_endian);
>
> /* No restriction for any CPU. */
> - test_bind_and_connect(_metadata, &self->srv1, false, false);
> + test_restricted_net_fixture(_metadata, &self->srv1, false, false,
> + false);
> }
>
> TEST_F(ipv4_tcp, with_fs)
> @@ -1691,7 +1710,7 @@ TEST_F(port_specific, bind_connect_zero)
> ret = bind_variant(bind_fd, &self->srv0);
> EXPECT_EQ(0, ret);
>
> - EXPECT_EQ(0, listen(bind_fd, backlog));
> + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
>
> /* Connects on port 0. */
> ret = connect_variant(connect_fd, &self->srv0);
> @@ -1714,26 +1733,23 @@ TEST_F(port_specific, bind_connect_zero)
> EXPECT_EQ(0, close(bind_fd));
> }
>
> -TEST_F(port_specific, bind_connect_1023)
> +TEST_F(port_specific, port_1023)
> {
> int bind_fd, connect_fd, ret;
>
> - /* Adds a rule layer with bind and connect actions. */
> + /* Adds a rule layer with all actions. */
> if (variant->sandbox == TCP_SANDBOX) {
> const struct landlock_ruleset_attr ruleset_attr = {
> - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
> - LANDLOCK_ACCESS_NET_CONNECT_TCP
> + .handled_access_net = ACCESS_ALL
> };
> /* A rule with port value less than 1024. */
> - const struct landlock_net_port_attr tcp_bind_connect_low_range = {
> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> + const struct landlock_net_port_attr tcp_low_range_port = {
> + .allowed_access = ACCESS_ALL,
> .port = 1023,
> };
> /* A rule with 1024 port. */
> - const struct landlock_net_port_attr tcp_bind_connect = {
> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> + const struct landlock_net_port_attr tcp_port_1024 = {
> + .allowed_access = ACCESS_ALL,
> .port = 1024,
> };
> int ruleset_fd;
> @@ -1744,10 +1760,10 @@ TEST_F(port_specific, bind_connect_1023)
>
> ASSERT_EQ(0,
> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> - &tcp_bind_connect_low_range, 0));
> + &tcp_low_range_port, 0));
> ASSERT_EQ(0,
> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> - &tcp_bind_connect, 0));
> + &tcp_port_1024, 0));
>
> enforce_ruleset(_metadata, ruleset_fd);
> EXPECT_EQ(0, close(ruleset_fd));
> @@ -1771,7 +1787,7 @@ TEST_F(port_specific, bind_connect_1023)
> ret = bind_variant(bind_fd, &self->srv0);
> clear_cap(_metadata, CAP_NET_BIND_SERVICE);
> EXPECT_EQ(0, ret);
> - EXPECT_EQ(0, listen(bind_fd, backlog));
> + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
>
> /* Connects on the binded port 1023. */
> ret = connect_variant(connect_fd, &self->srv0);
> @@ -1791,7 +1807,7 @@ TEST_F(port_specific, bind_connect_1023)
> /* Binds on port 1024. */
> ret = bind_variant(bind_fd, &self->srv0);
> EXPECT_EQ(0, ret);
> - EXPECT_EQ(0, listen(bind_fd, backlog));
> + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
>
> /* Connects on the binded port 1024. */
> ret = connect_variant(connect_fd, &self->srv0);
> --
> 2.34.1
>
—Günther
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [RFC PATCH v2 3/9] selftests/landlock: Support LANDLOCK_ACCESS_NET_LISTEN_TCP
2024-08-19 21:52 ` Günther Noack
@ 2024-08-20 12:32 ` Mikhail Ivanov
2024-08-20 13:14 ` Günther Noack
1 sibling, 0 replies; 40+ messages in thread
From: Mikhail Ivanov @ 2024-08-20 12:32 UTC (permalink / raw)
To: Günther Noack
Cc: mic, willemdebruijn.kernel, gnoack3000, linux-security-module,
netdev, netfilter-devel, yusongping, artem.kuzin,
konstantin.meskhidze
8/20/2024 12:52 AM, Günther Noack wrote:
> On Wed, Aug 14, 2024 at 11:01:45AM +0800, Mikhail Ivanov wrote:
>> * Add listen_variant() to simplify listen(2) return code checking.
>> * Rename test_bind_and_connect() to test_restricted_net_fixture().
>> * Extend current net rules with LANDLOCK_ACCESS_NET_LISTEN_TCP access.
>> * Rename test port_specific.bind_connect_1023 to
>> port_specific.port_1023.
>> * Check little endian port restriction for listen in
>> ipv4_tcp.port_endianness.
>> * Some local renames and comment changes.
>>
>> Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
>> ---
>> tools/testing/selftests/landlock/net_test.c | 198 +++++++++++---------
>> 1 file changed, 107 insertions(+), 91 deletions(-)
>>
>> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
>> index f21cfbbc3638..8126f5c0160f 100644
>> --- a/tools/testing/selftests/landlock/net_test.c
>> +++ b/tools/testing/selftests/landlock/net_test.c
>> @@ -2,7 +2,7 @@
>> /*
>> * Landlock tests - Network
>> *
>> - * Copyright © 2022-2023 Huawei Tech. Co., Ltd.
>> + * Copyright © 2022-2024 Huawei Tech. Co., Ltd.
>> * Copyright © 2023 Microsoft Corporation
>> */
>>
>> @@ -22,6 +22,17 @@
>>
>> #include "common.h"
>>
>> +/* clang-format off */
>> +
>> +#define ACCESS_LAST LANDLOCK_ACCESS_NET_LISTEN_TCP
>> +
>> +#define ACCESS_ALL ( \
>> + LANDLOCK_ACCESS_NET_BIND_TCP | \
>> + LANDLOCK_ACCESS_NET_CONNECT_TCP | \
>> + LANDLOCK_ACCESS_NET_LISTEN_TCP)
>> +
>> +/* clang-format on */
>> +
>> const short sock_port_start = (1 << 10);
>>
>> static const char loopback_ipv4[] = "127.0.0.1";
>> @@ -282,6 +293,16 @@ static int connect_variant(const int sock_fd,
>> return connect_variant_addrlen(sock_fd, srv, get_addrlen(srv, false));
>> }
>>
>> +static int listen_variant(const int sock_fd, const int backlog)
>
> I believe socket_variant(), connect_variant() and bind_variant() were called
> like that because they got an instance of a service_fixture as an argument. The
> fixture instances are called variants. But we don't use these fixtures here.
>
> In fs_test.c, we also have some functions that behave much like system calls,
> but clean up after themselves and return errno, for easier use in assert. The
> naming scheme we have used there is "test_foo" (e.g. test_open()). I think this
> would be more appropriate here as a name?
I think such naming is suitable when a function represents a simple
separate test for specific operation that doesn't affect the behavior
of the caller. In current case we just need a wrapper under listen(2)
which returns -errno on failure. Pros of "listen_variant()" is that
it follows the style of other tested syscall helpers ("bind_variant()",
..) but it does seem to be semantically incorrect indeed.
I suggest using a listen(2) synonym - "do_listen()". WDYT?
>
>> +{
>> + int ret;
>> +
>> + ret = listen(sock_fd, backlog);
>> + if (ret < 0)
>> + return -errno;
>> + return ret;
>> +}
>> +
>> FIXTURE(protocol)
>> {
>> struct service_fixture srv0, srv1, srv2, unspec_any0, unspec_srv0;
>> @@ -438,9 +459,11 @@ FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_unix_datagram) {
>> },
>> };
>>
>> -static void test_bind_and_connect(struct __test_metadata *const _metadata,
>> - const struct service_fixture *const srv,
>> - const bool deny_bind, const bool deny_connect)
>> +static void test_restricted_net_fixture(struct __test_metadata *const _metadata,
>> + const struct service_fixture *const srv,
>> + const bool deny_bind,
>> + const bool deny_connect,
>> + const bool deny_listen)
>> {
>> char buf = '\0';
>> int inval_fd, bind_fd, client_fd, status, ret;
>> @@ -512,8 +535,14 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
>> EXPECT_EQ(0, ret);
>>
>> /* Creates a listening socket. */
>> - if (srv->protocol.type == SOCK_STREAM)
>> - EXPECT_EQ(0, listen(bind_fd, backlog));
>> + if (srv->protocol.type == SOCK_STREAM) {
>> + ret = listen_variant(bind_fd, backlog);
>> + if (deny_listen) {
>> + EXPECT_EQ(-EACCES, ret);
>> + } else {
>> + EXPECT_EQ(0, ret);
>> + }
>
> Hmm, passing the expected error code instead of a boolean to this function was not possible?
> Then you could just write
>
> EXPECT_EQ(expected_listen_error, listen_variant(bind_fd, backlog));
>
> ? (Apologies if this was discussed already.)
deny_* arguments are required not only to check an appropriate syscall
behavior. They also test and control the behavior of other operations in
the current helper (e.g. connect(2) returns -ECONNREFUSED on
"deny_bind | deny_listen", listen(2) is not called if deny_bind is set).
>
>> + }
>> }
>>
>> child = fork();
>> @@ -530,7 +559,7 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
>> ret = connect_variant(connect_fd, srv);
>> if (deny_connect) {
>> EXPECT_EQ(-EACCES, ret);
>> - } else if (deny_bind) {
>> + } else if (deny_bind || deny_listen) {
>> /* No listening server. */
>> EXPECT_EQ(-ECONNREFUSED, ret);
>> } else {
>> @@ -545,7 +574,7 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
>>
>> /* Accepts connection from the child. */
>> client_fd = bind_fd;
>> - if (!deny_bind && !deny_connect) {
>> + if (!deny_bind && !deny_connect && !deny_listen) {
>> if (srv->protocol.type == SOCK_STREAM) {
>> client_fd = accept(bind_fd, NULL, 0);
>> ASSERT_LE(0, client_fd);
>> @@ -571,16 +600,15 @@ TEST_F(protocol, bind)
>> {
>> if (variant->sandbox == TCP_SANDBOX) {
>> const struct landlock_ruleset_attr ruleset_attr = {
>> - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> + .handled_access_net = ACCESS_ALL,
>> };
>> - const struct landlock_net_port_attr tcp_bind_connect_p0 = {
>> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> + const struct landlock_net_port_attr tcp_not_restricted_p0 = {
>> + .allowed_access = ACCESS_ALL,
>> .port = self->srv0.port,
>> };
>> - const struct landlock_net_port_attr tcp_connect_p1 = {
>> - .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> + const struct landlock_net_port_attr tcp_denied_bind_p1 = {
>> + .allowed_access = ACCESS_ALL &
>> + ~LANDLOCK_ACCESS_NET_BIND_TCP,
>> .port = self->srv1.port,
>> };
>> int ruleset_fd;
>> @@ -589,48 +617,47 @@ TEST_F(protocol, bind)
>> sizeof(ruleset_attr), 0);
>> ASSERT_LE(0, ruleset_fd);
>>
>> - /* Allows connect and bind for the first port. */
>> + /* Allows all actions for the first port. */
>> ASSERT_EQ(0,
>> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>> - &tcp_bind_connect_p0, 0));
>> + &tcp_not_restricted_p0, 0));
>>
>> - /* Allows connect and denies bind for the second port. */
>> + /* Allows all actions despite bind. */
>> ASSERT_EQ(0,
>> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>> - &tcp_connect_p1, 0));
>> + &tcp_denied_bind_p1, 0));
>>
>> enforce_ruleset(_metadata, ruleset_fd);
>> EXPECT_EQ(0, close(ruleset_fd));
>> }
>> + bool restricted = is_restricted(&variant->prot, variant->sandbox);
>>
>> /* Binds a socket to the first port. */
>> - test_bind_and_connect(_metadata, &self->srv0, false, false);
>> + test_restricted_net_fixture(_metadata, &self->srv0, false, false,
>> + false);
>>
>> /* Binds a socket to the second port. */
>> - test_bind_and_connect(_metadata, &self->srv1,
>> - is_restricted(&variant->prot, variant->sandbox),
>> - false);
>> + test_restricted_net_fixture(_metadata, &self->srv1, restricted, false,
>> + false);
>>
>> /* Binds a socket to the third port. */
>> - test_bind_and_connect(_metadata, &self->srv2,
>> - is_restricted(&variant->prot, variant->sandbox),
>> - is_restricted(&variant->prot, variant->sandbox));
>> + test_restricted_net_fixture(_metadata, &self->srv2, restricted,
>> + restricted, restricted);
>> }
>>
>> TEST_F(protocol, connect)
>> {
>> if (variant->sandbox == TCP_SANDBOX) {
>> const struct landlock_ruleset_attr ruleset_attr = {
>> - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> + .handled_access_net = ACCESS_ALL,
>> };
>> - const struct landlock_net_port_attr tcp_bind_connect_p0 = {
>> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> + const struct landlock_net_port_attr tcp_not_restricted_p0 = {
>> + .allowed_access = ACCESS_ALL,
>> .port = self->srv0.port,
>> };
>> - const struct landlock_net_port_attr tcp_bind_p1 = {
>> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
>> + const struct landlock_net_port_attr tcp_denied_connect_p1 = {
>> + .allowed_access = ACCESS_ALL &
>> + ~LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> .port = self->srv1.port,
>> };
>> int ruleset_fd;
>> @@ -639,28 +666,27 @@ TEST_F(protocol, connect)
>> sizeof(ruleset_attr), 0);
>> ASSERT_LE(0, ruleset_fd);
>>
>> - /* Allows connect and bind for the first port. */
>> + /* Allows all actions for the first port. */
>> ASSERT_EQ(0,
>> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>> - &tcp_bind_connect_p0, 0));
>> + &tcp_not_restricted_p0, 0));
>>
>> - /* Allows bind and denies connect for the second port. */
>> + /* Allows all actions despite connect. */
>> ASSERT_EQ(0,
>> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>> - &tcp_bind_p1, 0));
>> + &tcp_denied_connect_p1, 0));
>>
>> enforce_ruleset(_metadata, ruleset_fd);
>> EXPECT_EQ(0, close(ruleset_fd));
>> }
>> -
>> - test_bind_and_connect(_metadata, &self->srv0, false, false);
>> -
>> - test_bind_and_connect(_metadata, &self->srv1, false,
>> - is_restricted(&variant->prot, variant->sandbox));
>> -
>> - test_bind_and_connect(_metadata, &self->srv2,
>> - is_restricted(&variant->prot, variant->sandbox),
>> - is_restricted(&variant->prot, variant->sandbox));
>> + bool restricted = is_restricted(&variant->prot, variant->sandbox);
>> +
>> + test_restricted_net_fixture(_metadata, &self->srv0, false, false,
>> + false);
>> + test_restricted_net_fixture(_metadata, &self->srv1, false, restricted,
>> + false);
>> + test_restricted_net_fixture(_metadata, &self->srv2, restricted,
>> + restricted, restricted);
>> }
>>
>> TEST_F(protocol, bind_unspec)
>> @@ -761,7 +787,7 @@ TEST_F(protocol, connect_unspec)
>> ASSERT_LE(0, bind_fd);
>> EXPECT_EQ(0, bind_variant(bind_fd, &self->srv0));
>> if (self->srv0.protocol.type == SOCK_STREAM)
>> - EXPECT_EQ(0, listen(bind_fd, backlog));
>> + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
>>
>> child = fork();
>> ASSERT_LE(0, child);
>> @@ -1127,8 +1153,8 @@ TEST_F(tcp_layers, ruleset_overlap)
>> * Forbids to connect to the socket because only one ruleset layer
>> * allows connect.
>> */
>> - test_bind_and_connect(_metadata, &self->srv0, false,
>> - variant->num_layers >= 2);
>> + test_restricted_net_fixture(_metadata, &self->srv0, false,
>> + variant->num_layers >= 2, false);
>> }
>>
>> TEST_F(tcp_layers, ruleset_expand)
>> @@ -1208,11 +1234,12 @@ TEST_F(tcp_layers, ruleset_expand)
>> EXPECT_EQ(0, close(ruleset_fd));
>> }
>>
>> - test_bind_and_connect(_metadata, &self->srv0, false,
>> - variant->num_layers >= 3);
>> + test_restricted_net_fixture(_metadata, &self->srv0, false,
>> + variant->num_layers >= 3, false);
>>
>> - test_bind_and_connect(_metadata, &self->srv1, variant->num_layers >= 1,
>> - variant->num_layers >= 2);
>> + test_restricted_net_fixture(_metadata, &self->srv1,
>> + variant->num_layers >= 1,
>> + variant->num_layers >= 2, false);
>> }
>>
>> /* clang-format off */
>> @@ -1230,16 +1257,6 @@ FIXTURE_TEARDOWN(mini)
>> {
>> }
>>
>> -/* clang-format off */
>> -
>> -#define ACCESS_LAST LANDLOCK_ACCESS_NET_CONNECT_TCP
>> -
>> -#define ACCESS_ALL ( \
>> - LANDLOCK_ACCESS_NET_BIND_TCP | \
>> - LANDLOCK_ACCESS_NET_CONNECT_TCP)
>> -
>> -/* clang-format on */
>> -
>> TEST_F(mini, network_access_rights)
>> {
>> const struct landlock_ruleset_attr ruleset_attr = {
>> @@ -1454,8 +1471,9 @@ TEST_F(mini, tcp_port_overflow)
>>
>> enforce_ruleset(_metadata, ruleset_fd);
>>
>> - test_bind_and_connect(_metadata, &srv_denied, true, true);
>> - test_bind_and_connect(_metadata, &srv_max_allowed, false, false);
>> + test_restricted_net_fixture(_metadata, &srv_denied, true, true, false);
>> + test_restricted_net_fixture(_metadata, &srv_max_allowed, false, false,
>> + false);
>> }
>>
>> FIXTURE(ipv4_tcp)
>> @@ -1485,22 +1503,21 @@ FIXTURE_TEARDOWN(ipv4_tcp)
>> TEST_F(ipv4_tcp, port_endianness)
>> {
>> const struct landlock_ruleset_attr ruleset_attr = {
>> - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> + .handled_access_net = ACCESS_ALL,
>> };
>> const struct landlock_net_port_attr bind_host_endian_p0 = {
>> .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
>> /* Host port format. */
>> .port = self->srv0.port,
>> };
>> - const struct landlock_net_port_attr connect_big_endian_p0 = {
>> - .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> + const struct landlock_net_port_attr connect_listen_big_endian_p0 = {
>> + .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP |
>> + LANDLOCK_ACCESS_NET_LISTEN_TCP,
>> /* Big endian port format. */
>> .port = htons(self->srv0.port),
>> };
>> - const struct landlock_net_port_attr bind_connect_host_endian_p1 = {
>> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> + const struct landlock_net_port_attr not_restricted_host_endian_p1 = {
>> + .allowed_access = ACCESS_ALL,
>> /* Host port format. */
>> .port = self->srv1.port,
>> };
>> @@ -1514,16 +1531,18 @@ TEST_F(ipv4_tcp, port_endianness)
>> ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>> &bind_host_endian_p0, 0));
>> ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>> - &connect_big_endian_p0, 0));
>> + &connect_listen_big_endian_p0, 0));
>> ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>> - &bind_connect_host_endian_p1, 0));
>> + ¬_restricted_host_endian_p1, 0));
>> enforce_ruleset(_metadata, ruleset_fd);
>>
>> /* No restriction for big endinan CPU. */
>> - test_bind_and_connect(_metadata, &self->srv0, false, little_endian);
>> + test_restricted_net_fixture(_metadata, &self->srv0, false,
>> + little_endian, little_endian);
>>
>> /* No restriction for any CPU. */
>> - test_bind_and_connect(_metadata, &self->srv1, false, false);
>> + test_restricted_net_fixture(_metadata, &self->srv1, false, false,
>> + false);
>> }
>>
>> TEST_F(ipv4_tcp, with_fs)
>> @@ -1691,7 +1710,7 @@ TEST_F(port_specific, bind_connect_zero)
>> ret = bind_variant(bind_fd, &self->srv0);
>> EXPECT_EQ(0, ret);
>>
>> - EXPECT_EQ(0, listen(bind_fd, backlog));
>> + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
>>
>> /* Connects on port 0. */
>> ret = connect_variant(connect_fd, &self->srv0);
>> @@ -1714,26 +1733,23 @@ TEST_F(port_specific, bind_connect_zero)
>> EXPECT_EQ(0, close(bind_fd));
>> }
>>
>> -TEST_F(port_specific, bind_connect_1023)
>> +TEST_F(port_specific, port_1023)
>> {
>> int bind_fd, connect_fd, ret;
>>
>> - /* Adds a rule layer with bind and connect actions. */
>> + /* Adds a rule layer with all actions. */
>> if (variant->sandbox == TCP_SANDBOX) {
>> const struct landlock_ruleset_attr ruleset_attr = {
>> - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
>> - LANDLOCK_ACCESS_NET_CONNECT_TCP
>> + .handled_access_net = ACCESS_ALL
>> };
>> /* A rule with port value less than 1024. */
>> - const struct landlock_net_port_attr tcp_bind_connect_low_range = {
>> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> + const struct landlock_net_port_attr tcp_low_range_port = {
>> + .allowed_access = ACCESS_ALL,
>> .port = 1023,
>> };
>> /* A rule with 1024 port. */
>> - const struct landlock_net_port_attr tcp_bind_connect = {
>> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> + const struct landlock_net_port_attr tcp_port_1024 = {
>> + .allowed_access = ACCESS_ALL,
>> .port = 1024,
>> };
>> int ruleset_fd;
>> @@ -1744,10 +1760,10 @@ TEST_F(port_specific, bind_connect_1023)
>>
>> ASSERT_EQ(0,
>> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>> - &tcp_bind_connect_low_range, 0));
>> + &tcp_low_range_port, 0));
>> ASSERT_EQ(0,
>> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>> - &tcp_bind_connect, 0));
>> + &tcp_port_1024, 0));
>>
>> enforce_ruleset(_metadata, ruleset_fd);
>> EXPECT_EQ(0, close(ruleset_fd));
>> @@ -1771,7 +1787,7 @@ TEST_F(port_specific, bind_connect_1023)
>> ret = bind_variant(bind_fd, &self->srv0);
>> clear_cap(_metadata, CAP_NET_BIND_SERVICE);
>> EXPECT_EQ(0, ret);
>> - EXPECT_EQ(0, listen(bind_fd, backlog));
>> + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
>>
>> /* Connects on the binded port 1023. */
>> ret = connect_variant(connect_fd, &self->srv0);
>> @@ -1791,7 +1807,7 @@ TEST_F(port_specific, bind_connect_1023)
>> /* Binds on port 1024. */
>> ret = bind_variant(bind_fd, &self->srv0);
>> EXPECT_EQ(0, ret);
>> - EXPECT_EQ(0, listen(bind_fd, backlog));
>> + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
>>
>> /* Connects on the binded port 1024. */
>> ret = connect_variant(connect_fd, &self->srv0);
>> --
>> 2.34.1
>>
>
> —Günther
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [RFC PATCH v2 3/9] selftests/landlock: Support LANDLOCK_ACCESS_NET_LISTEN_TCP
2024-08-19 21:52 ` Günther Noack
2024-08-20 12:32 ` Mikhail Ivanov
@ 2024-08-20 13:14 ` Günther Noack
2024-08-20 18:27 ` Mikhail Ivanov
1 sibling, 1 reply; 40+ messages in thread
From: Günther Noack @ 2024-08-20 13:14 UTC (permalink / raw)
To: Mikhail Ivanov
Cc: mic, willemdebruijn.kernel, gnoack3000, linux-security-module,
netdev, netfilter-devel, yusongping, artem.kuzin,
konstantin.meskhidze
On Mon, Aug 19, 2024 at 11:52:36PM +0200, Günther Noack wrote:
> On Wed, Aug 14, 2024 at 11:01:45AM +0800, Mikhail Ivanov wrote:
> > * Add listen_variant() to simplify listen(2) return code checking.
> > * Rename test_bind_and_connect() to test_restricted_net_fixture().
> > * Extend current net rules with LANDLOCK_ACCESS_NET_LISTEN_TCP access.
> > * Rename test port_specific.bind_connect_1023 to
> > port_specific.port_1023.
> > * Check little endian port restriction for listen in
> > ipv4_tcp.port_endianness.
> > * Some local renames and comment changes.
> >
> > Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
> > ---
> > tools/testing/selftests/landlock/net_test.c | 198 +++++++++++---------
> > 1 file changed, 107 insertions(+), 91 deletions(-)
> >
> > diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
> > index f21cfbbc3638..8126f5c0160f 100644
> > --- a/tools/testing/selftests/landlock/net_test.c
> > +++ b/tools/testing/selftests/landlock/net_test.c
> > @@ -2,7 +2,7 @@
> > /*
> > * Landlock tests - Network
> > *
> > - * Copyright © 2022-2023 Huawei Tech. Co., Ltd.
> > + * Copyright © 2022-2024 Huawei Tech. Co., Ltd.
> > * Copyright © 2023 Microsoft Corporation
> > */
> >
> > @@ -22,6 +22,17 @@
> >
> > #include "common.h"
> >
> > +/* clang-format off */
> > +
> > +#define ACCESS_LAST LANDLOCK_ACCESS_NET_LISTEN_TCP
> > +
> > +#define ACCESS_ALL ( \
> > + LANDLOCK_ACCESS_NET_BIND_TCP | \
> > + LANDLOCK_ACCESS_NET_CONNECT_TCP | \
> > + LANDLOCK_ACCESS_NET_LISTEN_TCP)
> > +
> > +/* clang-format on */
> > +
> > const short sock_port_start = (1 << 10);
> >
> > static const char loopback_ipv4[] = "127.0.0.1";
> > @@ -282,6 +293,16 @@ static int connect_variant(const int sock_fd,
> > return connect_variant_addrlen(sock_fd, srv, get_addrlen(srv, false));
> > }
> >
> > +static int listen_variant(const int sock_fd, const int backlog)
>
> I believe socket_variant(), connect_variant() and bind_variant() were called
> like that because they got an instance of a service_fixture as an argument. The
> fixture instances are called variants. But we don't use these fixtures here.
>
> In fs_test.c, we also have some functions that behave much like system calls,
> but clean up after themselves and return errno, for easier use in assert. The
> naming scheme we have used there is "test_foo" (e.g. test_open()). I think this
> would be more appropriate here as a name?
>
> > +{
> > + int ret;
> > +
> > + ret = listen(sock_fd, backlog);
> > + if (ret < 0)
> > + return -errno;
> > + return ret;
listen() can only return -1 or 0. It might be simpler to just return 0 here,
to make it more obvious that this returns an error code.
> > +}
Another remark about listen_variant(): The helper functions in net_test.c return
negative error codes, whereas the ones in fs_test.c return positive error codes.
We should probably make that more consistent.
> > +
> > FIXTURE(protocol)
> > {
> > struct service_fixture srv0, srv1, srv2, unspec_any0, unspec_srv0;
> > @@ -438,9 +459,11 @@ FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_unix_datagram) {
> > },
> > };
> >
> > -static void test_bind_and_connect(struct __test_metadata *const _metadata,
> > - const struct service_fixture *const srv,
> > - const bool deny_bind, const bool deny_connect)
> > +static void test_restricted_net_fixture(struct __test_metadata *const _metadata,
> > + const struct service_fixture *const srv,
> > + const bool deny_bind,
> > + const bool deny_connect,
> > + const bool deny_listen)
> > {
> > char buf = '\0';
> > int inval_fd, bind_fd, client_fd, status, ret;
> > @@ -512,8 +535,14 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
> > EXPECT_EQ(0, ret);
> >
> > /* Creates a listening socket. */
> > - if (srv->protocol.type == SOCK_STREAM)
> > - EXPECT_EQ(0, listen(bind_fd, backlog));
> > + if (srv->protocol.type == SOCK_STREAM) {
> > + ret = listen_variant(bind_fd, backlog);
> > + if (deny_listen) {
> > + EXPECT_EQ(-EACCES, ret);
> > + } else {
> > + EXPECT_EQ(0, ret);
> > + }
>
> Hmm, passing the expected error code instead of a boolean to this function was not possible?
> Then you could just write
>
> EXPECT_EQ(expected_listen_error, listen_variant(bind_fd, backlog));
>
> ? (Apologies if this was discussed already.)
>
> > + }
> > }
> >
> > child = fork();
> > @@ -530,7 +559,7 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
> > ret = connect_variant(connect_fd, srv);
> > if (deny_connect) {
> > EXPECT_EQ(-EACCES, ret);
> > - } else if (deny_bind) {
> > + } else if (deny_bind || deny_listen) {
> > /* No listening server. */
> > EXPECT_EQ(-ECONNREFUSED, ret);
> > } else {
> > @@ -545,7 +574,7 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
> >
> > /* Accepts connection from the child. */
> > client_fd = bind_fd;
> > - if (!deny_bind && !deny_connect) {
> > + if (!deny_bind && !deny_connect && !deny_listen) {
> > if (srv->protocol.type == SOCK_STREAM) {
> > client_fd = accept(bind_fd, NULL, 0);
> > ASSERT_LE(0, client_fd);
> > @@ -571,16 +600,15 @@ TEST_F(protocol, bind)
> > {
> > if (variant->sandbox == TCP_SANDBOX) {
> > const struct landlock_ruleset_attr ruleset_attr = {
> > - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
> > - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> > + .handled_access_net = ACCESS_ALL,
> > };
> > - const struct landlock_net_port_attr tcp_bind_connect_p0 = {
> > - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
> > - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> > + const struct landlock_net_port_attr tcp_not_restricted_p0 = {
> > + .allowed_access = ACCESS_ALL,
> > .port = self->srv0.port,
> > };
> > - const struct landlock_net_port_attr tcp_connect_p1 = {
> > - .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
> > + const struct landlock_net_port_attr tcp_denied_bind_p1 = {
> > + .allowed_access = ACCESS_ALL &
> > + ~LANDLOCK_ACCESS_NET_BIND_TCP,
> > .port = self->srv1.port,
> > };
> > int ruleset_fd;
> > @@ -589,48 +617,47 @@ TEST_F(protocol, bind)
> > sizeof(ruleset_attr), 0);
> > ASSERT_LE(0, ruleset_fd);
> >
> > - /* Allows connect and bind for the first port. */
> > + /* Allows all actions for the first port. */
> > ASSERT_EQ(0,
> > landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> > - &tcp_bind_connect_p0, 0));
> > + &tcp_not_restricted_p0, 0));
> >
> > - /* Allows connect and denies bind for the second port. */
> > + /* Allows all actions despite bind. */
> > ASSERT_EQ(0,
> > landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> > - &tcp_connect_p1, 0));
> > + &tcp_denied_bind_p1, 0));
> >
> > enforce_ruleset(_metadata, ruleset_fd);
> > EXPECT_EQ(0, close(ruleset_fd));
> > }
> > + bool restricted = is_restricted(&variant->prot, variant->sandbox);
> >
> > /* Binds a socket to the first port. */
> > - test_bind_and_connect(_metadata, &self->srv0, false, false);
> > + test_restricted_net_fixture(_metadata, &self->srv0, false, false,
> > + false);
> >
> > /* Binds a socket to the second port. */
> > - test_bind_and_connect(_metadata, &self->srv1,
> > - is_restricted(&variant->prot, variant->sandbox),
> > - false);
> > + test_restricted_net_fixture(_metadata, &self->srv1, restricted, false,
> > + false);
> >
> > /* Binds a socket to the third port. */
> > - test_bind_and_connect(_metadata, &self->srv2,
> > - is_restricted(&variant->prot, variant->sandbox),
> > - is_restricted(&variant->prot, variant->sandbox));
> > + test_restricted_net_fixture(_metadata, &self->srv2, restricted,
> > + restricted, restricted);
> > }
> >
> > TEST_F(protocol, connect)
> > {
> > if (variant->sandbox == TCP_SANDBOX) {
> > const struct landlock_ruleset_attr ruleset_attr = {
> > - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
> > - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> > + .handled_access_net = ACCESS_ALL,
> > };
> > - const struct landlock_net_port_attr tcp_bind_connect_p0 = {
> > - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
> > - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> > + const struct landlock_net_port_attr tcp_not_restricted_p0 = {
> > + .allowed_access = ACCESS_ALL,
> > .port = self->srv0.port,
> > };
> > - const struct landlock_net_port_attr tcp_bind_p1 = {
> > - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
> > + const struct landlock_net_port_attr tcp_denied_connect_p1 = {
> > + .allowed_access = ACCESS_ALL &
> > + ~LANDLOCK_ACCESS_NET_CONNECT_TCP,
> > .port = self->srv1.port,
> > };
> > int ruleset_fd;
> > @@ -639,28 +666,27 @@ TEST_F(protocol, connect)
> > sizeof(ruleset_attr), 0);
> > ASSERT_LE(0, ruleset_fd);
> >
> > - /* Allows connect and bind for the first port. */
> > + /* Allows all actions for the first port. */
> > ASSERT_EQ(0,
> > landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> > - &tcp_bind_connect_p0, 0));
> > + &tcp_not_restricted_p0, 0));
> >
> > - /* Allows bind and denies connect for the second port. */
> > + /* Allows all actions despite connect. */
> > ASSERT_EQ(0,
> > landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> > - &tcp_bind_p1, 0));
> > + &tcp_denied_connect_p1, 0));
> >
> > enforce_ruleset(_metadata, ruleset_fd);
> > EXPECT_EQ(0, close(ruleset_fd));
> > }
> > -
> > - test_bind_and_connect(_metadata, &self->srv0, false, false);
> > -
> > - test_bind_and_connect(_metadata, &self->srv1, false,
> > - is_restricted(&variant->prot, variant->sandbox));
> > -
> > - test_bind_and_connect(_metadata, &self->srv2,
> > - is_restricted(&variant->prot, variant->sandbox),
> > - is_restricted(&variant->prot, variant->sandbox));
> > + bool restricted = is_restricted(&variant->prot, variant->sandbox);
> > +
> > + test_restricted_net_fixture(_metadata, &self->srv0, false, false,
> > + false);
> > + test_restricted_net_fixture(_metadata, &self->srv1, false, restricted,
> > + false);
> > + test_restricted_net_fixture(_metadata, &self->srv2, restricted,
> > + restricted, restricted);
> > }
> >
> > TEST_F(protocol, bind_unspec)
> > @@ -761,7 +787,7 @@ TEST_F(protocol, connect_unspec)
> > ASSERT_LE(0, bind_fd);
> > EXPECT_EQ(0, bind_variant(bind_fd, &self->srv0));
> > if (self->srv0.protocol.type == SOCK_STREAM)
> > - EXPECT_EQ(0, listen(bind_fd, backlog));
> > + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
> >
> > child = fork();
> > ASSERT_LE(0, child);
> > @@ -1127,8 +1153,8 @@ TEST_F(tcp_layers, ruleset_overlap)
> > * Forbids to connect to the socket because only one ruleset layer
> > * allows connect.
> > */
> > - test_bind_and_connect(_metadata, &self->srv0, false,
> > - variant->num_layers >= 2);
> > + test_restricted_net_fixture(_metadata, &self->srv0, false,
> > + variant->num_layers >= 2, false);
> > }
> >
> > TEST_F(tcp_layers, ruleset_expand)
> > @@ -1208,11 +1234,12 @@ TEST_F(tcp_layers, ruleset_expand)
> > EXPECT_EQ(0, close(ruleset_fd));
> > }
> >
> > - test_bind_and_connect(_metadata, &self->srv0, false,
> > - variant->num_layers >= 3);
> > + test_restricted_net_fixture(_metadata, &self->srv0, false,
> > + variant->num_layers >= 3, false);
> >
> > - test_bind_and_connect(_metadata, &self->srv1, variant->num_layers >= 1,
> > - variant->num_layers >= 2);
> > + test_restricted_net_fixture(_metadata, &self->srv1,
> > + variant->num_layers >= 1,
> > + variant->num_layers >= 2, false);
> > }
> >
> > /* clang-format off */
> > @@ -1230,16 +1257,6 @@ FIXTURE_TEARDOWN(mini)
> > {
> > }
> >
> > -/* clang-format off */
> > -
> > -#define ACCESS_LAST LANDLOCK_ACCESS_NET_CONNECT_TCP
> > -
> > -#define ACCESS_ALL ( \
> > - LANDLOCK_ACCESS_NET_BIND_TCP | \
> > - LANDLOCK_ACCESS_NET_CONNECT_TCP)
> > -
> > -/* clang-format on */
> > -
> > TEST_F(mini, network_access_rights)
> > {
> > const struct landlock_ruleset_attr ruleset_attr = {
> > @@ -1454,8 +1471,9 @@ TEST_F(mini, tcp_port_overflow)
> >
> > enforce_ruleset(_metadata, ruleset_fd);
> >
> > - test_bind_and_connect(_metadata, &srv_denied, true, true);
> > - test_bind_and_connect(_metadata, &srv_max_allowed, false, false);
> > + test_restricted_net_fixture(_metadata, &srv_denied, true, true, false);
> > + test_restricted_net_fixture(_metadata, &srv_max_allowed, false, false,
> > + false);
> > }
> >
> > FIXTURE(ipv4_tcp)
> > @@ -1485,22 +1503,21 @@ FIXTURE_TEARDOWN(ipv4_tcp)
> > TEST_F(ipv4_tcp, port_endianness)
> > {
> > const struct landlock_ruleset_attr ruleset_attr = {
> > - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
> > - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> > + .handled_access_net = ACCESS_ALL,
> > };
> > const struct landlock_net_port_attr bind_host_endian_p0 = {
> > .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
> > /* Host port format. */
> > .port = self->srv0.port,
> > };
> > - const struct landlock_net_port_attr connect_big_endian_p0 = {
> > - .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
> > + const struct landlock_net_port_attr connect_listen_big_endian_p0 = {
> > + .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP |
> > + LANDLOCK_ACCESS_NET_LISTEN_TCP,
> > /* Big endian port format. */
> > .port = htons(self->srv0.port),
> > };
> > - const struct landlock_net_port_attr bind_connect_host_endian_p1 = {
> > - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
> > - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> > + const struct landlock_net_port_attr not_restricted_host_endian_p1 = {
> > + .allowed_access = ACCESS_ALL,
> > /* Host port format. */
> > .port = self->srv1.port,
> > };
> > @@ -1514,16 +1531,18 @@ TEST_F(ipv4_tcp, port_endianness)
> > ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> > &bind_host_endian_p0, 0));
> > ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> > - &connect_big_endian_p0, 0));
> > + &connect_listen_big_endian_p0, 0));
> > ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> > - &bind_connect_host_endian_p1, 0));
> > + ¬_restricted_host_endian_p1, 0));
> > enforce_ruleset(_metadata, ruleset_fd);
> >
> > /* No restriction for big endinan CPU. */
> > - test_bind_and_connect(_metadata, &self->srv0, false, little_endian);
> > + test_restricted_net_fixture(_metadata, &self->srv0, false,
> > + little_endian, little_endian);
> >
> > /* No restriction for any CPU. */
> > - test_bind_and_connect(_metadata, &self->srv1, false, false);
> > + test_restricted_net_fixture(_metadata, &self->srv1, false, false,
> > + false);
> > }
> >
> > TEST_F(ipv4_tcp, with_fs)
> > @@ -1691,7 +1710,7 @@ TEST_F(port_specific, bind_connect_zero)
> > ret = bind_variant(bind_fd, &self->srv0);
> > EXPECT_EQ(0, ret);
> >
> > - EXPECT_EQ(0, listen(bind_fd, backlog));
> > + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
> >
> > /* Connects on port 0. */
> > ret = connect_variant(connect_fd, &self->srv0);
> > @@ -1714,26 +1733,23 @@ TEST_F(port_specific, bind_connect_zero)
> > EXPECT_EQ(0, close(bind_fd));
> > }
> >
> > -TEST_F(port_specific, bind_connect_1023)
> > +TEST_F(port_specific, port_1023)
> > {
> > int bind_fd, connect_fd, ret;
> >
> > - /* Adds a rule layer with bind and connect actions. */
> > + /* Adds a rule layer with all actions. */
> > if (variant->sandbox == TCP_SANDBOX) {
> > const struct landlock_ruleset_attr ruleset_attr = {
> > - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
> > - LANDLOCK_ACCESS_NET_CONNECT_TCP
> > + .handled_access_net = ACCESS_ALL
> > };
> > /* A rule with port value less than 1024. */
> > - const struct landlock_net_port_attr tcp_bind_connect_low_range = {
> > - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
> > - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> > + const struct landlock_net_port_attr tcp_low_range_port = {
> > + .allowed_access = ACCESS_ALL,
> > .port = 1023,
> > };
> > /* A rule with 1024 port. */
> > - const struct landlock_net_port_attr tcp_bind_connect = {
> > - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
> > - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> > + const struct landlock_net_port_attr tcp_port_1024 = {
> > + .allowed_access = ACCESS_ALL,
> > .port = 1024,
> > };
> > int ruleset_fd;
> > @@ -1744,10 +1760,10 @@ TEST_F(port_specific, bind_connect_1023)
> >
> > ASSERT_EQ(0,
> > landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> > - &tcp_bind_connect_low_range, 0));
> > + &tcp_low_range_port, 0));
> > ASSERT_EQ(0,
> > landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> > - &tcp_bind_connect, 0));
> > + &tcp_port_1024, 0));
> >
> > enforce_ruleset(_metadata, ruleset_fd);
> > EXPECT_EQ(0, close(ruleset_fd));
> > @@ -1771,7 +1787,7 @@ TEST_F(port_specific, bind_connect_1023)
> > ret = bind_variant(bind_fd, &self->srv0);
> > clear_cap(_metadata, CAP_NET_BIND_SERVICE);
> > EXPECT_EQ(0, ret);
> > - EXPECT_EQ(0, listen(bind_fd, backlog));
> > + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
> >
> > /* Connects on the binded port 1023. */
> > ret = connect_variant(connect_fd, &self->srv0);
> > @@ -1791,7 +1807,7 @@ TEST_F(port_specific, bind_connect_1023)
> > /* Binds on port 1024. */
> > ret = bind_variant(bind_fd, &self->srv0);
> > EXPECT_EQ(0, ret);
> > - EXPECT_EQ(0, listen(bind_fd, backlog));
> > + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
> >
> > /* Connects on the binded port 1024. */
> > ret = connect_variant(connect_fd, &self->srv0);
> > --
> > 2.34.1
> >
>
> —Günther
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [RFC PATCH v2 3/9] selftests/landlock: Support LANDLOCK_ACCESS_NET_LISTEN_TCP
2024-08-20 13:14 ` Günther Noack
@ 2024-08-20 18:27 ` Mikhail Ivanov
2024-09-25 18:31 ` Mickaël Salaün
0 siblings, 1 reply; 40+ messages in thread
From: Mikhail Ivanov @ 2024-08-20 18:27 UTC (permalink / raw)
To: Günther Noack
Cc: mic, willemdebruijn.kernel, gnoack3000, linux-security-module,
netdev, netfilter-devel, yusongping, artem.kuzin,
konstantin.meskhidze
8/20/2024 4:14 PM, Günther Noack wrote:
> On Mon, Aug 19, 2024 at 11:52:36PM +0200, Günther Noack wrote:
>> On Wed, Aug 14, 2024 at 11:01:45AM +0800, Mikhail Ivanov wrote:
>>> * Add listen_variant() to simplify listen(2) return code checking.
>>> * Rename test_bind_and_connect() to test_restricted_net_fixture().
>>> * Extend current net rules with LANDLOCK_ACCESS_NET_LISTEN_TCP access.
>>> * Rename test port_specific.bind_connect_1023 to
>>> port_specific.port_1023.
>>> * Check little endian port restriction for listen in
>>> ipv4_tcp.port_endianness.
>>> * Some local renames and comment changes.
>>>
>>> Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
>>> ---
>>> tools/testing/selftests/landlock/net_test.c | 198 +++++++++++---------
>>> 1 file changed, 107 insertions(+), 91 deletions(-)
>>>
>>> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
>>> index f21cfbbc3638..8126f5c0160f 100644
>>> --- a/tools/testing/selftests/landlock/net_test.c
>>> +++ b/tools/testing/selftests/landlock/net_test.c
>>> @@ -2,7 +2,7 @@
>>> /*
>>> * Landlock tests - Network
>>> *
>>> - * Copyright © 2022-2023 Huawei Tech. Co., Ltd.
>>> + * Copyright © 2022-2024 Huawei Tech. Co., Ltd.
>>> * Copyright © 2023 Microsoft Corporation
>>> */
>>>
>>> @@ -22,6 +22,17 @@
>>>
>>> #include "common.h"
>>>
>>> +/* clang-format off */
>>> +
>>> +#define ACCESS_LAST LANDLOCK_ACCESS_NET_LISTEN_TCP
>>> +
>>> +#define ACCESS_ALL ( \
>>> + LANDLOCK_ACCESS_NET_BIND_TCP | \
>>> + LANDLOCK_ACCESS_NET_CONNECT_TCP | \
>>> + LANDLOCK_ACCESS_NET_LISTEN_TCP)
>>> +
>>> +/* clang-format on */
>>> +
>>> const short sock_port_start = (1 << 10);
>>>
>>> static const char loopback_ipv4[] = "127.0.0.1";
>>> @@ -282,6 +293,16 @@ static int connect_variant(const int sock_fd,
>>> return connect_variant_addrlen(sock_fd, srv, get_addrlen(srv, false));
>>> }
>>>
>>> +static int listen_variant(const int sock_fd, const int backlog)
>>
>> I believe socket_variant(), connect_variant() and bind_variant() were called
>> like that because they got an instance of a service_fixture as an argument. The
>> fixture instances are called variants. But we don't use these fixtures here.
>>
>> In fs_test.c, we also have some functions that behave much like system calls,
>> but clean up after themselves and return errno, for easier use in assert. The
>> naming scheme we have used there is "test_foo" (e.g. test_open()). I think this
>> would be more appropriate here as a name?
>>
>>> +{
>>> + int ret;
>>> +
>>> + ret = listen(sock_fd, backlog);
>>> + if (ret < 0)
>>> + return -errno;
>>> + return ret;
>
> listen() can only return -1 or 0. It might be simpler to just return 0 here,
> to make it more obvious that this returns an error code.
Agreed, thanks. I'll do such refactoring for the connect_variant() as
well.
>
>>> +}
>
> Another remark about listen_variant(): The helper functions in net_test.c return
> negative error codes, whereas the ones in fs_test.c return positive error codes.
> We should probably make that more consistent.
socket_variant() returns positive descriptor in a case of success, so
let's use negative ones.
Should it be a separate patch btw?
>
>>> +
>>> FIXTURE(protocol)
>>> {
>>> struct service_fixture srv0, srv1, srv2, unspec_any0, unspec_srv0;
>>> @@ -438,9 +459,11 @@ FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_unix_datagram) {
>>> },
>>> };
>>>
>>> -static void test_bind_and_connect(struct __test_metadata *const _metadata,
>>> - const struct service_fixture *const srv,
>>> - const bool deny_bind, const bool deny_connect)
>>> +static void test_restricted_net_fixture(struct __test_metadata *const _metadata,
>>> + const struct service_fixture *const srv,
>>> + const bool deny_bind,
>>> + const bool deny_connect,
>>> + const bool deny_listen)
>>> {
>>> char buf = '\0';
>>> int inval_fd, bind_fd, client_fd, status, ret;
>>> @@ -512,8 +535,14 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
>>> EXPECT_EQ(0, ret);
>>>
>>> /* Creates a listening socket. */
>>> - if (srv->protocol.type == SOCK_STREAM)
>>> - EXPECT_EQ(0, listen(bind_fd, backlog));
>>> + if (srv->protocol.type == SOCK_STREAM) {
>>> + ret = listen_variant(bind_fd, backlog);
>>> + if (deny_listen) {
>>> + EXPECT_EQ(-EACCES, ret);
>>> + } else {
>>> + EXPECT_EQ(0, ret);
>>> + }
>>
>> Hmm, passing the expected error code instead of a boolean to this function was not possible?
>> Then you could just write
>>
>> EXPECT_EQ(expected_listen_error, listen_variant(bind_fd, backlog));
>>
>> ? (Apologies if this was discussed already.)
>>
>>> + }
>>> }
>>>
>>> child = fork();
>>> @@ -530,7 +559,7 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
>>> ret = connect_variant(connect_fd, srv);
>>> if (deny_connect) {
>>> EXPECT_EQ(-EACCES, ret);
>>> - } else if (deny_bind) {
>>> + } else if (deny_bind || deny_listen) {
>>> /* No listening server. */
>>> EXPECT_EQ(-ECONNREFUSED, ret);
>>> } else {
>>> @@ -545,7 +574,7 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
>>>
>>> /* Accepts connection from the child. */
>>> client_fd = bind_fd;
>>> - if (!deny_bind && !deny_connect) {
>>> + if (!deny_bind && !deny_connect && !deny_listen) {
>>> if (srv->protocol.type == SOCK_STREAM) {
>>> client_fd = accept(bind_fd, NULL, 0);
>>> ASSERT_LE(0, client_fd);
>>> @@ -571,16 +600,15 @@ TEST_F(protocol, bind)
>>> {
>>> if (variant->sandbox == TCP_SANDBOX) {
>>> const struct landlock_ruleset_attr ruleset_attr = {
>>> - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
>>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>> + .handled_access_net = ACCESS_ALL,
>>> };
>>> - const struct landlock_net_port_attr tcp_bind_connect_p0 = {
>>> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
>>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>> + const struct landlock_net_port_attr tcp_not_restricted_p0 = {
>>> + .allowed_access = ACCESS_ALL,
>>> .port = self->srv0.port,
>>> };
>>> - const struct landlock_net_port_attr tcp_connect_p1 = {
>>> - .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>> + const struct landlock_net_port_attr tcp_denied_bind_p1 = {
>>> + .allowed_access = ACCESS_ALL &
>>> + ~LANDLOCK_ACCESS_NET_BIND_TCP,
>>> .port = self->srv1.port,
>>> };
>>> int ruleset_fd;
>>> @@ -589,48 +617,47 @@ TEST_F(protocol, bind)
>>> sizeof(ruleset_attr), 0);
>>> ASSERT_LE(0, ruleset_fd);
>>>
>>> - /* Allows connect and bind for the first port. */
>>> + /* Allows all actions for the first port. */
>>> ASSERT_EQ(0,
>>> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>>> - &tcp_bind_connect_p0, 0));
>>> + &tcp_not_restricted_p0, 0));
>>>
>>> - /* Allows connect and denies bind for the second port. */
>>> + /* Allows all actions despite bind. */
>>> ASSERT_EQ(0,
>>> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>>> - &tcp_connect_p1, 0));
>>> + &tcp_denied_bind_p1, 0));
>>>
>>> enforce_ruleset(_metadata, ruleset_fd);
>>> EXPECT_EQ(0, close(ruleset_fd));
>>> }
>>> + bool restricted = is_restricted(&variant->prot, variant->sandbox);
>>>
>>> /* Binds a socket to the first port. */
>>> - test_bind_and_connect(_metadata, &self->srv0, false, false);
>>> + test_restricted_net_fixture(_metadata, &self->srv0, false, false,
>>> + false);
>>>
>>> /* Binds a socket to the second port. */
>>> - test_bind_and_connect(_metadata, &self->srv1,
>>> - is_restricted(&variant->prot, variant->sandbox),
>>> - false);
>>> + test_restricted_net_fixture(_metadata, &self->srv1, restricted, false,
>>> + false);
>>>
>>> /* Binds a socket to the third port. */
>>> - test_bind_and_connect(_metadata, &self->srv2,
>>> - is_restricted(&variant->prot, variant->sandbox),
>>> - is_restricted(&variant->prot, variant->sandbox));
>>> + test_restricted_net_fixture(_metadata, &self->srv2, restricted,
>>> + restricted, restricted);
>>> }
>>>
>>> TEST_F(protocol, connect)
>>> {
>>> if (variant->sandbox == TCP_SANDBOX) {
>>> const struct landlock_ruleset_attr ruleset_attr = {
>>> - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
>>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>> + .handled_access_net = ACCESS_ALL,
>>> };
>>> - const struct landlock_net_port_attr tcp_bind_connect_p0 = {
>>> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
>>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>> + const struct landlock_net_port_attr tcp_not_restricted_p0 = {
>>> + .allowed_access = ACCESS_ALL,
>>> .port = self->srv0.port,
>>> };
>>> - const struct landlock_net_port_attr tcp_bind_p1 = {
>>> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
>>> + const struct landlock_net_port_attr tcp_denied_connect_p1 = {
>>> + .allowed_access = ACCESS_ALL &
>>> + ~LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>> .port = self->srv1.port,
>>> };
>>> int ruleset_fd;
>>> @@ -639,28 +666,27 @@ TEST_F(protocol, connect)
>>> sizeof(ruleset_attr), 0);
>>> ASSERT_LE(0, ruleset_fd);
>>>
>>> - /* Allows connect and bind for the first port. */
>>> + /* Allows all actions for the first port. */
>>> ASSERT_EQ(0,
>>> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>>> - &tcp_bind_connect_p0, 0));
>>> + &tcp_not_restricted_p0, 0));
>>>
>>> - /* Allows bind and denies connect for the second port. */
>>> + /* Allows all actions despite connect. */
>>> ASSERT_EQ(0,
>>> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>>> - &tcp_bind_p1, 0));
>>> + &tcp_denied_connect_p1, 0));
>>>
>>> enforce_ruleset(_metadata, ruleset_fd);
>>> EXPECT_EQ(0, close(ruleset_fd));
>>> }
>>> -
>>> - test_bind_and_connect(_metadata, &self->srv0, false, false);
>>> -
>>> - test_bind_and_connect(_metadata, &self->srv1, false,
>>> - is_restricted(&variant->prot, variant->sandbox));
>>> -
>>> - test_bind_and_connect(_metadata, &self->srv2,
>>> - is_restricted(&variant->prot, variant->sandbox),
>>> - is_restricted(&variant->prot, variant->sandbox));
>>> + bool restricted = is_restricted(&variant->prot, variant->sandbox);
>>> +
>>> + test_restricted_net_fixture(_metadata, &self->srv0, false, false,
>>> + false);
>>> + test_restricted_net_fixture(_metadata, &self->srv1, false, restricted,
>>> + false);
>>> + test_restricted_net_fixture(_metadata, &self->srv2, restricted,
>>> + restricted, restricted);
>>> }
>>>
>>> TEST_F(protocol, bind_unspec)
>>> @@ -761,7 +787,7 @@ TEST_F(protocol, connect_unspec)
>>> ASSERT_LE(0, bind_fd);
>>> EXPECT_EQ(0, bind_variant(bind_fd, &self->srv0));
>>> if (self->srv0.protocol.type == SOCK_STREAM)
>>> - EXPECT_EQ(0, listen(bind_fd, backlog));
>>> + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
>>>
>>> child = fork();
>>> ASSERT_LE(0, child);
>>> @@ -1127,8 +1153,8 @@ TEST_F(tcp_layers, ruleset_overlap)
>>> * Forbids to connect to the socket because only one ruleset layer
>>> * allows connect.
>>> */
>>> - test_bind_and_connect(_metadata, &self->srv0, false,
>>> - variant->num_layers >= 2);
>>> + test_restricted_net_fixture(_metadata, &self->srv0, false,
>>> + variant->num_layers >= 2, false);
>>> }
>>>
>>> TEST_F(tcp_layers, ruleset_expand)
>>> @@ -1208,11 +1234,12 @@ TEST_F(tcp_layers, ruleset_expand)
>>> EXPECT_EQ(0, close(ruleset_fd));
>>> }
>>>
>>> - test_bind_and_connect(_metadata, &self->srv0, false,
>>> - variant->num_layers >= 3);
>>> + test_restricted_net_fixture(_metadata, &self->srv0, false,
>>> + variant->num_layers >= 3, false);
>>>
>>> - test_bind_and_connect(_metadata, &self->srv1, variant->num_layers >= 1,
>>> - variant->num_layers >= 2);
>>> + test_restricted_net_fixture(_metadata, &self->srv1,
>>> + variant->num_layers >= 1,
>>> + variant->num_layers >= 2, false);
>>> }
>>>
>>> /* clang-format off */
>>> @@ -1230,16 +1257,6 @@ FIXTURE_TEARDOWN(mini)
>>> {
>>> }
>>>
>>> -/* clang-format off */
>>> -
>>> -#define ACCESS_LAST LANDLOCK_ACCESS_NET_CONNECT_TCP
>>> -
>>> -#define ACCESS_ALL ( \
>>> - LANDLOCK_ACCESS_NET_BIND_TCP | \
>>> - LANDLOCK_ACCESS_NET_CONNECT_TCP)
>>> -
>>> -/* clang-format on */
>>> -
>>> TEST_F(mini, network_access_rights)
>>> {
>>> const struct landlock_ruleset_attr ruleset_attr = {
>>> @@ -1454,8 +1471,9 @@ TEST_F(mini, tcp_port_overflow)
>>>
>>> enforce_ruleset(_metadata, ruleset_fd);
>>>
>>> - test_bind_and_connect(_metadata, &srv_denied, true, true);
>>> - test_bind_and_connect(_metadata, &srv_max_allowed, false, false);
>>> + test_restricted_net_fixture(_metadata, &srv_denied, true, true, false);
>>> + test_restricted_net_fixture(_metadata, &srv_max_allowed, false, false,
>>> + false);
>>> }
>>>
>>> FIXTURE(ipv4_tcp)
>>> @@ -1485,22 +1503,21 @@ FIXTURE_TEARDOWN(ipv4_tcp)
>>> TEST_F(ipv4_tcp, port_endianness)
>>> {
>>> const struct landlock_ruleset_attr ruleset_attr = {
>>> - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
>>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>> + .handled_access_net = ACCESS_ALL,
>>> };
>>> const struct landlock_net_port_attr bind_host_endian_p0 = {
>>> .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
>>> /* Host port format. */
>>> .port = self->srv0.port,
>>> };
>>> - const struct landlock_net_port_attr connect_big_endian_p0 = {
>>> - .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>> + const struct landlock_net_port_attr connect_listen_big_endian_p0 = {
>>> + .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP |
>>> + LANDLOCK_ACCESS_NET_LISTEN_TCP,
>>> /* Big endian port format. */
>>> .port = htons(self->srv0.port),
>>> };
>>> - const struct landlock_net_port_attr bind_connect_host_endian_p1 = {
>>> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
>>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>> + const struct landlock_net_port_attr not_restricted_host_endian_p1 = {
>>> + .allowed_access = ACCESS_ALL,
>>> /* Host port format. */
>>> .port = self->srv1.port,
>>> };
>>> @@ -1514,16 +1531,18 @@ TEST_F(ipv4_tcp, port_endianness)
>>> ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>>> &bind_host_endian_p0, 0));
>>> ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>>> - &connect_big_endian_p0, 0));
>>> + &connect_listen_big_endian_p0, 0));
>>> ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>>> - &bind_connect_host_endian_p1, 0));
>>> + ¬_restricted_host_endian_p1, 0));
>>> enforce_ruleset(_metadata, ruleset_fd);
>>>
>>> /* No restriction for big endinan CPU. */
>>> - test_bind_and_connect(_metadata, &self->srv0, false, little_endian);
>>> + test_restricted_net_fixture(_metadata, &self->srv0, false,
>>> + little_endian, little_endian);
>>>
>>> /* No restriction for any CPU. */
>>> - test_bind_and_connect(_metadata, &self->srv1, false, false);
>>> + test_restricted_net_fixture(_metadata, &self->srv1, false, false,
>>> + false);
>>> }
>>>
>>> TEST_F(ipv4_tcp, with_fs)
>>> @@ -1691,7 +1710,7 @@ TEST_F(port_specific, bind_connect_zero)
>>> ret = bind_variant(bind_fd, &self->srv0);
>>> EXPECT_EQ(0, ret);
>>>
>>> - EXPECT_EQ(0, listen(bind_fd, backlog));
>>> + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
>>>
>>> /* Connects on port 0. */
>>> ret = connect_variant(connect_fd, &self->srv0);
>>> @@ -1714,26 +1733,23 @@ TEST_F(port_specific, bind_connect_zero)
>>> EXPECT_EQ(0, close(bind_fd));
>>> }
>>>
>>> -TEST_F(port_specific, bind_connect_1023)
>>> +TEST_F(port_specific, port_1023)
>>> {
>>> int bind_fd, connect_fd, ret;
>>>
>>> - /* Adds a rule layer with bind and connect actions. */
>>> + /* Adds a rule layer with all actions. */
>>> if (variant->sandbox == TCP_SANDBOX) {
>>> const struct landlock_ruleset_attr ruleset_attr = {
>>> - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
>>> - LANDLOCK_ACCESS_NET_CONNECT_TCP
>>> + .handled_access_net = ACCESS_ALL
>>> };
>>> /* A rule with port value less than 1024. */
>>> - const struct landlock_net_port_attr tcp_bind_connect_low_range = {
>>> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
>>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>> + const struct landlock_net_port_attr tcp_low_range_port = {
>>> + .allowed_access = ACCESS_ALL,
>>> .port = 1023,
>>> };
>>> /* A rule with 1024 port. */
>>> - const struct landlock_net_port_attr tcp_bind_connect = {
>>> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
>>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>> + const struct landlock_net_port_attr tcp_port_1024 = {
>>> + .allowed_access = ACCESS_ALL,
>>> .port = 1024,
>>> };
>>> int ruleset_fd;
>>> @@ -1744,10 +1760,10 @@ TEST_F(port_specific, bind_connect_1023)
>>>
>>> ASSERT_EQ(0,
>>> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>>> - &tcp_bind_connect_low_range, 0));
>>> + &tcp_low_range_port, 0));
>>> ASSERT_EQ(0,
>>> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>>> - &tcp_bind_connect, 0));
>>> + &tcp_port_1024, 0));
>>>
>>> enforce_ruleset(_metadata, ruleset_fd);
>>> EXPECT_EQ(0, close(ruleset_fd));
>>> @@ -1771,7 +1787,7 @@ TEST_F(port_specific, bind_connect_1023)
>>> ret = bind_variant(bind_fd, &self->srv0);
>>> clear_cap(_metadata, CAP_NET_BIND_SERVICE);
>>> EXPECT_EQ(0, ret);
>>> - EXPECT_EQ(0, listen(bind_fd, backlog));
>>> + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
>>>
>>> /* Connects on the binded port 1023. */
>>> ret = connect_variant(connect_fd, &self->srv0);
>>> @@ -1791,7 +1807,7 @@ TEST_F(port_specific, bind_connect_1023)
>>> /* Binds on port 1024. */
>>> ret = bind_variant(bind_fd, &self->srv0);
>>> EXPECT_EQ(0, ret);
>>> - EXPECT_EQ(0, listen(bind_fd, backlog));
>>> + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
>>>
>>> /* Connects on the binded port 1024. */
>>> ret = connect_variant(connect_fd, &self->srv0);
>>> --
>>> 2.34.1
>>>
>>
>> —Günther
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [RFC PATCH v2 3/9] selftests/landlock: Support LANDLOCK_ACCESS_NET_LISTEN_TCP
2024-08-20 18:27 ` Mikhail Ivanov
@ 2024-09-25 18:31 ` Mickaël Salaün
2024-09-26 11:59 ` Mikhail Ivanov
0 siblings, 1 reply; 40+ messages in thread
From: Mickaël Salaün @ 2024-09-25 18:31 UTC (permalink / raw)
To: Mikhail Ivanov
Cc: Günther Noack, willemdebruijn.kernel, gnoack3000,
linux-security-module, netdev, netfilter-devel, yusongping,
artem.kuzin, konstantin.meskhidze, Matthieu Buffet
On Tue, Aug 20, 2024 at 09:27:10PM +0300, Mikhail Ivanov wrote:
> 8/20/2024 4:14 PM, Günther Noack wrote:
> > On Mon, Aug 19, 2024 at 11:52:36PM +0200, Günther Noack wrote:
> > > On Wed, Aug 14, 2024 at 11:01:45AM +0800, Mikhail Ivanov wrote:
> > > > * Add listen_variant() to simplify listen(2) return code checking.
> > > > * Rename test_bind_and_connect() to test_restricted_net_fixture().
> > > > * Extend current net rules with LANDLOCK_ACCESS_NET_LISTEN_TCP access.
> > > > * Rename test port_specific.bind_connect_1023 to
> > > > port_specific.port_1023.
> > > > * Check little endian port restriction for listen in
> > > > ipv4_tcp.port_endianness.
> > > > * Some local renames and comment changes.
> > > >
> > > > Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
> > > > ---
> > > > tools/testing/selftests/landlock/net_test.c | 198 +++++++++++---------
> > > > 1 file changed, 107 insertions(+), 91 deletions(-)
> > > >
> > > > diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
> > > > index f21cfbbc3638..8126f5c0160f 100644
> > > > --- a/tools/testing/selftests/landlock/net_test.c
> > > > +++ b/tools/testing/selftests/landlock/net_test.c
> > > > @@ -2,7 +2,7 @@
> > > > /*
> > > > * Landlock tests - Network
> > > > *
> > > > - * Copyright © 2022-2023 Huawei Tech. Co., Ltd.
> > > > + * Copyright © 2022-2024 Huawei Tech. Co., Ltd.
> > > > * Copyright © 2023 Microsoft Corporation
> > > > */
> > > > @@ -22,6 +22,17 @@
> > > > #include "common.h"
> > > > +/* clang-format off */
> > > > +
> > > > +#define ACCESS_LAST LANDLOCK_ACCESS_NET_LISTEN_TCP
> > > > +
> > > > +#define ACCESS_ALL ( \
> > > > + LANDLOCK_ACCESS_NET_BIND_TCP | \
> > > > + LANDLOCK_ACCESS_NET_CONNECT_TCP | \
> > > > + LANDLOCK_ACCESS_NET_LISTEN_TCP)
> > > > +
> > > > +/* clang-format on */
> > > > +
> > > > const short sock_port_start = (1 << 10);
> > > > static const char loopback_ipv4[] = "127.0.0.1";
> > > > @@ -282,6 +293,16 @@ static int connect_variant(const int sock_fd,
> > > > return connect_variant_addrlen(sock_fd, srv, get_addrlen(srv, false));
> > > > }
> > > > +static int listen_variant(const int sock_fd, const int backlog)
> > >
> > > I believe socket_variant(), connect_variant() and bind_variant() were called
> > > like that because they got an instance of a service_fixture as an argument. The
> > > fixture instances are called variants. But we don't use these fixtures here.
Correct
> > >
> > > In fs_test.c, we also have some functions that behave much like system calls,
> > > but clean up after themselves and return errno, for easier use in assert. The
> > > naming scheme we have used there is "test_foo" (e.g. test_open()). I think this
> > > would be more appropriate here as a name?
> > >
> > > > +{
> > > > + int ret;
> > > > +
> > > > + ret = listen(sock_fd, backlog);
> > > > + if (ret < 0)
> > > > + return -errno;
> > > > + return ret;
> >
> > listen() can only return -1 or 0. It might be simpler to just return 0 here,
> > to make it more obvious that this returns an error code.
>
> Agreed, thanks. I'll do such refactoring for the connect_variant() as
> well.
>
> >
> > > > +}
> >
> > Another remark about listen_variant(): The helper functions in net_test.c return
> > negative error codes, whereas the ones in fs_test.c return positive error codes.
> > We should probably make that more consistent.
The test_*() helpers either return 0 on success or errno on error,
but not something else (e.g. not a file descriptor). Some test_*()
helper directly takes _metadata argument, so in this case they don't
need to return anything.
>
> socket_variant() returns positive descriptor in a case of success, so
> let's use negative ones.
The *_variant() helpers may indeed return a file descriptor so we should
return -errno if there is an error. Anyway, calling to the existing
helpers should always succeed and return a file descriptor (greater or
equal to 0).
However, all *_variant() helpers should take as argument a fixture
variant. If this is not needed, we either check the returned value of
the syscall and errno, or we can create a sys_<syscall>() helper that
return -errno on error.
>
> Should it be a separate patch btw?
Please just remove this listen_variant() helper and the related changes,
and use listen() + errno checks instead.
>
> >
> > > > +
> > > > FIXTURE(protocol)
> > > > {
> > > > struct service_fixture srv0, srv1, srv2, unspec_any0, unspec_srv0;
> > > > @@ -438,9 +459,11 @@ FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_unix_datagram) {
> > > > },
> > > > };
> > > > -static void test_bind_and_connect(struct __test_metadata *const _metadata,
> > > > - const struct service_fixture *const srv,
> > > > - const bool deny_bind, const bool deny_connect)
> > > > +static void test_restricted_net_fixture(struct __test_metadata *const _metadata,
> > > > + const struct service_fixture *const srv,
> > > > + const bool deny_bind,
> > > > + const bool deny_connect,
> > > > + const bool deny_listen)
> > > > {
> > > > char buf = '\0';
> > > > int inval_fd, bind_fd, client_fd, status, ret;
> > > > @@ -512,8 +535,14 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
> > > > EXPECT_EQ(0, ret);
> > > > /* Creates a listening socket. */
> > > > - if (srv->protocol.type == SOCK_STREAM)
> > > > - EXPECT_EQ(0, listen(bind_fd, backlog));
> > > > + if (srv->protocol.type == SOCK_STREAM) {
> > > > + ret = listen_variant(bind_fd, backlog);
> > > > + if (deny_listen) {
> > > > + EXPECT_EQ(-EACCES, ret);
> > > > + } else {
> > > > + EXPECT_EQ(0, ret);
> > > > + }
> > >
> > > Hmm, passing the expected error code instead of a boolean to this function was not possible?
> > > Then you could just write
> > >
> > > EXPECT_EQ(expected_listen_error, listen_variant(bind_fd, backlog));
> > >
> > > ? (Apologies if this was discussed already.)
> > >
> > > > + }
> > > > }
> > > > child = fork();
> > > > @@ -530,7 +559,7 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
> > > > ret = connect_variant(connect_fd, srv);
> > > > if (deny_connect) {
> > > > EXPECT_EQ(-EACCES, ret);
> > > > - } else if (deny_bind) {
> > > > + } else if (deny_bind || deny_listen) {
> > > > /* No listening server. */
> > > > EXPECT_EQ(-ECONNREFUSED, ret);
> > > > } else {
> > > > @@ -545,7 +574,7 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
> > > > /* Accepts connection from the child. */
> > > > client_fd = bind_fd;
> > > > - if (!deny_bind && !deny_connect) {
> > > > + if (!deny_bind && !deny_connect && !deny_listen) {
> > > > if (srv->protocol.type == SOCK_STREAM) {
> > > > client_fd = accept(bind_fd, NULL, 0);
> > > > ASSERT_LE(0, client_fd);
> > > > @@ -571,16 +600,15 @@ TEST_F(protocol, bind)
> > > > {
> > > > if (variant->sandbox == TCP_SANDBOX) {
> > > > const struct landlock_ruleset_attr ruleset_attr = {
> > > > - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
> > > > - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> > > > + .handled_access_net = ACCESS_ALL,
> > > > };
> > > > - const struct landlock_net_port_attr tcp_bind_connect_p0 = {
> > > > - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
> > > > - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> > > > + const struct landlock_net_port_attr tcp_not_restricted_p0 = {
> > > > + .allowed_access = ACCESS_ALL,
> > > > .port = self->srv0.port,
> > > > };
> > > > - const struct landlock_net_port_attr tcp_connect_p1 = {
> > > > - .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
> > > > + const struct landlock_net_port_attr tcp_denied_bind_p1 = {
> > > > + .allowed_access = ACCESS_ALL &
> > > > + ~LANDLOCK_ACCESS_NET_BIND_TCP,
> > > > .port = self->srv1.port,
> > > > };
> > > > int ruleset_fd;
> > > > @@ -589,48 +617,47 @@ TEST_F(protocol, bind)
> > > > sizeof(ruleset_attr), 0);
> > > > ASSERT_LE(0, ruleset_fd);
> > > > - /* Allows connect and bind for the first port. */
> > > > + /* Allows all actions for the first port. */
> > > > ASSERT_EQ(0,
> > > > landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> > > > - &tcp_bind_connect_p0, 0));
> > > > + &tcp_not_restricted_p0, 0));
> > > > - /* Allows connect and denies bind for the second port. */
> > > > + /* Allows all actions despite bind. */
> > > > ASSERT_EQ(0,
> > > > landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> > > > - &tcp_connect_p1, 0));
> > > > + &tcp_denied_bind_p1, 0));
> > > > enforce_ruleset(_metadata, ruleset_fd);
> > > > EXPECT_EQ(0, close(ruleset_fd));
> > > > }
> > > > + bool restricted = is_restricted(&variant->prot, variant->sandbox);
> > > > /* Binds a socket to the first port. */
> > > > - test_bind_and_connect(_metadata, &self->srv0, false, false);
> > > > + test_restricted_net_fixture(_metadata, &self->srv0, false, false,
> > > > + false);
> > > > /* Binds a socket to the second port. */
> > > > - test_bind_and_connect(_metadata, &self->srv1,
> > > > - is_restricted(&variant->prot, variant->sandbox),
> > > > - false);
> > > > + test_restricted_net_fixture(_metadata, &self->srv1, restricted, false,
> > > > + false);
> > > > /* Binds a socket to the third port. */
> > > > - test_bind_and_connect(_metadata, &self->srv2,
> > > > - is_restricted(&variant->prot, variant->sandbox),
> > > > - is_restricted(&variant->prot, variant->sandbox));
> > > > + test_restricted_net_fixture(_metadata, &self->srv2, restricted,
> > > > + restricted, restricted);
> > > > }
> > > > TEST_F(protocol, connect)
> > > > {
> > > > if (variant->sandbox == TCP_SANDBOX) {
> > > > const struct landlock_ruleset_attr ruleset_attr = {
> > > > - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
> > > > - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> > > > + .handled_access_net = ACCESS_ALL,
> > > > };
> > > > - const struct landlock_net_port_attr tcp_bind_connect_p0 = {
> > > > - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
> > > > - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> > > > + const struct landlock_net_port_attr tcp_not_restricted_p0 = {
> > > > + .allowed_access = ACCESS_ALL,
> > > > .port = self->srv0.port,
> > > > };
> > > > - const struct landlock_net_port_attr tcp_bind_p1 = {
> > > > - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
> > > > + const struct landlock_net_port_attr tcp_denied_connect_p1 = {
> > > > + .allowed_access = ACCESS_ALL &
> > > > + ~LANDLOCK_ACCESS_NET_CONNECT_TCP,
> > > > .port = self->srv1.port,
> > > > };
> > > > int ruleset_fd;
> > > > @@ -639,28 +666,27 @@ TEST_F(protocol, connect)
> > > > sizeof(ruleset_attr), 0);
> > > > ASSERT_LE(0, ruleset_fd);
> > > > - /* Allows connect and bind for the first port. */
> > > > + /* Allows all actions for the first port. */
> > > > ASSERT_EQ(0,
> > > > landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> > > > - &tcp_bind_connect_p0, 0));
> > > > + &tcp_not_restricted_p0, 0));
> > > > - /* Allows bind and denies connect for the second port. */
> > > > + /* Allows all actions despite connect. */
> > > > ASSERT_EQ(0,
> > > > landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> > > > - &tcp_bind_p1, 0));
> > > > + &tcp_denied_connect_p1, 0));
> > > > enforce_ruleset(_metadata, ruleset_fd);
> > > > EXPECT_EQ(0, close(ruleset_fd));
> > > > }
> > > > -
> > > > - test_bind_and_connect(_metadata, &self->srv0, false, false);
> > > > -
> > > > - test_bind_and_connect(_metadata, &self->srv1, false,
> > > > - is_restricted(&variant->prot, variant->sandbox));
> > > > -
> > > > - test_bind_and_connect(_metadata, &self->srv2,
> > > > - is_restricted(&variant->prot, variant->sandbox),
> > > > - is_restricted(&variant->prot, variant->sandbox));
> > > > + bool restricted = is_restricted(&variant->prot, variant->sandbox);
> > > > +
> > > > + test_restricted_net_fixture(_metadata, &self->srv0, false, false,
> > > > + false);
> > > > + test_restricted_net_fixture(_metadata, &self->srv1, false, restricted,
> > > > + false);
> > > > + test_restricted_net_fixture(_metadata, &self->srv2, restricted,
> > > > + restricted, restricted);
> > > > }
> > > > TEST_F(protocol, bind_unspec)
> > > > @@ -761,7 +787,7 @@ TEST_F(protocol, connect_unspec)
> > > > ASSERT_LE(0, bind_fd);
> > > > EXPECT_EQ(0, bind_variant(bind_fd, &self->srv0));
> > > > if (self->srv0.protocol.type == SOCK_STREAM)
> > > > - EXPECT_EQ(0, listen(bind_fd, backlog));
> > > > + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
> > > > child = fork();
> > > > ASSERT_LE(0, child);
> > > > @@ -1127,8 +1153,8 @@ TEST_F(tcp_layers, ruleset_overlap)
> > > > * Forbids to connect to the socket because only one ruleset layer
> > > > * allows connect.
> > > > */
> > > > - test_bind_and_connect(_metadata, &self->srv0, false,
> > > > - variant->num_layers >= 2);
> > > > + test_restricted_net_fixture(_metadata, &self->srv0, false,
> > > > + variant->num_layers >= 2, false);
> > > > }
> > > > TEST_F(tcp_layers, ruleset_expand)
> > > > @@ -1208,11 +1234,12 @@ TEST_F(tcp_layers, ruleset_expand)
> > > > EXPECT_EQ(0, close(ruleset_fd));
> > > > }
> > > > - test_bind_and_connect(_metadata, &self->srv0, false,
> > > > - variant->num_layers >= 3);
> > > > + test_restricted_net_fixture(_metadata, &self->srv0, false,
> > > > + variant->num_layers >= 3, false);
> > > > - test_bind_and_connect(_metadata, &self->srv1, variant->num_layers >= 1,
> > > > - variant->num_layers >= 2);
> > > > + test_restricted_net_fixture(_metadata, &self->srv1,
> > > > + variant->num_layers >= 1,
> > > > + variant->num_layers >= 2, false);
> > > > }
> > > > /* clang-format off */
> > > > @@ -1230,16 +1257,6 @@ FIXTURE_TEARDOWN(mini)
> > > > {
> > > > }
> > > > -/* clang-format off */
> > > > -
> > > > -#define ACCESS_LAST LANDLOCK_ACCESS_NET_CONNECT_TCP
> > > > -
> > > > -#define ACCESS_ALL ( \
> > > > - LANDLOCK_ACCESS_NET_BIND_TCP | \
> > > > - LANDLOCK_ACCESS_NET_CONNECT_TCP)
I'd like to avoid changes that impact existing tests. For now, tests
can only run against the kernel in the same tree, but I'd like to make
these tests able to run against previous kernel versions too. We're not
there yet, but that's one reason why we should not change such constants
but use some kind of argument instead.
> > > > -
> > > > -/* clang-format on */
> > > > -
> > > > TEST_F(mini, network_access_rights)
> > > > {
> > > > const struct landlock_ruleset_attr ruleset_attr = {
> > > > @@ -1454,8 +1471,9 @@ TEST_F(mini, tcp_port_overflow)
> > > > enforce_ruleset(_metadata, ruleset_fd);
> > > > - test_bind_and_connect(_metadata, &srv_denied, true, true);
> > > > - test_bind_and_connect(_metadata, &srv_max_allowed, false, false);
> > > > + test_restricted_net_fixture(_metadata, &srv_denied, true, true, false);
> > > > + test_restricted_net_fixture(_metadata, &srv_max_allowed, false, false,
> > > > + false);
> > > > }
> > > > FIXTURE(ipv4_tcp)
> > > > @@ -1485,22 +1503,21 @@ FIXTURE_TEARDOWN(ipv4_tcp)
> > > > TEST_F(ipv4_tcp, port_endianness)
> > > > {
> > > > const struct landlock_ruleset_attr ruleset_attr = {
> > > > - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
> > > > - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> > > > + .handled_access_net = ACCESS_ALL,
> > > > };
> > > > const struct landlock_net_port_attr bind_host_endian_p0 = {
> > > > .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
> > > > /* Host port format. */
> > > > .port = self->srv0.port,
> > > > };
> > > > - const struct landlock_net_port_attr connect_big_endian_p0 = {
> > > > - .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
> > > > + const struct landlock_net_port_attr connect_listen_big_endian_p0 = {
> > > > + .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP |
> > > > + LANDLOCK_ACCESS_NET_LISTEN_TCP,
> > > > /* Big endian port format. */
> > > > .port = htons(self->srv0.port),
> > > > };
> > > > - const struct landlock_net_port_attr bind_connect_host_endian_p1 = {
> > > > - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
> > > > - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> > > > + const struct landlock_net_port_attr not_restricted_host_endian_p1 = {
> > > > + .allowed_access = ACCESS_ALL,
> > > > /* Host port format. */
> > > > .port = self->srv1.port,
> > > > };
> > > > @@ -1514,16 +1531,18 @@ TEST_F(ipv4_tcp, port_endianness)
> > > > ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> > > > &bind_host_endian_p0, 0));
> > > > ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> > > > - &connect_big_endian_p0, 0));
> > > > + &connect_listen_big_endian_p0, 0));
> > > > ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> > > > - &bind_connect_host_endian_p1, 0));
> > > > + ¬_restricted_host_endian_p1, 0));
> > > > enforce_ruleset(_metadata, ruleset_fd);
> > > > /* No restriction for big endinan CPU. */
> > > > - test_bind_and_connect(_metadata, &self->srv0, false, little_endian);
> > > > + test_restricted_net_fixture(_metadata, &self->srv0, false,
> > > > + little_endian, little_endian);
> > > > /* No restriction for any CPU. */
> > > > - test_bind_and_connect(_metadata, &self->srv1, false, false);
> > > > + test_restricted_net_fixture(_metadata, &self->srv1, false, false,
> > > > + false);
> > > > }
> > > > TEST_F(ipv4_tcp, with_fs)
> > > > @@ -1691,7 +1710,7 @@ TEST_F(port_specific, bind_connect_zero)
> > > > ret = bind_variant(bind_fd, &self->srv0);
> > > > EXPECT_EQ(0, ret);
> > > > - EXPECT_EQ(0, listen(bind_fd, backlog));
> > > > + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
> > > > /* Connects on port 0. */
> > > > ret = connect_variant(connect_fd, &self->srv0);
> > > > @@ -1714,26 +1733,23 @@ TEST_F(port_specific, bind_connect_zero)
> > > > EXPECT_EQ(0, close(bind_fd));
> > > > }
> > > > -TEST_F(port_specific, bind_connect_1023)
> > > > +TEST_F(port_specific, port_1023)
> > > > {
> > > > int bind_fd, connect_fd, ret;
> > > > - /* Adds a rule layer with bind and connect actions. */
> > > > + /* Adds a rule layer with all actions. */
> > > > if (variant->sandbox == TCP_SANDBOX) {
> > > > const struct landlock_ruleset_attr ruleset_attr = {
> > > > - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
> > > > - LANDLOCK_ACCESS_NET_CONNECT_TCP
> > > > + .handled_access_net = ACCESS_ALL
> > > > };
> > > > /* A rule with port value less than 1024. */
> > > > - const struct landlock_net_port_attr tcp_bind_connect_low_range = {
> > > > - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
> > > > - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> > > > + const struct landlock_net_port_attr tcp_low_range_port = {
> > > > + .allowed_access = ACCESS_ALL,
> > > > .port = 1023,
> > > > };
> > > > /* A rule with 1024 port. */
> > > > - const struct landlock_net_port_attr tcp_bind_connect = {
> > > > - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
> > > > - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> > > > + const struct landlock_net_port_attr tcp_port_1024 = {
> > > > + .allowed_access = ACCESS_ALL,
> > > > .port = 1024,
> > > > };
> > > > int ruleset_fd;
> > > > @@ -1744,10 +1760,10 @@ TEST_F(port_specific, bind_connect_1023)
> > > > ASSERT_EQ(0,
> > > > landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> > > > - &tcp_bind_connect_low_range, 0));
> > > > + &tcp_low_range_port, 0));
> > > > ASSERT_EQ(0,
> > > > landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> > > > - &tcp_bind_connect, 0));
> > > > + &tcp_port_1024, 0));
> > > > enforce_ruleset(_metadata, ruleset_fd);
> > > > EXPECT_EQ(0, close(ruleset_fd));
> > > > @@ -1771,7 +1787,7 @@ TEST_F(port_specific, bind_connect_1023)
> > > > ret = bind_variant(bind_fd, &self->srv0);
> > > > clear_cap(_metadata, CAP_NET_BIND_SERVICE);
> > > > EXPECT_EQ(0, ret);
> > > > - EXPECT_EQ(0, listen(bind_fd, backlog));
> > > > + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
> > > > /* Connects on the binded port 1023. */
> > > > ret = connect_variant(connect_fd, &self->srv0);
> > > > @@ -1791,7 +1807,7 @@ TEST_F(port_specific, bind_connect_1023)
> > > > /* Binds on port 1024. */
> > > > ret = bind_variant(bind_fd, &self->srv0);
> > > > EXPECT_EQ(0, ret);
> > > > - EXPECT_EQ(0, listen(bind_fd, backlog));
> > > > + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
> > > > /* Connects on the binded port 1024. */
> > > > ret = connect_variant(connect_fd, &self->srv0);
> > > > --
> > > > 2.34.1
> > > >
> > >
> > > —Günther
>
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [RFC PATCH v2 3/9] selftests/landlock: Support LANDLOCK_ACCESS_NET_LISTEN_TCP
2024-09-25 18:31 ` Mickaël Salaün
@ 2024-09-26 11:59 ` Mikhail Ivanov
0 siblings, 0 replies; 40+ messages in thread
From: Mikhail Ivanov @ 2024-09-26 11:59 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Günther Noack, willemdebruijn.kernel, gnoack3000,
linux-security-module, netdev, netfilter-devel, yusongping,
artem.kuzin, konstantin.meskhidze, Matthieu Buffet
On 9/25/2024 9:31 PM, Mickaël Salaün wrote:
> On Tue, Aug 20, 2024 at 09:27:10PM +0300, Mikhail Ivanov wrote:
>> 8/20/2024 4:14 PM, Günther Noack wrote:
>>> On Mon, Aug 19, 2024 at 11:52:36PM +0200, Günther Noack wrote:
>>>> On Wed, Aug 14, 2024 at 11:01:45AM +0800, Mikhail Ivanov wrote:
>>>>> * Add listen_variant() to simplify listen(2) return code checking.
>>>>> * Rename test_bind_and_connect() to test_restricted_net_fixture().
>>>>> * Extend current net rules with LANDLOCK_ACCESS_NET_LISTEN_TCP access.
>>>>> * Rename test port_specific.bind_connect_1023 to
>>>>> port_specific.port_1023.
>>>>> * Check little endian port restriction for listen in
>>>>> ipv4_tcp.port_endianness.
>>>>> * Some local renames and comment changes.
>>>>>
>>>>> Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
>>>>> ---
>>>>> tools/testing/selftests/landlock/net_test.c | 198 +++++++++++---------
>>>>> 1 file changed, 107 insertions(+), 91 deletions(-)
>>>>>
>>>>> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
>>>>> index f21cfbbc3638..8126f5c0160f 100644
>>>>> --- a/tools/testing/selftests/landlock/net_test.c
>>>>> +++ b/tools/testing/selftests/landlock/net_test.c
>>>>> @@ -2,7 +2,7 @@
>>>>> /*
>>>>> * Landlock tests - Network
>>>>> *
>>>>> - * Copyright © 2022-2023 Huawei Tech. Co., Ltd.
>>>>> + * Copyright © 2022-2024 Huawei Tech. Co., Ltd.
>>>>> * Copyright © 2023 Microsoft Corporation
>>>>> */
>>>>> @@ -22,6 +22,17 @@
>>>>> #include "common.h"
>>>>> +/* clang-format off */
>>>>> +
>>>>> +#define ACCESS_LAST LANDLOCK_ACCESS_NET_LISTEN_TCP
>>>>> +
>>>>> +#define ACCESS_ALL ( \
>>>>> + LANDLOCK_ACCESS_NET_BIND_TCP | \
>>>>> + LANDLOCK_ACCESS_NET_CONNECT_TCP | \
>>>>> + LANDLOCK_ACCESS_NET_LISTEN_TCP)
>>>>> +
>>>>> +/* clang-format on */
>>>>> +
>>>>> const short sock_port_start = (1 << 10);
>>>>> static const char loopback_ipv4[] = "127.0.0.1";
>>>>> @@ -282,6 +293,16 @@ static int connect_variant(const int sock_fd,
>>>>> return connect_variant_addrlen(sock_fd, srv, get_addrlen(srv, false));
>>>>> }
>>>>> +static int listen_variant(const int sock_fd, const int backlog)
>>>>
>>>> I believe socket_variant(), connect_variant() and bind_variant() were called
>>>> like that because they got an instance of a service_fixture as an argument. The
>>>> fixture instances are called variants. But we don't use these fixtures here.
>
> Correct
>
>>>>
>>>> In fs_test.c, we also have some functions that behave much like system calls,
>>>> but clean up after themselves and return errno, for easier use in assert. The
>>>> naming scheme we have used there is "test_foo" (e.g. test_open()). I think this
>>>> would be more appropriate here as a name?
>>>>
>>>>> +{
>>>>> + int ret;
>>>>> +
>>>>> + ret = listen(sock_fd, backlog);
>>>>> + if (ret < 0)
>>>>> + return -errno;
>>>>> + return ret;
>>>
>>> listen() can only return -1 or 0. It might be simpler to just return 0 here,
>>> to make it more obvious that this returns an error code.
>>
>> Agreed, thanks. I'll do such refactoring for the connect_variant() as
>> well.
>>
>>>
>>>>> +}
>>>
>>> Another remark about listen_variant(): The helper functions in net_test.c return
>>> negative error codes, whereas the ones in fs_test.c return positive error codes.
>>> We should probably make that more consistent.
>
> The test_*() helpers either return 0 on success or errno on error,
> but not something else (e.g. not a file descriptor). Some test_*()
> helper directly takes _metadata argument, so in this case they don't
> need to return anything.
>
>>
>> socket_variant() returns positive descriptor in a case of success, so
>> let's use negative ones.
>
> The *_variant() helpers may indeed return a file descriptor so we should
> return -errno if there is an error. Anyway, calling to the existing
> helpers should always succeed and return a file descriptor (greater or
> equal to 0).
>
> However, all *_variant() helpers should take as argument a fixture
> variant. If this is not needed, we either check the returned value of
> the syscall and errno, or we can create a sys_<syscall>() helper that
> return -errno on error.
Agreed, I've suggested similar variant (do_listen()) here [1]. Can I do
sys_listen() to simplify errno checks?
[1]
https://lore.kernel.org/all/2f67fa30-d4e6-3a1b-7166-eee33c734899@huawei-partners.com/
>
>>
>> Should it be a separate patch btw?
>
> Please just remove this listen_variant() helper and the related changes,
> and use listen() + errno checks instead.
Separate patch suggestion is more about refactoring inconsistent
posivite/negative errno return values in existing *_variant() helpers.
I'll remove listen_variant() anyway.
>
>>
>>>
>>>>> +
>>>>> FIXTURE(protocol)
>>>>> {
>>>>> struct service_fixture srv0, srv1, srv2, unspec_any0, unspec_srv0;
>>>>> @@ -438,9 +459,11 @@ FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_unix_datagram) {
>>>>> },
>>>>> };
>>>>> -static void test_bind_and_connect(struct __test_metadata *const _metadata,
>>>>> - const struct service_fixture *const srv,
>>>>> - const bool deny_bind, const bool deny_connect)
>>>>> +static void test_restricted_net_fixture(struct __test_metadata *const _metadata,
>>>>> + const struct service_fixture *const srv,
>>>>> + const bool deny_bind,
>>>>> + const bool deny_connect,
>>>>> + const bool deny_listen)
>>>>> {
>>>>> char buf = '\0';
>>>>> int inval_fd, bind_fd, client_fd, status, ret;
>>>>> @@ -512,8 +535,14 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
>>>>> EXPECT_EQ(0, ret);
>>>>> /* Creates a listening socket. */
>>>>> - if (srv->protocol.type == SOCK_STREAM)
>>>>> - EXPECT_EQ(0, listen(bind_fd, backlog));
>>>>> + if (srv->protocol.type == SOCK_STREAM) {
>>>>> + ret = listen_variant(bind_fd, backlog);
>>>>> + if (deny_listen) {
>>>>> + EXPECT_EQ(-EACCES, ret);
>>>>> + } else {
>>>>> + EXPECT_EQ(0, ret);
>>>>> + }
>>>>
>>>> Hmm, passing the expected error code instead of a boolean to this function was not possible?
>>>> Then you could just write
>>>>
>>>> EXPECT_EQ(expected_listen_error, listen_variant(bind_fd, backlog));
>>>>
>>>> ? (Apologies if this was discussed already.)
>>>>
>>>>> + }
>>>>> }
>>>>> child = fork();
>>>>> @@ -530,7 +559,7 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
>>>>> ret = connect_variant(connect_fd, srv);
>>>>> if (deny_connect) {
>>>>> EXPECT_EQ(-EACCES, ret);
>>>>> - } else if (deny_bind) {
>>>>> + } else if (deny_bind || deny_listen) {
>>>>> /* No listening server. */
>>>>> EXPECT_EQ(-ECONNREFUSED, ret);
>>>>> } else {
>>>>> @@ -545,7 +574,7 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
>>>>> /* Accepts connection from the child. */
>>>>> client_fd = bind_fd;
>>>>> - if (!deny_bind && !deny_connect) {
>>>>> + if (!deny_bind && !deny_connect && !deny_listen) {
>>>>> if (srv->protocol.type == SOCK_STREAM) {
>>>>> client_fd = accept(bind_fd, NULL, 0);
>>>>> ASSERT_LE(0, client_fd);
>>>>> @@ -571,16 +600,15 @@ TEST_F(protocol, bind)
>>>>> {
>>>>> if (variant->sandbox == TCP_SANDBOX) {
>>>>> const struct landlock_ruleset_attr ruleset_attr = {
>>>>> - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
>>>>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>>>> + .handled_access_net = ACCESS_ALL,
>>>>> };
>>>>> - const struct landlock_net_port_attr tcp_bind_connect_p0 = {
>>>>> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
>>>>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>>>> + const struct landlock_net_port_attr tcp_not_restricted_p0 = {
>>>>> + .allowed_access = ACCESS_ALL,
>>>>> .port = self->srv0.port,
>>>>> };
>>>>> - const struct landlock_net_port_attr tcp_connect_p1 = {
>>>>> - .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>>>> + const struct landlock_net_port_attr tcp_denied_bind_p1 = {
>>>>> + .allowed_access = ACCESS_ALL &
>>>>> + ~LANDLOCK_ACCESS_NET_BIND_TCP,
>>>>> .port = self->srv1.port,
>>>>> };
>>>>> int ruleset_fd;
>>>>> @@ -589,48 +617,47 @@ TEST_F(protocol, bind)
>>>>> sizeof(ruleset_attr), 0);
>>>>> ASSERT_LE(0, ruleset_fd);
>>>>> - /* Allows connect and bind for the first port. */
>>>>> + /* Allows all actions for the first port. */
>>>>> ASSERT_EQ(0,
>>>>> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>>>>> - &tcp_bind_connect_p0, 0));
>>>>> + &tcp_not_restricted_p0, 0));
>>>>> - /* Allows connect and denies bind for the second port. */
>>>>> + /* Allows all actions despite bind. */
>>>>> ASSERT_EQ(0,
>>>>> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>>>>> - &tcp_connect_p1, 0));
>>>>> + &tcp_denied_bind_p1, 0));
>>>>> enforce_ruleset(_metadata, ruleset_fd);
>>>>> EXPECT_EQ(0, close(ruleset_fd));
>>>>> }
>>>>> + bool restricted = is_restricted(&variant->prot, variant->sandbox);
>>>>> /* Binds a socket to the first port. */
>>>>> - test_bind_and_connect(_metadata, &self->srv0, false, false);
>>>>> + test_restricted_net_fixture(_metadata, &self->srv0, false, false,
>>>>> + false);
>>>>> /* Binds a socket to the second port. */
>>>>> - test_bind_and_connect(_metadata, &self->srv1,
>>>>> - is_restricted(&variant->prot, variant->sandbox),
>>>>> - false);
>>>>> + test_restricted_net_fixture(_metadata, &self->srv1, restricted, false,
>>>>> + false);
>>>>> /* Binds a socket to the third port. */
>>>>> - test_bind_and_connect(_metadata, &self->srv2,
>>>>> - is_restricted(&variant->prot, variant->sandbox),
>>>>> - is_restricted(&variant->prot, variant->sandbox));
>>>>> + test_restricted_net_fixture(_metadata, &self->srv2, restricted,
>>>>> + restricted, restricted);
>>>>> }
>>>>> TEST_F(protocol, connect)
>>>>> {
>>>>> if (variant->sandbox == TCP_SANDBOX) {
>>>>> const struct landlock_ruleset_attr ruleset_attr = {
>>>>> - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
>>>>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>>>> + .handled_access_net = ACCESS_ALL,
>>>>> };
>>>>> - const struct landlock_net_port_attr tcp_bind_connect_p0 = {
>>>>> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
>>>>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>>>> + const struct landlock_net_port_attr tcp_not_restricted_p0 = {
>>>>> + .allowed_access = ACCESS_ALL,
>>>>> .port = self->srv0.port,
>>>>> };
>>>>> - const struct landlock_net_port_attr tcp_bind_p1 = {
>>>>> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
>>>>> + const struct landlock_net_port_attr tcp_denied_connect_p1 = {
>>>>> + .allowed_access = ACCESS_ALL &
>>>>> + ~LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>>>> .port = self->srv1.port,
>>>>> };
>>>>> int ruleset_fd;
>>>>> @@ -639,28 +666,27 @@ TEST_F(protocol, connect)
>>>>> sizeof(ruleset_attr), 0);
>>>>> ASSERT_LE(0, ruleset_fd);
>>>>> - /* Allows connect and bind for the first port. */
>>>>> + /* Allows all actions for the first port. */
>>>>> ASSERT_EQ(0,
>>>>> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>>>>> - &tcp_bind_connect_p0, 0));
>>>>> + &tcp_not_restricted_p0, 0));
>>>>> - /* Allows bind and denies connect for the second port. */
>>>>> + /* Allows all actions despite connect. */
>>>>> ASSERT_EQ(0,
>>>>> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>>>>> - &tcp_bind_p1, 0));
>>>>> + &tcp_denied_connect_p1, 0));
>>>>> enforce_ruleset(_metadata, ruleset_fd);
>>>>> EXPECT_EQ(0, close(ruleset_fd));
>>>>> }
>>>>> -
>>>>> - test_bind_and_connect(_metadata, &self->srv0, false, false);
>>>>> -
>>>>> - test_bind_and_connect(_metadata, &self->srv1, false,
>>>>> - is_restricted(&variant->prot, variant->sandbox));
>>>>> -
>>>>> - test_bind_and_connect(_metadata, &self->srv2,
>>>>> - is_restricted(&variant->prot, variant->sandbox),
>>>>> - is_restricted(&variant->prot, variant->sandbox));
>>>>> + bool restricted = is_restricted(&variant->prot, variant->sandbox);
>>>>> +
>>>>> + test_restricted_net_fixture(_metadata, &self->srv0, false, false,
>>>>> + false);
>>>>> + test_restricted_net_fixture(_metadata, &self->srv1, false, restricted,
>>>>> + false);
>>>>> + test_restricted_net_fixture(_metadata, &self->srv2, restricted,
>>>>> + restricted, restricted);
>>>>> }
>>>>> TEST_F(protocol, bind_unspec)
>>>>> @@ -761,7 +787,7 @@ TEST_F(protocol, connect_unspec)
>>>>> ASSERT_LE(0, bind_fd);
>>>>> EXPECT_EQ(0, bind_variant(bind_fd, &self->srv0));
>>>>> if (self->srv0.protocol.type == SOCK_STREAM)
>>>>> - EXPECT_EQ(0, listen(bind_fd, backlog));
>>>>> + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
>>>>> child = fork();
>>>>> ASSERT_LE(0, child);
>>>>> @@ -1127,8 +1153,8 @@ TEST_F(tcp_layers, ruleset_overlap)
>>>>> * Forbids to connect to the socket because only one ruleset layer
>>>>> * allows connect.
>>>>> */
>>>>> - test_bind_and_connect(_metadata, &self->srv0, false,
>>>>> - variant->num_layers >= 2);
>>>>> + test_restricted_net_fixture(_metadata, &self->srv0, false,
>>>>> + variant->num_layers >= 2, false);
>>>>> }
>>>>> TEST_F(tcp_layers, ruleset_expand)
>>>>> @@ -1208,11 +1234,12 @@ TEST_F(tcp_layers, ruleset_expand)
>>>>> EXPECT_EQ(0, close(ruleset_fd));
>>>>> }
>>>>> - test_bind_and_connect(_metadata, &self->srv0, false,
>>>>> - variant->num_layers >= 3);
>>>>> + test_restricted_net_fixture(_metadata, &self->srv0, false,
>>>>> + variant->num_layers >= 3, false);
>>>>> - test_bind_and_connect(_metadata, &self->srv1, variant->num_layers >= 1,
>>>>> - variant->num_layers >= 2);
>>>>> + test_restricted_net_fixture(_metadata, &self->srv1,
>>>>> + variant->num_layers >= 1,
>>>>> + variant->num_layers >= 2, false);
>>>>> }
>>>>> /* clang-format off */
>>>>> @@ -1230,16 +1257,6 @@ FIXTURE_TEARDOWN(mini)
>>>>> {
>>>>> }
>>>>> -/* clang-format off */
>>>>> -
>>>>> -#define ACCESS_LAST LANDLOCK_ACCESS_NET_CONNECT_TCP
>>>>> -
>>>>> -#define ACCESS_ALL ( \
>>>>> - LANDLOCK_ACCESS_NET_BIND_TCP | \
>>>>> - LANDLOCK_ACCESS_NET_CONNECT_TCP)
>
> I'd like to avoid changes that impact existing tests. For now, tests
> can only run against the kernel in the same tree, but I'd like to make
> these tests able to run against previous kernel versions too. We're not
> there yet, but that's one reason why we should not change such constants
> but use some kind of argument instead.
Ok, I'll fix this.
>
>>>>> -
>>>>> -/* clang-format on */
>>>>> -
>>>>> TEST_F(mini, network_access_rights)
>>>>> {
>>>>> const struct landlock_ruleset_attr ruleset_attr = {
>>>>> @@ -1454,8 +1471,9 @@ TEST_F(mini, tcp_port_overflow)
>>>>> enforce_ruleset(_metadata, ruleset_fd);
>>>>> - test_bind_and_connect(_metadata, &srv_denied, true, true);
>>>>> - test_bind_and_connect(_metadata, &srv_max_allowed, false, false);
>>>>> + test_restricted_net_fixture(_metadata, &srv_denied, true, true, false);
>>>>> + test_restricted_net_fixture(_metadata, &srv_max_allowed, false, false,
>>>>> + false);
>>>>> }
>>>>> FIXTURE(ipv4_tcp)
>>>>> @@ -1485,22 +1503,21 @@ FIXTURE_TEARDOWN(ipv4_tcp)
>>>>> TEST_F(ipv4_tcp, port_endianness)
>>>>> {
>>>>> const struct landlock_ruleset_attr ruleset_attr = {
>>>>> - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
>>>>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>>>> + .handled_access_net = ACCESS_ALL,
>>>>> };
>>>>> const struct landlock_net_port_attr bind_host_endian_p0 = {
>>>>> .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
>>>>> /* Host port format. */
>>>>> .port = self->srv0.port,
>>>>> };
>>>>> - const struct landlock_net_port_attr connect_big_endian_p0 = {
>>>>> - .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>>>> + const struct landlock_net_port_attr connect_listen_big_endian_p0 = {
>>>>> + .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP |
>>>>> + LANDLOCK_ACCESS_NET_LISTEN_TCP,
>>>>> /* Big endian port format. */
>>>>> .port = htons(self->srv0.port),
>>>>> };
>>>>> - const struct landlock_net_port_attr bind_connect_host_endian_p1 = {
>>>>> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
>>>>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>>>> + const struct landlock_net_port_attr not_restricted_host_endian_p1 = {
>>>>> + .allowed_access = ACCESS_ALL,
>>>>> /* Host port format. */
>>>>> .port = self->srv1.port,
>>>>> };
>>>>> @@ -1514,16 +1531,18 @@ TEST_F(ipv4_tcp, port_endianness)
>>>>> ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>>>>> &bind_host_endian_p0, 0));
>>>>> ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>>>>> - &connect_big_endian_p0, 0));
>>>>> + &connect_listen_big_endian_p0, 0));
>>>>> ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>>>>> - &bind_connect_host_endian_p1, 0));
>>>>> + ¬_restricted_host_endian_p1, 0));
>>>>> enforce_ruleset(_metadata, ruleset_fd);
>>>>> /* No restriction for big endinan CPU. */
>>>>> - test_bind_and_connect(_metadata, &self->srv0, false, little_endian);
>>>>> + test_restricted_net_fixture(_metadata, &self->srv0, false,
>>>>> + little_endian, little_endian);
>>>>> /* No restriction for any CPU. */
>>>>> - test_bind_and_connect(_metadata, &self->srv1, false, false);
>>>>> + test_restricted_net_fixture(_metadata, &self->srv1, false, false,
>>>>> + false);
>>>>> }
>>>>> TEST_F(ipv4_tcp, with_fs)
>>>>> @@ -1691,7 +1710,7 @@ TEST_F(port_specific, bind_connect_zero)
>>>>> ret = bind_variant(bind_fd, &self->srv0);
>>>>> EXPECT_EQ(0, ret);
>>>>> - EXPECT_EQ(0, listen(bind_fd, backlog));
>>>>> + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
>>>>> /* Connects on port 0. */
>>>>> ret = connect_variant(connect_fd, &self->srv0);
>>>>> @@ -1714,26 +1733,23 @@ TEST_F(port_specific, bind_connect_zero)
>>>>> EXPECT_EQ(0, close(bind_fd));
>>>>> }
>>>>> -TEST_F(port_specific, bind_connect_1023)
>>>>> +TEST_F(port_specific, port_1023)
>>>>> {
>>>>> int bind_fd, connect_fd, ret;
>>>>> - /* Adds a rule layer with bind and connect actions. */
>>>>> + /* Adds a rule layer with all actions. */
>>>>> if (variant->sandbox == TCP_SANDBOX) {
>>>>> const struct landlock_ruleset_attr ruleset_attr = {
>>>>> - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
>>>>> - LANDLOCK_ACCESS_NET_CONNECT_TCP
>>>>> + .handled_access_net = ACCESS_ALL
>>>>> };
>>>>> /* A rule with port value less than 1024. */
>>>>> - const struct landlock_net_port_attr tcp_bind_connect_low_range = {
>>>>> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
>>>>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>>>> + const struct landlock_net_port_attr tcp_low_range_port = {
>>>>> + .allowed_access = ACCESS_ALL,
>>>>> .port = 1023,
>>>>> };
>>>>> /* A rule with 1024 port. */
>>>>> - const struct landlock_net_port_attr tcp_bind_connect = {
>>>>> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
>>>>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>>>>> + const struct landlock_net_port_attr tcp_port_1024 = {
>>>>> + .allowed_access = ACCESS_ALL,
>>>>> .port = 1024,
>>>>> };
>>>>> int ruleset_fd;
>>>>> @@ -1744,10 +1760,10 @@ TEST_F(port_specific, bind_connect_1023)
>>>>> ASSERT_EQ(0,
>>>>> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>>>>> - &tcp_bind_connect_low_range, 0));
>>>>> + &tcp_low_range_port, 0));
>>>>> ASSERT_EQ(0,
>>>>> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>>>>> - &tcp_bind_connect, 0));
>>>>> + &tcp_port_1024, 0));
>>>>> enforce_ruleset(_metadata, ruleset_fd);
>>>>> EXPECT_EQ(0, close(ruleset_fd));
>>>>> @@ -1771,7 +1787,7 @@ TEST_F(port_specific, bind_connect_1023)
>>>>> ret = bind_variant(bind_fd, &self->srv0);
>>>>> clear_cap(_metadata, CAP_NET_BIND_SERVICE);
>>>>> EXPECT_EQ(0, ret);
>>>>> - EXPECT_EQ(0, listen(bind_fd, backlog));
>>>>> + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
>>>>> /* Connects on the binded port 1023. */
>>>>> ret = connect_variant(connect_fd, &self->srv0);
>>>>> @@ -1791,7 +1807,7 @@ TEST_F(port_specific, bind_connect_1023)
>>>>> /* Binds on port 1024. */
>>>>> ret = bind_variant(bind_fd, &self->srv0);
>>>>> EXPECT_EQ(0, ret);
>>>>> - EXPECT_EQ(0, listen(bind_fd, backlog));
>>>>> + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
>>>>> /* Connects on the binded port 1024. */
>>>>> ret = connect_variant(connect_fd, &self->srv0);
>>>>> --
>>>>> 2.34.1
>>>>>
>>>>
>>>> —Günther
>>
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [RFC PATCH v2 3/9] selftests/landlock: Support LANDLOCK_ACCESS_NET_LISTEN_TCP
2024-08-14 3:01 ` [RFC PATCH v2 3/9] selftests/landlock: Support LANDLOCK_ACCESS_NET_LISTEN_TCP Mikhail Ivanov
2024-08-19 21:52 ` Günther Noack
@ 2024-08-19 21:53 ` Günther Noack
2024-08-20 12:35 ` Mikhail Ivanov
1 sibling, 1 reply; 40+ messages in thread
From: Günther Noack @ 2024-08-19 21:53 UTC (permalink / raw)
To: Mikhail Ivanov
Cc: mic, willemdebruijn.kernel, gnoack3000, linux-security-module,
netdev, netfilter-devel, yusongping, artem.kuzin,
konstantin.meskhidze
Some comment nits I forgot, see below.
On Wed, Aug 14, 2024 at 11:01:45AM +0800, Mikhail Ivanov wrote:
> * Add listen_variant() to simplify listen(2) return code checking.
> * Rename test_bind_and_connect() to test_restricted_net_fixture().
> * Extend current net rules with LANDLOCK_ACCESS_NET_LISTEN_TCP access.
> * Rename test port_specific.bind_connect_1023 to
> port_specific.port_1023.
> * Check little endian port restriction for listen in
> ipv4_tcp.port_endianness.
> * Some local renames and comment changes.
>
> Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
> ---
> tools/testing/selftests/landlock/net_test.c | 198 +++++++++++---------
> 1 file changed, 107 insertions(+), 91 deletions(-)
>
> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
> index f21cfbbc3638..8126f5c0160f 100644
> --- a/tools/testing/selftests/landlock/net_test.c
> +++ b/tools/testing/selftests/landlock/net_test.c
> @@ -2,7 +2,7 @@
> /*
> * Landlock tests - Network
> *
> - * Copyright © 2022-2023 Huawei Tech. Co., Ltd.
> + * Copyright © 2022-2024 Huawei Tech. Co., Ltd.
> * Copyright © 2023 Microsoft Corporation
> */
>
> @@ -22,6 +22,17 @@
>
> #include "common.h"
>
> +/* clang-format off */
> +
> +#define ACCESS_LAST LANDLOCK_ACCESS_NET_LISTEN_TCP
> +
> +#define ACCESS_ALL ( \
> + LANDLOCK_ACCESS_NET_BIND_TCP | \
> + LANDLOCK_ACCESS_NET_CONNECT_TCP | \
> + LANDLOCK_ACCESS_NET_LISTEN_TCP)
> +
> +/* clang-format on */
> +
> const short sock_port_start = (1 << 10);
>
> static const char loopback_ipv4[] = "127.0.0.1";
> @@ -282,6 +293,16 @@ static int connect_variant(const int sock_fd,
> return connect_variant_addrlen(sock_fd, srv, get_addrlen(srv, false));
> }
>
> +static int listen_variant(const int sock_fd, const int backlog)
> +{
> + int ret;
> +
> + ret = listen(sock_fd, backlog);
> + if (ret < 0)
> + return -errno;
> + return ret;
> +}
> +
> FIXTURE(protocol)
> {
> struct service_fixture srv0, srv1, srv2, unspec_any0, unspec_srv0;
> @@ -438,9 +459,11 @@ FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_unix_datagram) {
> },
> };
>
> -static void test_bind_and_connect(struct __test_metadata *const _metadata,
> - const struct service_fixture *const srv,
> - const bool deny_bind, const bool deny_connect)
> +static void test_restricted_net_fixture(struct __test_metadata *const _metadata,
> + const struct service_fixture *const srv,
> + const bool deny_bind,
> + const bool deny_connect,
> + const bool deny_listen)
> {
> char buf = '\0';
> int inval_fd, bind_fd, client_fd, status, ret;
> @@ -512,8 +535,14 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
> EXPECT_EQ(0, ret);
>
> /* Creates a listening socket. */
> - if (srv->protocol.type == SOCK_STREAM)
> - EXPECT_EQ(0, listen(bind_fd, backlog));
> + if (srv->protocol.type == SOCK_STREAM) {
> + ret = listen_variant(bind_fd, backlog);
> + if (deny_listen) {
> + EXPECT_EQ(-EACCES, ret);
> + } else {
> + EXPECT_EQ(0, ret);
> + }
> + }
> }
>
> child = fork();
> @@ -530,7 +559,7 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
> ret = connect_variant(connect_fd, srv);
> if (deny_connect) {
> EXPECT_EQ(-EACCES, ret);
> - } else if (deny_bind) {
> + } else if (deny_bind || deny_listen) {
> /* No listening server. */
> EXPECT_EQ(-ECONNREFUSED, ret);
> } else {
> @@ -545,7 +574,7 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
>
> /* Accepts connection from the child. */
> client_fd = bind_fd;
> - if (!deny_bind && !deny_connect) {
> + if (!deny_bind && !deny_connect && !deny_listen) {
> if (srv->protocol.type == SOCK_STREAM) {
> client_fd = accept(bind_fd, NULL, 0);
> ASSERT_LE(0, client_fd);
> @@ -571,16 +600,15 @@ TEST_F(protocol, bind)
> {
> if (variant->sandbox == TCP_SANDBOX) {
> const struct landlock_ruleset_attr ruleset_attr = {
> - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> + .handled_access_net = ACCESS_ALL,
> };
> - const struct landlock_net_port_attr tcp_bind_connect_p0 = {
> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> + const struct landlock_net_port_attr tcp_not_restricted_p0 = {
> + .allowed_access = ACCESS_ALL,
> .port = self->srv0.port,
> };
> - const struct landlock_net_port_attr tcp_connect_p1 = {
> - .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
> + const struct landlock_net_port_attr tcp_denied_bind_p1 = {
> + .allowed_access = ACCESS_ALL &
> + ~LANDLOCK_ACCESS_NET_BIND_TCP,
> .port = self->srv1.port,
> };
> int ruleset_fd;
> @@ -589,48 +617,47 @@ TEST_F(protocol, bind)
> sizeof(ruleset_attr), 0);
> ASSERT_LE(0, ruleset_fd);
>
> - /* Allows connect and bind for the first port. */
> + /* Allows all actions for the first port. */
> ASSERT_EQ(0,
> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> - &tcp_bind_connect_p0, 0));
> + &tcp_not_restricted_p0, 0));
>
> - /* Allows connect and denies bind for the second port. */
> + /* Allows all actions despite bind. */
s/despite/except/ would be more conventional English, I believe.
> ASSERT_EQ(0,
> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> - &tcp_connect_p1, 0));
> + &tcp_denied_bind_p1, 0));
>
> enforce_ruleset(_metadata, ruleset_fd);
> EXPECT_EQ(0, close(ruleset_fd));
> }
> + bool restricted = is_restricted(&variant->prot, variant->sandbox);
>
> /* Binds a socket to the first port. */
> - test_bind_and_connect(_metadata, &self->srv0, false, false);
> + test_restricted_net_fixture(_metadata, &self->srv0, false, false,
> + false);
>
> /* Binds a socket to the second port. */
> - test_bind_and_connect(_metadata, &self->srv1,
> - is_restricted(&variant->prot, variant->sandbox),
> - false);
> + test_restricted_net_fixture(_metadata, &self->srv1, restricted, false,
> + false);
>
> /* Binds a socket to the third port. */
> - test_bind_and_connect(_metadata, &self->srv2,
> - is_restricted(&variant->prot, variant->sandbox),
> - is_restricted(&variant->prot, variant->sandbox));
> + test_restricted_net_fixture(_metadata, &self->srv2, restricted,
> + restricted, restricted);
> }
>
> TEST_F(protocol, connect)
> {
> if (variant->sandbox == TCP_SANDBOX) {
> const struct landlock_ruleset_attr ruleset_attr = {
> - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> + .handled_access_net = ACCESS_ALL,
> };
> - const struct landlock_net_port_attr tcp_bind_connect_p0 = {
> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> + const struct landlock_net_port_attr tcp_not_restricted_p0 = {
> + .allowed_access = ACCESS_ALL,
> .port = self->srv0.port,
> };
> - const struct landlock_net_port_attr tcp_bind_p1 = {
> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
> + const struct landlock_net_port_attr tcp_denied_connect_p1 = {
> + .allowed_access = ACCESS_ALL &
> + ~LANDLOCK_ACCESS_NET_CONNECT_TCP,
> .port = self->srv1.port,
> };
> int ruleset_fd;
> @@ -639,28 +666,27 @@ TEST_F(protocol, connect)
> sizeof(ruleset_attr), 0);
> ASSERT_LE(0, ruleset_fd);
>
> - /* Allows connect and bind for the first port. */
> + /* Allows all actions for the first port. */
> ASSERT_EQ(0,
> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> - &tcp_bind_connect_p0, 0));
> + &tcp_not_restricted_p0, 0));
>
> - /* Allows bind and denies connect for the second port. */
> + /* Allows all actions despite connect. */
Same here.
> ASSERT_EQ(0,
> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> - &tcp_bind_p1, 0));
> + &tcp_denied_connect_p1, 0));
>
> enforce_ruleset(_metadata, ruleset_fd);
> EXPECT_EQ(0, close(ruleset_fd));
> }
> -
> - test_bind_and_connect(_metadata, &self->srv0, false, false);
> -
> - test_bind_and_connect(_metadata, &self->srv1, false,
> - is_restricted(&variant->prot, variant->sandbox));
> -
> - test_bind_and_connect(_metadata, &self->srv2,
> - is_restricted(&variant->prot, variant->sandbox),
> - is_restricted(&variant->prot, variant->sandbox));
> + bool restricted = is_restricted(&variant->prot, variant->sandbox);
> +
> + test_restricted_net_fixture(_metadata, &self->srv0, false, false,
> + false);
> + test_restricted_net_fixture(_metadata, &self->srv1, false, restricted,
> + false);
> + test_restricted_net_fixture(_metadata, &self->srv2, restricted,
> + restricted, restricted);
> }
>
> TEST_F(protocol, bind_unspec)
> @@ -761,7 +787,7 @@ TEST_F(protocol, connect_unspec)
> ASSERT_LE(0, bind_fd);
> EXPECT_EQ(0, bind_variant(bind_fd, &self->srv0));
> if (self->srv0.protocol.type == SOCK_STREAM)
> - EXPECT_EQ(0, listen(bind_fd, backlog));
> + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
>
> child = fork();
> ASSERT_LE(0, child);
> @@ -1127,8 +1153,8 @@ TEST_F(tcp_layers, ruleset_overlap)
> * Forbids to connect to the socket because only one ruleset layer
> * allows connect.
> */
> - test_bind_and_connect(_metadata, &self->srv0, false,
> - variant->num_layers >= 2);
> + test_restricted_net_fixture(_metadata, &self->srv0, false,
> + variant->num_layers >= 2, false);
> }
>
> TEST_F(tcp_layers, ruleset_expand)
> @@ -1208,11 +1234,12 @@ TEST_F(tcp_layers, ruleset_expand)
> EXPECT_EQ(0, close(ruleset_fd));
> }
>
> - test_bind_and_connect(_metadata, &self->srv0, false,
> - variant->num_layers >= 3);
> + test_restricted_net_fixture(_metadata, &self->srv0, false,
> + variant->num_layers >= 3, false);
>
> - test_bind_and_connect(_metadata, &self->srv1, variant->num_layers >= 1,
> - variant->num_layers >= 2);
> + test_restricted_net_fixture(_metadata, &self->srv1,
> + variant->num_layers >= 1,
> + variant->num_layers >= 2, false);
> }
>
> /* clang-format off */
> @@ -1230,16 +1257,6 @@ FIXTURE_TEARDOWN(mini)
> {
> }
>
> -/* clang-format off */
> -
> -#define ACCESS_LAST LANDLOCK_ACCESS_NET_CONNECT_TCP
> -
> -#define ACCESS_ALL ( \
> - LANDLOCK_ACCESS_NET_BIND_TCP | \
> - LANDLOCK_ACCESS_NET_CONNECT_TCP)
> -
> -/* clang-format on */
> -
> TEST_F(mini, network_access_rights)
> {
> const struct landlock_ruleset_attr ruleset_attr = {
> @@ -1454,8 +1471,9 @@ TEST_F(mini, tcp_port_overflow)
>
> enforce_ruleset(_metadata, ruleset_fd);
>
> - test_bind_and_connect(_metadata, &srv_denied, true, true);
> - test_bind_and_connect(_metadata, &srv_max_allowed, false, false);
> + test_restricted_net_fixture(_metadata, &srv_denied, true, true, false);
> + test_restricted_net_fixture(_metadata, &srv_max_allowed, false, false,
> + false);
> }
>
> FIXTURE(ipv4_tcp)
> @@ -1485,22 +1503,21 @@ FIXTURE_TEARDOWN(ipv4_tcp)
> TEST_F(ipv4_tcp, port_endianness)
> {
> const struct landlock_ruleset_attr ruleset_attr = {
> - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> + .handled_access_net = ACCESS_ALL,
> };
> const struct landlock_net_port_attr bind_host_endian_p0 = {
> .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
> /* Host port format. */
> .port = self->srv0.port,
> };
> - const struct landlock_net_port_attr connect_big_endian_p0 = {
> - .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
> + const struct landlock_net_port_attr connect_listen_big_endian_p0 = {
> + .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP |
> + LANDLOCK_ACCESS_NET_LISTEN_TCP,
> /* Big endian port format. */
> .port = htons(self->srv0.port),
> };
> - const struct landlock_net_port_attr bind_connect_host_endian_p1 = {
> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> + const struct landlock_net_port_attr not_restricted_host_endian_p1 = {
> + .allowed_access = ACCESS_ALL,
> /* Host port format. */
> .port = self->srv1.port,
> };
> @@ -1514,16 +1531,18 @@ TEST_F(ipv4_tcp, port_endianness)
> ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> &bind_host_endian_p0, 0));
> ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> - &connect_big_endian_p0, 0));
> + &connect_listen_big_endian_p0, 0));
> ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> - &bind_connect_host_endian_p1, 0));
> + ¬_restricted_host_endian_p1, 0));
> enforce_ruleset(_metadata, ruleset_fd);
>
> /* No restriction for big endinan CPU. */
> - test_bind_and_connect(_metadata, &self->srv0, false, little_endian);
> + test_restricted_net_fixture(_metadata, &self->srv0, false,
> + little_endian, little_endian);
>
> /* No restriction for any CPU. */
> - test_bind_and_connect(_metadata, &self->srv1, false, false);
> + test_restricted_net_fixture(_metadata, &self->srv1, false, false,
> + false);
> }
>
> TEST_F(ipv4_tcp, with_fs)
> @@ -1691,7 +1710,7 @@ TEST_F(port_specific, bind_connect_zero)
> ret = bind_variant(bind_fd, &self->srv0);
> EXPECT_EQ(0, ret);
>
> - EXPECT_EQ(0, listen(bind_fd, backlog));
> + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
>
> /* Connects on port 0. */
> ret = connect_variant(connect_fd, &self->srv0);
> @@ -1714,26 +1733,23 @@ TEST_F(port_specific, bind_connect_zero)
> EXPECT_EQ(0, close(bind_fd));
> }
>
> -TEST_F(port_specific, bind_connect_1023)
> +TEST_F(port_specific, port_1023)
> {
> int bind_fd, connect_fd, ret;
>
> - /* Adds a rule layer with bind and connect actions. */
> + /* Adds a rule layer with all actions. */
> if (variant->sandbox == TCP_SANDBOX) {
> const struct landlock_ruleset_attr ruleset_attr = {
> - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
> - LANDLOCK_ACCESS_NET_CONNECT_TCP
> + .handled_access_net = ACCESS_ALL
> };
> /* A rule with port value less than 1024. */
> - const struct landlock_net_port_attr tcp_bind_connect_low_range = {
> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> + const struct landlock_net_port_attr tcp_low_range_port = {
> + .allowed_access = ACCESS_ALL,
> .port = 1023,
> };
> /* A rule with 1024 port. */
> - const struct landlock_net_port_attr tcp_bind_connect = {
> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> + const struct landlock_net_port_attr tcp_port_1024 = {
> + .allowed_access = ACCESS_ALL,
> .port = 1024,
> };
> int ruleset_fd;
> @@ -1744,10 +1760,10 @@ TEST_F(port_specific, bind_connect_1023)
>
> ASSERT_EQ(0,
> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> - &tcp_bind_connect_low_range, 0));
> + &tcp_low_range_port, 0));
> ASSERT_EQ(0,
> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> - &tcp_bind_connect, 0));
> + &tcp_port_1024, 0));
>
> enforce_ruleset(_metadata, ruleset_fd);
> EXPECT_EQ(0, close(ruleset_fd));
> @@ -1771,7 +1787,7 @@ TEST_F(port_specific, bind_connect_1023)
> ret = bind_variant(bind_fd, &self->srv0);
> clear_cap(_metadata, CAP_NET_BIND_SERVICE);
> EXPECT_EQ(0, ret);
> - EXPECT_EQ(0, listen(bind_fd, backlog));
> + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
>
> /* Connects on the binded port 1023. */
> ret = connect_variant(connect_fd, &self->srv0);
> @@ -1791,7 +1807,7 @@ TEST_F(port_specific, bind_connect_1023)
> /* Binds on port 1024. */
> ret = bind_variant(bind_fd, &self->srv0);
> EXPECT_EQ(0, ret);
> - EXPECT_EQ(0, listen(bind_fd, backlog));
> + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
>
> /* Connects on the binded port 1024. */
> ret = connect_variant(connect_fd, &self->srv0);
> --
> 2.34.1
>
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [RFC PATCH v2 3/9] selftests/landlock: Support LANDLOCK_ACCESS_NET_LISTEN_TCP
2024-08-19 21:53 ` Günther Noack
@ 2024-08-20 12:35 ` Mikhail Ivanov
0 siblings, 0 replies; 40+ messages in thread
From: Mikhail Ivanov @ 2024-08-20 12:35 UTC (permalink / raw)
To: Günther Noack
Cc: mic, willemdebruijn.kernel, gnoack3000, linux-security-module,
netdev, netfilter-devel, yusongping, artem.kuzin,
konstantin.meskhidze
8/20/2024 12:53 AM, Günther Noack wrote:
> Some comment nits I forgot, see below.
>
> On Wed, Aug 14, 2024 at 11:01:45AM +0800, Mikhail Ivanov wrote:
>> * Add listen_variant() to simplify listen(2) return code checking.
>> * Rename test_bind_and_connect() to test_restricted_net_fixture().
>> * Extend current net rules with LANDLOCK_ACCESS_NET_LISTEN_TCP access.
>> * Rename test port_specific.bind_connect_1023 to
>> port_specific.port_1023.
>> * Check little endian port restriction for listen in
>> ipv4_tcp.port_endianness.
>> * Some local renames and comment changes.
>>
>> Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
>> ---
>> tools/testing/selftests/landlock/net_test.c | 198 +++++++++++---------
>> 1 file changed, 107 insertions(+), 91 deletions(-)
>>
>> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
>> index f21cfbbc3638..8126f5c0160f 100644
>> --- a/tools/testing/selftests/landlock/net_test.c
>> +++ b/tools/testing/selftests/landlock/net_test.c
>> @@ -2,7 +2,7 @@
>> /*
>> * Landlock tests - Network
>> *
>> - * Copyright © 2022-2023 Huawei Tech. Co., Ltd.
>> + * Copyright © 2022-2024 Huawei Tech. Co., Ltd.
>> * Copyright © 2023 Microsoft Corporation
>> */
>>
>> @@ -22,6 +22,17 @@
>>
>> #include "common.h"
>>
>> +/* clang-format off */
>> +
>> +#define ACCESS_LAST LANDLOCK_ACCESS_NET_LISTEN_TCP
>> +
>> +#define ACCESS_ALL ( \
>> + LANDLOCK_ACCESS_NET_BIND_TCP | \
>> + LANDLOCK_ACCESS_NET_CONNECT_TCP | \
>> + LANDLOCK_ACCESS_NET_LISTEN_TCP)
>> +
>> +/* clang-format on */
>> +
>> const short sock_port_start = (1 << 10);
>>
>> static const char loopback_ipv4[] = "127.0.0.1";
>> @@ -282,6 +293,16 @@ static int connect_variant(const int sock_fd,
>> return connect_variant_addrlen(sock_fd, srv, get_addrlen(srv, false));
>> }
>>
>> +static int listen_variant(const int sock_fd, const int backlog)
>> +{
>> + int ret;
>> +
>> + ret = listen(sock_fd, backlog);
>> + if (ret < 0)
>> + return -errno;
>> + return ret;
>> +}
>> +
>> FIXTURE(protocol)
>> {
>> struct service_fixture srv0, srv1, srv2, unspec_any0, unspec_srv0;
>> @@ -438,9 +459,11 @@ FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_unix_datagram) {
>> },
>> };
>>
>> -static void test_bind_and_connect(struct __test_metadata *const _metadata,
>> - const struct service_fixture *const srv,
>> - const bool deny_bind, const bool deny_connect)
>> +static void test_restricted_net_fixture(struct __test_metadata *const _metadata,
>> + const struct service_fixture *const srv,
>> + const bool deny_bind,
>> + const bool deny_connect,
>> + const bool deny_listen)
>> {
>> char buf = '\0';
>> int inval_fd, bind_fd, client_fd, status, ret;
>> @@ -512,8 +535,14 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
>> EXPECT_EQ(0, ret);
>>
>> /* Creates a listening socket. */
>> - if (srv->protocol.type == SOCK_STREAM)
>> - EXPECT_EQ(0, listen(bind_fd, backlog));
>> + if (srv->protocol.type == SOCK_STREAM) {
>> + ret = listen_variant(bind_fd, backlog);
>> + if (deny_listen) {
>> + EXPECT_EQ(-EACCES, ret);
>> + } else {
>> + EXPECT_EQ(0, ret);
>> + }
>> + }
>> }
>>
>> child = fork();
>> @@ -530,7 +559,7 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
>> ret = connect_variant(connect_fd, srv);
>> if (deny_connect) {
>> EXPECT_EQ(-EACCES, ret);
>> - } else if (deny_bind) {
>> + } else if (deny_bind || deny_listen) {
>> /* No listening server. */
>> EXPECT_EQ(-ECONNREFUSED, ret);
>> } else {
>> @@ -545,7 +574,7 @@ static void test_bind_and_connect(struct __test_metadata *const _metadata,
>>
>> /* Accepts connection from the child. */
>> client_fd = bind_fd;
>> - if (!deny_bind && !deny_connect) {
>> + if (!deny_bind && !deny_connect && !deny_listen) {
>> if (srv->protocol.type == SOCK_STREAM) {
>> client_fd = accept(bind_fd, NULL, 0);
>> ASSERT_LE(0, client_fd);
>> @@ -571,16 +600,15 @@ TEST_F(protocol, bind)
>> {
>> if (variant->sandbox == TCP_SANDBOX) {
>> const struct landlock_ruleset_attr ruleset_attr = {
>> - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> + .handled_access_net = ACCESS_ALL,
>> };
>> - const struct landlock_net_port_attr tcp_bind_connect_p0 = {
>> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> + const struct landlock_net_port_attr tcp_not_restricted_p0 = {
>> + .allowed_access = ACCESS_ALL,
>> .port = self->srv0.port,
>> };
>> - const struct landlock_net_port_attr tcp_connect_p1 = {
>> - .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> + const struct landlock_net_port_attr tcp_denied_bind_p1 = {
>> + .allowed_access = ACCESS_ALL &
>> + ~LANDLOCK_ACCESS_NET_BIND_TCP,
>> .port = self->srv1.port,
>> };
>> int ruleset_fd;
>> @@ -589,48 +617,47 @@ TEST_F(protocol, bind)
>> sizeof(ruleset_attr), 0);
>> ASSERT_LE(0, ruleset_fd);
>>
>> - /* Allows connect and bind for the first port. */
>> + /* Allows all actions for the first port. */
>> ASSERT_EQ(0,
>> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>> - &tcp_bind_connect_p0, 0));
>> + &tcp_not_restricted_p0, 0));
>>
>> - /* Allows connect and denies bind for the second port. */
>> + /* Allows all actions despite bind. */
>
> s/despite/except/ would be more conventional English, I believe.
will be fixed, thanks!
>
>> ASSERT_EQ(0,
>> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>> - &tcp_connect_p1, 0));
>> + &tcp_denied_bind_p1, 0));
>>
>> enforce_ruleset(_metadata, ruleset_fd);
>> EXPECT_EQ(0, close(ruleset_fd));
>> }
>> + bool restricted = is_restricted(&variant->prot, variant->sandbox);
>>
>> /* Binds a socket to the first port. */
>> - test_bind_and_connect(_metadata, &self->srv0, false, false);
>> + test_restricted_net_fixture(_metadata, &self->srv0, false, false,
>> + false);
>>
>> /* Binds a socket to the second port. */
>> - test_bind_and_connect(_metadata, &self->srv1,
>> - is_restricted(&variant->prot, variant->sandbox),
>> - false);
>> + test_restricted_net_fixture(_metadata, &self->srv1, restricted, false,
>> + false);
>>
>> /* Binds a socket to the third port. */
>> - test_bind_and_connect(_metadata, &self->srv2,
>> - is_restricted(&variant->prot, variant->sandbox),
>> - is_restricted(&variant->prot, variant->sandbox));
>> + test_restricted_net_fixture(_metadata, &self->srv2, restricted,
>> + restricted, restricted);
>> }
>>
>> TEST_F(protocol, connect)
>> {
>> if (variant->sandbox == TCP_SANDBOX) {
>> const struct landlock_ruleset_attr ruleset_attr = {
>> - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> + .handled_access_net = ACCESS_ALL,
>> };
>> - const struct landlock_net_port_attr tcp_bind_connect_p0 = {
>> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> + const struct landlock_net_port_attr tcp_not_restricted_p0 = {
>> + .allowed_access = ACCESS_ALL,
>> .port = self->srv0.port,
>> };
>> - const struct landlock_net_port_attr tcp_bind_p1 = {
>> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
>> + const struct landlock_net_port_attr tcp_denied_connect_p1 = {
>> + .allowed_access = ACCESS_ALL &
>> + ~LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> .port = self->srv1.port,
>> };
>> int ruleset_fd;
>> @@ -639,28 +666,27 @@ TEST_F(protocol, connect)
>> sizeof(ruleset_attr), 0);
>> ASSERT_LE(0, ruleset_fd);
>>
>> - /* Allows connect and bind for the first port. */
>> + /* Allows all actions for the first port. */
>> ASSERT_EQ(0,
>> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>> - &tcp_bind_connect_p0, 0));
>> + &tcp_not_restricted_p0, 0));
>>
>> - /* Allows bind and denies connect for the second port. */
>> + /* Allows all actions despite connect. */
>
> Same here.
will be fixed also
>
>> ASSERT_EQ(0,
>> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>> - &tcp_bind_p1, 0));
>> + &tcp_denied_connect_p1, 0));
>>
>> enforce_ruleset(_metadata, ruleset_fd);
>> EXPECT_EQ(0, close(ruleset_fd));
>> }
>> -
>> - test_bind_and_connect(_metadata, &self->srv0, false, false);
>> -
>> - test_bind_and_connect(_metadata, &self->srv1, false,
>> - is_restricted(&variant->prot, variant->sandbox));
>> -
>> - test_bind_and_connect(_metadata, &self->srv2,
>> - is_restricted(&variant->prot, variant->sandbox),
>> - is_restricted(&variant->prot, variant->sandbox));
>> + bool restricted = is_restricted(&variant->prot, variant->sandbox);
>> +
>> + test_restricted_net_fixture(_metadata, &self->srv0, false, false,
>> + false);
>> + test_restricted_net_fixture(_metadata, &self->srv1, false, restricted,
>> + false);
>> + test_restricted_net_fixture(_metadata, &self->srv2, restricted,
>> + restricted, restricted);
>> }
>>
>> TEST_F(protocol, bind_unspec)
>> @@ -761,7 +787,7 @@ TEST_F(protocol, connect_unspec)
>> ASSERT_LE(0, bind_fd);
>> EXPECT_EQ(0, bind_variant(bind_fd, &self->srv0));
>> if (self->srv0.protocol.type == SOCK_STREAM)
>> - EXPECT_EQ(0, listen(bind_fd, backlog));
>> + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
>>
>> child = fork();
>> ASSERT_LE(0, child);
>> @@ -1127,8 +1153,8 @@ TEST_F(tcp_layers, ruleset_overlap)
>> * Forbids to connect to the socket because only one ruleset layer
>> * allows connect.
>> */
>> - test_bind_and_connect(_metadata, &self->srv0, false,
>> - variant->num_layers >= 2);
>> + test_restricted_net_fixture(_metadata, &self->srv0, false,
>> + variant->num_layers >= 2, false);
>> }
>>
>> TEST_F(tcp_layers, ruleset_expand)
>> @@ -1208,11 +1234,12 @@ TEST_F(tcp_layers, ruleset_expand)
>> EXPECT_EQ(0, close(ruleset_fd));
>> }
>>
>> - test_bind_and_connect(_metadata, &self->srv0, false,
>> - variant->num_layers >= 3);
>> + test_restricted_net_fixture(_metadata, &self->srv0, false,
>> + variant->num_layers >= 3, false);
>>
>> - test_bind_and_connect(_metadata, &self->srv1, variant->num_layers >= 1,
>> - variant->num_layers >= 2);
>> + test_restricted_net_fixture(_metadata, &self->srv1,
>> + variant->num_layers >= 1,
>> + variant->num_layers >= 2, false);
>> }
>>
>> /* clang-format off */
>> @@ -1230,16 +1257,6 @@ FIXTURE_TEARDOWN(mini)
>> {
>> }
>>
>> -/* clang-format off */
>> -
>> -#define ACCESS_LAST LANDLOCK_ACCESS_NET_CONNECT_TCP
>> -
>> -#define ACCESS_ALL ( \
>> - LANDLOCK_ACCESS_NET_BIND_TCP | \
>> - LANDLOCK_ACCESS_NET_CONNECT_TCP)
>> -
>> -/* clang-format on */
>> -
>> TEST_F(mini, network_access_rights)
>> {
>> const struct landlock_ruleset_attr ruleset_attr = {
>> @@ -1454,8 +1471,9 @@ TEST_F(mini, tcp_port_overflow)
>>
>> enforce_ruleset(_metadata, ruleset_fd);
>>
>> - test_bind_and_connect(_metadata, &srv_denied, true, true);
>> - test_bind_and_connect(_metadata, &srv_max_allowed, false, false);
>> + test_restricted_net_fixture(_metadata, &srv_denied, true, true, false);
>> + test_restricted_net_fixture(_metadata, &srv_max_allowed, false, false,
>> + false);
>> }
>>
>> FIXTURE(ipv4_tcp)
>> @@ -1485,22 +1503,21 @@ FIXTURE_TEARDOWN(ipv4_tcp)
>> TEST_F(ipv4_tcp, port_endianness)
>> {
>> const struct landlock_ruleset_attr ruleset_attr = {
>> - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> + .handled_access_net = ACCESS_ALL,
>> };
>> const struct landlock_net_port_attr bind_host_endian_p0 = {
>> .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP,
>> /* Host port format. */
>> .port = self->srv0.port,
>> };
>> - const struct landlock_net_port_attr connect_big_endian_p0 = {
>> - .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> + const struct landlock_net_port_attr connect_listen_big_endian_p0 = {
>> + .allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP |
>> + LANDLOCK_ACCESS_NET_LISTEN_TCP,
>> /* Big endian port format. */
>> .port = htons(self->srv0.port),
>> };
>> - const struct landlock_net_port_attr bind_connect_host_endian_p1 = {
>> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> + const struct landlock_net_port_attr not_restricted_host_endian_p1 = {
>> + .allowed_access = ACCESS_ALL,
>> /* Host port format. */
>> .port = self->srv1.port,
>> };
>> @@ -1514,16 +1531,18 @@ TEST_F(ipv4_tcp, port_endianness)
>> ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>> &bind_host_endian_p0, 0));
>> ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>> - &connect_big_endian_p0, 0));
>> + &connect_listen_big_endian_p0, 0));
>> ASSERT_EQ(0, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>> - &bind_connect_host_endian_p1, 0));
>> + ¬_restricted_host_endian_p1, 0));
>> enforce_ruleset(_metadata, ruleset_fd);
>>
>> /* No restriction for big endinan CPU. */
>> - test_bind_and_connect(_metadata, &self->srv0, false, little_endian);
>> + test_restricted_net_fixture(_metadata, &self->srv0, false,
>> + little_endian, little_endian);
>>
>> /* No restriction for any CPU. */
>> - test_bind_and_connect(_metadata, &self->srv1, false, false);
>> + test_restricted_net_fixture(_metadata, &self->srv1, false, false,
>> + false);
>> }
>>
>> TEST_F(ipv4_tcp, with_fs)
>> @@ -1691,7 +1710,7 @@ TEST_F(port_specific, bind_connect_zero)
>> ret = bind_variant(bind_fd, &self->srv0);
>> EXPECT_EQ(0, ret);
>>
>> - EXPECT_EQ(0, listen(bind_fd, backlog));
>> + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
>>
>> /* Connects on port 0. */
>> ret = connect_variant(connect_fd, &self->srv0);
>> @@ -1714,26 +1733,23 @@ TEST_F(port_specific, bind_connect_zero)
>> EXPECT_EQ(0, close(bind_fd));
>> }
>>
>> -TEST_F(port_specific, bind_connect_1023)
>> +TEST_F(port_specific, port_1023)
>> {
>> int bind_fd, connect_fd, ret;
>>
>> - /* Adds a rule layer with bind and connect actions. */
>> + /* Adds a rule layer with all actions. */
>> if (variant->sandbox == TCP_SANDBOX) {
>> const struct landlock_ruleset_attr ruleset_attr = {
>> - .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
>> - LANDLOCK_ACCESS_NET_CONNECT_TCP
>> + .handled_access_net = ACCESS_ALL
>> };
>> /* A rule with port value less than 1024. */
>> - const struct landlock_net_port_attr tcp_bind_connect_low_range = {
>> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> + const struct landlock_net_port_attr tcp_low_range_port = {
>> + .allowed_access = ACCESS_ALL,
>> .port = 1023,
>> };
>> /* A rule with 1024 port. */
>> - const struct landlock_net_port_attr tcp_bind_connect = {
>> - .allowed_access = LANDLOCK_ACCESS_NET_BIND_TCP |
>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> + const struct landlock_net_port_attr tcp_port_1024 = {
>> + .allowed_access = ACCESS_ALL,
>> .port = 1024,
>> };
>> int ruleset_fd;
>> @@ -1744,10 +1760,10 @@ TEST_F(port_specific, bind_connect_1023)
>>
>> ASSERT_EQ(0,
>> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>> - &tcp_bind_connect_low_range, 0));
>> + &tcp_low_range_port, 0));
>> ASSERT_EQ(0,
>> landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>> - &tcp_bind_connect, 0));
>> + &tcp_port_1024, 0));
>>
>> enforce_ruleset(_metadata, ruleset_fd);
>> EXPECT_EQ(0, close(ruleset_fd));
>> @@ -1771,7 +1787,7 @@ TEST_F(port_specific, bind_connect_1023)
>> ret = bind_variant(bind_fd, &self->srv0);
>> clear_cap(_metadata, CAP_NET_BIND_SERVICE);
>> EXPECT_EQ(0, ret);
>> - EXPECT_EQ(0, listen(bind_fd, backlog));
>> + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
>>
>> /* Connects on the binded port 1023. */
>> ret = connect_variant(connect_fd, &self->srv0);
>> @@ -1791,7 +1807,7 @@ TEST_F(port_specific, bind_connect_1023)
>> /* Binds on port 1024. */
>> ret = bind_variant(bind_fd, &self->srv0);
>> EXPECT_EQ(0, ret);
>> - EXPECT_EQ(0, listen(bind_fd, backlog));
>> + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
>>
>> /* Connects on the binded port 1024. */
>> ret = connect_variant(connect_fd, &self->srv0);
>> --
>> 2.34.1
>>
^ permalink raw reply [flat|nested] 40+ messages in thread
* [RFC PATCH v2 4/9] selftests/landlock: Test listening restriction
2024-08-14 3:01 [RFC PATCH v2 0/9] Support TCP listen access-control Mikhail Ivanov
` (2 preceding siblings ...)
2024-08-14 3:01 ` [RFC PATCH v2 3/9] selftests/landlock: Support LANDLOCK_ACCESS_NET_LISTEN_TCP Mikhail Ivanov
@ 2024-08-14 3:01 ` Mikhail Ivanov
2024-08-20 12:31 ` Günther Noack
2024-08-14 3:01 ` [RFC PATCH v2 5/9] selftests/landlock: Test listen on connected socket Mikhail Ivanov
` (5 subsequent siblings)
9 siblings, 1 reply; 40+ messages in thread
From: Mikhail Ivanov @ 2024-08-14 3:01 UTC (permalink / raw)
To: mic
Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
netfilter-devel, yusongping, artem.kuzin, konstantin.meskhidze
Add a test for listening restriction. It's similar to protocol.bind and
protocol.connect tests.
Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
---
tools/testing/selftests/landlock/net_test.c | 44 +++++++++++++++++++++
1 file changed, 44 insertions(+)
diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
index 8126f5c0160f..b6fe9bde205f 100644
--- a/tools/testing/selftests/landlock/net_test.c
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -689,6 +689,50 @@ TEST_F(protocol, connect)
restricted, restricted);
}
+TEST_F(protocol, listen)
+{
+ if (variant->sandbox == TCP_SANDBOX) {
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_net = ACCESS_ALL,
+ };
+ const struct landlock_net_port_attr tcp_not_restricted_p0 = {
+ .allowed_access = ACCESS_ALL,
+ .port = self->srv0.port,
+ };
+ const struct landlock_net_port_attr tcp_denied_listen_p1 = {
+ .allowed_access = ACCESS_ALL &
+ ~LANDLOCK_ACCESS_NET_LISTEN_TCP,
+ .port = self->srv1.port,
+ };
+ int ruleset_fd;
+
+ ruleset_fd = landlock_create_ruleset(&ruleset_attr,
+ sizeof(ruleset_attr), 0);
+ ASSERT_LE(0, ruleset_fd);
+
+ /* Allows all actions for the first port. */
+ ASSERT_EQ(0,
+ landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
+ &tcp_not_restricted_p0, 0));
+
+ /* Allows all actions despite listen. */
+ ASSERT_EQ(0,
+ landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
+ &tcp_denied_listen_p1, 0));
+
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+ }
+ bool restricted = is_restricted(&variant->prot, variant->sandbox);
+
+ test_restricted_net_fixture(_metadata, &self->srv0, false, false,
+ false);
+ test_restricted_net_fixture(_metadata, &self->srv1, false, false,
+ restricted);
+ test_restricted_net_fixture(_metadata, &self->srv2, restricted,
+ restricted, restricted);
+}
+
TEST_F(protocol, bind_unspec)
{
const struct landlock_ruleset_attr ruleset_attr = {
--
2.34.1
^ permalink raw reply related [flat|nested] 40+ messages in thread
* Re: [RFC PATCH v2 4/9] selftests/landlock: Test listening restriction
2024-08-14 3:01 ` [RFC PATCH v2 4/9] selftests/landlock: Test listening restriction Mikhail Ivanov
@ 2024-08-20 12:31 ` Günther Noack
2024-08-20 18:46 ` Mikhail Ivanov
0 siblings, 1 reply; 40+ messages in thread
From: Günther Noack @ 2024-08-20 12:31 UTC (permalink / raw)
To: Mikhail Ivanov
Cc: mic, willemdebruijn.kernel, gnoack3000, linux-security-module,
netdev, netfilter-devel, yusongping, artem.kuzin,
konstantin.meskhidze
On Wed, Aug 14, 2024 at 11:01:46AM +0800, Mikhail Ivanov wrote:
> Add a test for listening restriction. It's similar to protocol.bind and
> protocol.connect tests.
>
> Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
> ---
> tools/testing/selftests/landlock/net_test.c | 44 +++++++++++++++++++++
> 1 file changed, 44 insertions(+)
>
> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
> index 8126f5c0160f..b6fe9bde205f 100644
> --- a/tools/testing/selftests/landlock/net_test.c
> +++ b/tools/testing/selftests/landlock/net_test.c
> @@ -689,6 +689,50 @@ TEST_F(protocol, connect)
> restricted, restricted);
> }
>
> +TEST_F(protocol, listen)
> +{
> + if (variant->sandbox == TCP_SANDBOX) {
> + const struct landlock_ruleset_attr ruleset_attr = {
> + .handled_access_net = ACCESS_ALL,
> + };
> + const struct landlock_net_port_attr tcp_not_restricted_p0 = {
> + .allowed_access = ACCESS_ALL,
> + .port = self->srv0.port,
> + };
> + const struct landlock_net_port_attr tcp_denied_listen_p1 = {
> + .allowed_access = ACCESS_ALL &
> + ~LANDLOCK_ACCESS_NET_LISTEN_TCP,
> + .port = self->srv1.port,
> + };
> + int ruleset_fd;
> +
> + ruleset_fd = landlock_create_ruleset(&ruleset_attr,
> + sizeof(ruleset_attr), 0);
Nit: The declaration and the assignment of ruleset_fd can be merged into one
line and made const. (Not a big deal, but it was done a bit more consistently
in the rest of the code, I think.)
> + ASSERT_LE(0, ruleset_fd);
> +
> + /* Allows all actions for the first port. */
> + ASSERT_EQ(0,
> + landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> + &tcp_not_restricted_p0, 0));
> +
> + /* Allows all actions despite listen. */
> + ASSERT_EQ(0,
> + landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> + &tcp_denied_listen_p1, 0));
> +
> + enforce_ruleset(_metadata, ruleset_fd);
> + EXPECT_EQ(0, close(ruleset_fd));
> + }
This entire "if (variant->sandbox == TCP_SANDBOX)" conditional does the exact
same thing as the one from patch 5/9. Should that (or parts of it) get
extracted into a suitable helper?
> + bool restricted = is_restricted(&variant->prot, variant->sandbox);
> +
> + test_restricted_net_fixture(_metadata, &self->srv0, false, false,
> + false);
> + test_restricted_net_fixture(_metadata, &self->srv1, false, false,
> + restricted);
> + test_restricted_net_fixture(_metadata, &self->srv2, restricted,
> + restricted, restricted);
If we start having logic and conditionals in the test implementation (in this
case, in test_restricted_test_fixture()), this might be a sign that that test
implementation should maybe be split apart? Once the test is as complicated as
the code under test, it does not simplify our confidence in the code much any
more?
(It is often considered bad practice to put conditionals in tests, e.g. in
https://testing.googleblog.com/2014/07/testing-on-toilet-dont-put-logic-in.html)
Do you think we have a way to simplify that?
Readability remark: I am not that strongly invested in this idea, but in the
call to test_restricted_net_fixture(), it is difficult to understand "false,
false, false", without jumping around in the file. Should we try to make this
more explicit?
I wonder whether we should just pass a struct, so that everything at least has a
name?
test_restricted_net_fixture((struct expected_net_enforcement){
.deny_bind = false,
.deny_connect = false,
.deny_listen = false,
});
Then it would be clearer which boolean is which,
and you could use the fact that unspecified struct fields are zero-initialized?
(Alternatively, you could also spell out error codes here, instead of booleans.)
> +}
> +
> TEST_F(protocol, bind_unspec)
> {
> const struct landlock_ruleset_attr ruleset_attr = {
> --
> 2.34.1
>
—Günther
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [RFC PATCH v2 4/9] selftests/landlock: Test listening restriction
2024-08-20 12:31 ` Günther Noack
@ 2024-08-20 18:46 ` Mikhail Ivanov
2024-09-25 18:31 ` Mickaël Salaün
0 siblings, 1 reply; 40+ messages in thread
From: Mikhail Ivanov @ 2024-08-20 18:46 UTC (permalink / raw)
To: Günther Noack
Cc: mic, willemdebruijn.kernel, gnoack3000, linux-security-module,
netdev, netfilter-devel, yusongping, artem.kuzin,
konstantin.meskhidze
8/20/2024 3:31 PM, Günther Noack wrote:
> On Wed, Aug 14, 2024 at 11:01:46AM +0800, Mikhail Ivanov wrote:
>> Add a test for listening restriction. It's similar to protocol.bind and
>> protocol.connect tests.
>>
>> Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
>> ---
>> tools/testing/selftests/landlock/net_test.c | 44 +++++++++++++++++++++
>> 1 file changed, 44 insertions(+)
>>
>> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
>> index 8126f5c0160f..b6fe9bde205f 100644
>> --- a/tools/testing/selftests/landlock/net_test.c
>> +++ b/tools/testing/selftests/landlock/net_test.c
>> @@ -689,6 +689,50 @@ TEST_F(protocol, connect)
>> restricted, restricted);
>> }
>>
>> +TEST_F(protocol, listen)
>> +{
>> + if (variant->sandbox == TCP_SANDBOX) {
>> + const struct landlock_ruleset_attr ruleset_attr = {
>> + .handled_access_net = ACCESS_ALL,
>> + };
>> + const struct landlock_net_port_attr tcp_not_restricted_p0 = {
>> + .allowed_access = ACCESS_ALL,
>> + .port = self->srv0.port,
>> + };
>> + const struct landlock_net_port_attr tcp_denied_listen_p1 = {
>> + .allowed_access = ACCESS_ALL &
>> + ~LANDLOCK_ACCESS_NET_LISTEN_TCP,
>> + .port = self->srv1.port,
>> + };
>> + int ruleset_fd;
>> +
>> + ruleset_fd = landlock_create_ruleset(&ruleset_attr,
>> + sizeof(ruleset_attr), 0);
>
> Nit: The declaration and the assignment of ruleset_fd can be merged into one
> line and made const. (Not a big deal, but it was done a bit more consistently
> in the rest of the code, I think.)
Current variant is performed in every TEST_F() method. I assume that
this is required in order to not make a mess by combining the
ruleset_attr and several rule structures with the operation of creating
ruleset. WDYT?
>
>> + ASSERT_LE(0, ruleset_fd);
>> +
>> + /* Allows all actions for the first port. */
>> + ASSERT_EQ(0,
>> + landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>> + &tcp_not_restricted_p0, 0));
>> +
>> + /* Allows all actions despite listen. */
>> + ASSERT_EQ(0,
>> + landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>> + &tcp_denied_listen_p1, 0));
>> +
>> + enforce_ruleset(_metadata, ruleset_fd);
>> + EXPECT_EQ(0, close(ruleset_fd));
>> + }
>
> This entire "if (variant->sandbox == TCP_SANDBOX)" conditional does the exact
> same thing as the one from patch 5/9. Should that (or parts of it) get
> extracted into a suitable helper?
I don't think replacing
if (variant->sandbox == TCP_SANDBOX)
with
if (is_tcp_sandbox(variant))
will change anything, this condition is already quite simple. If
you think that such helper is more convenient, I can add it.
>
>> + bool restricted = is_restricted(&variant->prot, variant->sandbox);
>> +
>> + test_restricted_net_fixture(_metadata, &self->srv0, false, false,
>> + false);
>> + test_restricted_net_fixture(_metadata, &self->srv1, false, false,
>> + restricted);
>> + test_restricted_net_fixture(_metadata, &self->srv2, restricted,
>> + restricted, restricted);
>
> If we start having logic and conditionals in the test implementation (in this
> case, in test_restricted_test_fixture()), this might be a sign that that test
> implementation should maybe be split apart? Once the test is as complicated as
> the code under test, it does not simplify our confidence in the code much any
> more?
>
> (It is often considered bad practice to put conditionals in tests, e.g. in
> https://testing.googleblog.com/2014/07/testing-on-toilet-dont-put-logic-in.html)
>
> Do you think we have a way to simplify that?
I agree.. using 3 external booleans to control behavior of the
test is really messy. I believe the best we can do to avoid this is to
split "test_restricted_net_fixture()" into few independent tests. For
example we can turn this call:
test_restricted_net_fixture(_metadata, &self->srv0, false,
false, false);
into multiple smaller tests:
/* Tries to bind with invalid and minimal addrlen. */
EXPECT_EQ(0, TEST_BIND(&self->srv0));
/* Tries to connect with invalid and minimal addrlen. */
EXPECT_EQ(0, TEST_CONNECT(&self->srv0));
/* Tries to listen. */
EXPECT_EQ(0, TEST_LISTEN(&self->srv0));
/* Connection tests. */
EXPECT_EQ(0, TEST_CLIENT_SERVER(&self->srv0));
Each test is wrapped in a macro that implicitly passes _metadata argument.
This case in which every access is allowed can be wrapped in a macro:
TEST_UNRESTRICTED_NET_FIXTURE(&self->srv0);
Such approach has following cons though:
* A lot of duplicated code. These small helpers should be added to every
test that uses "test_restricted_net_fixture()". Currently there
are 16 calls of this helper.
* There is wouldn't be a single entity that is used to test a network
under different sandbox scenarios. If we split the helper each test
should care about (1) sandboxing, (2) running all required tests. For
example TEST_LISTEN() and TEST_CLIENT_SERVER() could not be called if
bind is restricted.
For example protocol.bind test would have following lines after
"test_restricted_net_fixture()" is removed:
TEST_UNRESTRICTED_NET_FIXTURE(&self->srv0);
if (is_restricted(&variant->prot, variant->sandbox)) {
EXPECT_EQ(-EACCES, TEST_BIND(&self->srv1));
EXPECT_EQ(0, TEST_CONNECT(&self->srv1));
EXPECT_EQ(-EACCES, TEST_BIND(&self->srv2));
EXPECT_EQ(-EACCES, TEST_CONNECT(&self->srv2));
} else {
TEST_UNRESTRICTED_NET_FIXTURE(&self->srv1);
TEST_UNRESTRICTED_NET_FIXTURE(&self->srv2);
}
I suggest leaving "test_restricted_net_fixture()" and refactor this
booleans (in the way you suggested) in order not to lose simplicity in
the testing:
bool restricted = is_restricted(&variant->prot,
variant->sandbox);
test_restricted_net_fixture(_metadata, &self->srv0,
(struct expected_net_enforcement){
.deny_bind = false,
.deny_connect = false,
.deny_listen = false
});
test_restricted_net_fixture(_metadata, &self->srv1,
(struct expected_net_enforcement){
.deny_bind = false,
.deny_connect = restricted,
.deny_listen = false
});
test_restricted_net_fixture(_metadata, &self->srv2,
(struct expected_net_enforcement){
.deny_bind = restricted,
.deny_connect = restricted,
.deny_listen = restricted
});
But it's really not obvious design issue and splitting helper can really
be a better solution. WDYT?
>
>
> Readability remark: I am not that strongly invested in this idea, but in the
> call to test_restricted_net_fixture(), it is difficult to understand "false,
> false, false", without jumping around in the file. Should we try to make this
> more explicit?
>
> I wonder whether we should just pass a struct, so that everything at least has a
> name?
>
> test_restricted_net_fixture((struct expected_net_enforcement){
> .deny_bind = false,
> .deny_connect = false,
> .deny_listen = false,
> });
>
> Then it would be clearer which boolean is which,
> and you could use the fact that unspecified struct fields are zero-initialized?
>
> (Alternatively, you could also spell out error codes here, instead of booleans.)
Agreed, this is a best option for refactoring.
I've also tried adding access_mask field to the service_fixture struct
with all accesses allowed by default. In a test, then you just need to
remove the necessary accesses after sandboxing:
if (is_restricted(&variant->prot, variant->sandbox))
clear_access(&self->srv2,
LANDLOCK_ACCESS_NET_BIND_TCP |
LANDLOCK_ACCESS_NET_CONNECT_TCP);
test_restricted_net_fixture(_metadata, &self->srv2);
But this solution is too implicit for the helper. Passing struct would
be better.
>
>> +}
>> +
>> TEST_F(protocol, bind_unspec)
>> {
>> const struct landlock_ruleset_attr ruleset_attr = {
>> --
>> 2.34.1
>>
>
> —Günther
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [RFC PATCH v2 4/9] selftests/landlock: Test listening restriction
2024-08-20 18:46 ` Mikhail Ivanov
@ 2024-09-25 18:31 ` Mickaël Salaün
2024-09-26 13:51 ` Mikhail Ivanov
0 siblings, 1 reply; 40+ messages in thread
From: Mickaël Salaün @ 2024-09-25 18:31 UTC (permalink / raw)
To: Mikhail Ivanov
Cc: Günther Noack, willemdebruijn.kernel, gnoack3000,
linux-security-module, netdev, netfilter-devel, yusongping,
artem.kuzin, konstantin.meskhidze, Matthieu Buffet
On Tue, Aug 20, 2024 at 09:46:56PM +0300, Mikhail Ivanov wrote:
> 8/20/2024 3:31 PM, Günther Noack wrote:
> > On Wed, Aug 14, 2024 at 11:01:46AM +0800, Mikhail Ivanov wrote:
> > > Add a test for listening restriction. It's similar to protocol.bind and
> > > protocol.connect tests.
> > >
> > > Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
> > > ---
> > > tools/testing/selftests/landlock/net_test.c | 44 +++++++++++++++++++++
> > > 1 file changed, 44 insertions(+)
> > >
> > > diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
> > > index 8126f5c0160f..b6fe9bde205f 100644
> > > --- a/tools/testing/selftests/landlock/net_test.c
> > > +++ b/tools/testing/selftests/landlock/net_test.c
> > > @@ -689,6 +689,50 @@ TEST_F(protocol, connect)
> > > restricted, restricted);
> > > }
> > > +TEST_F(protocol, listen)
> > > +{
> > > + if (variant->sandbox == TCP_SANDBOX) {
> > > + const struct landlock_ruleset_attr ruleset_attr = {
> > > + .handled_access_net = ACCESS_ALL,
> > > + };
> > > + const struct landlock_net_port_attr tcp_not_restricted_p0 = {
> > > + .allowed_access = ACCESS_ALL,
> > > + .port = self->srv0.port,
> > > + };
> > > + const struct landlock_net_port_attr tcp_denied_listen_p1 = {
> > > + .allowed_access = ACCESS_ALL &
> > > + ~LANDLOCK_ACCESS_NET_LISTEN_TCP,
> > > + .port = self->srv1.port,
> > > + };
> > > + int ruleset_fd;
> > > +
> > > + ruleset_fd = landlock_create_ruleset(&ruleset_attr,
> > > + sizeof(ruleset_attr), 0);
> >
> > Nit: The declaration and the assignment of ruleset_fd can be merged into one
> > line and made const. (Not a big deal, but it was done a bit more consistently
> > in the rest of the code, I think.)
>
> Current variant is performed in every TEST_F() method. I assume that
> this is required in order to not make a mess by combining the
> ruleset_attr and several rule structures with the operation of creating
> ruleset. WDYT?
Using variant->sandbox helps identify test scenarios.
>
> >
> > > + ASSERT_LE(0, ruleset_fd);
> > > +
> > > + /* Allows all actions for the first port. */
> > > + ASSERT_EQ(0,
> > > + landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> > > + &tcp_not_restricted_p0, 0));
> > > +
> > > + /* Allows all actions despite listen. */
> > > + ASSERT_EQ(0,
> > > + landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> > > + &tcp_denied_listen_p1, 0));
> > > +
> > > + enforce_ruleset(_metadata, ruleset_fd);
> > > + EXPECT_EQ(0, close(ruleset_fd));
> > > + }
> >
> > This entire "if (variant->sandbox == TCP_SANDBOX)" conditional does the exact
> > same thing as the one from patch 5/9. Should that (or parts of it) get
> > extracted into a suitable helper?
>
> I don't think replacing
> if (variant->sandbox == TCP_SANDBOX)
> with
> if (is_tcp_sandbox(variant))
> will change anything, this condition is already quite simple. If
> you think that such helper is more convenient, I can add it.
The variant->sandbox check is OK, but the following code block should
not be duplicated because it makes more code to review and we may wonder
if it does the same thing. Intead we can have something like this:
if (variant->sandbox == TCP_SANDBOX)
restrict_tcp_listen(_metadata, self);
>
> >
> > > + bool restricted = is_restricted(&variant->prot, variant->sandbox);
> > > +
> > > + test_restricted_net_fixture(_metadata, &self->srv0, false, false,
> > > + false);
> > > + test_restricted_net_fixture(_metadata, &self->srv1, false, false,
> > > + restricted);
> > > + test_restricted_net_fixture(_metadata, &self->srv2, restricted,
> > > + restricted, restricted);
> >
> > If we start having logic and conditionals in the test implementation (in this
> > case, in test_restricted_test_fixture()), this might be a sign that that test
> > implementation should maybe be split apart? Once the test is as complicated as
> > the code under test, it does not simplify our confidence in the code much any
> > more?
> >
> > (It is often considered bad practice to put conditionals in tests, e.g. in
> > https://testing.googleblog.com/2014/07/testing-on-toilet-dont-put-logic-in.html)
> >
> > Do you think we have a way to simplify that?
>
> I agree.. using 3 external booleans to control behavior of the
> test is really messy. I believe the best we can do to avoid this is to
> split "test_restricted_net_fixture()" into few independent tests. For
> example we can turn this call:
>
> test_restricted_net_fixture(_metadata, &self->srv0, false,
> false, false);
>
> into multiple smaller tests:
>
> /* Tries to bind with invalid and minimal addrlen. */
> EXPECT_EQ(0, TEST_BIND(&self->srv0));
>
> /* Tries to connect with invalid and minimal addrlen. */
> EXPECT_EQ(0, TEST_CONNECT(&self->srv0));
>
> /* Tries to listen. */
> EXPECT_EQ(0, TEST_LISTEN(&self->srv0));
>
> /* Connection tests. */
> EXPECT_EQ(0, TEST_CLIENT_SERVER(&self->srv0));
These standalone bind/connect/listen/client_server looks good.
>
> Each test is wrapped in a macro that implicitly passes _metadata argument.
I'd prefer to not use macros to pass argument because it makes it more
difficult to understand what is going on. Just create a
test_*(_metadata, ...) helper.
>
> This case in which every access is allowed can be wrapped in a macro:
>
> TEST_UNRESTRICTED_NET_FIXTURE(&self->srv0);
Let's try to avoid macros as much as possible.
>
> Such approach has following cons though:
> * A lot of duplicated code. These small helpers should be added to every
> test that uses "test_restricted_net_fixture()". Currently there
> are 16 calls of this helper.
We can start by calling these test_listen()-like helpers in
test_bind_and_connect(). We should be careful to not change too much
the existing test code to be able to run them against older kernels
without too much changes.
>
> * There is wouldn't be a single entity that is used to test a network
> under different sandbox scenarios. If we split the helper each test
> should care about (1) sandboxing, (2) running all required tests. For
> example TEST_LISTEN() and TEST_CLIENT_SERVER() could not be called if
> bind is restricted.
Yes, this might be an issue, but for this specific case we may write a
dedicated test if it helps.
>
> For example protocol.bind test would have following lines after
> "test_restricted_net_fixture()" is removed:
>
> TEST_UNRESTRICTED_NET_FIXTURE(&self->srv0);
>
> if (is_restricted(&variant->prot, variant->sandbox)) {
> EXPECT_EQ(-EACCES, TEST_BIND(&self->srv1));
> EXPECT_EQ(0, TEST_CONNECT(&self->srv1));
>
> EXPECT_EQ(-EACCES, TEST_BIND(&self->srv2));
> EXPECT_EQ(-EACCES, TEST_CONNECT(&self->srv2));
> } else {
> TEST_UNRESTRICTED_NET_FIXTURE(&self->srv1);
> TEST_UNRESTRICTED_NET_FIXTURE(&self->srv2);
> }
>
> I suggest leaving "test_restricted_net_fixture()" and refactor this
> booleans (in the way you suggested) in order not to lose simplicity in
> the testing:
>
> bool restricted = is_restricted(&variant->prot,
> variant->sandbox);
>
> test_restricted_net_fixture(_metadata, &self->srv0,
> (struct expected_net_enforcement){
> .deny_bind = false,
> .deny_connect = false,
> .deny_listen = false
> });
> test_restricted_net_fixture(_metadata, &self->srv1,
> (struct expected_net_enforcement){
> .deny_bind = false,
> .deny_connect = restricted,
> .deny_listen = false
> });
> test_restricted_net_fixture(_metadata, &self->srv2,
> (struct expected_net_enforcement){
> .deny_bind = restricted,
> .deny_connect = restricted,
> .deny_listen = restricted
> });
>
> But it's really not obvious design issue and splitting helper can really
> be a better solution. WDYT?
>
> >
> >
> > Readability remark: I am not that strongly invested in this idea, but in the
> > call to test_restricted_net_fixture(), it is difficult to understand "false,
> > false, false", without jumping around in the file. Should we try to make this
> > more explicit?
> >
> > I wonder whether we should just pass a struct, so that everything at least has a
> > name?
> >
> > test_restricted_net_fixture((struct expected_net_enforcement){
> > .deny_bind = false,
> > .deny_connect = false,
> > .deny_listen = false,
> > });
> >
> > Then it would be clearer which boolean is which,
> > and you could use the fact that unspecified struct fields are zero-initialized?
> >
> > (Alternatively, you could also spell out error codes here, instead of booleans.)
>
> Agreed, this is a best option for refactoring.
>
> I've also tried adding access_mask field to the service_fixture struct
> with all accesses allowed by default. In a test, then you just need to
> remove the necessary accesses after sandboxing:
>
> if (is_restricted(&variant->prot, variant->sandbox))
> clear_access(&self->srv2,
> LANDLOCK_ACCESS_NET_BIND_TCP |
> LANDLOCK_ACCESS_NET_CONNECT_TCP);
>
> test_restricted_net_fixture(_metadata, &self->srv2);
>
> But this solution is too implicit for the helper. Passing struct would
> be better.
What about passing the variant to these tests and creating more
fine-grained is_restricted_*() helpers?
>
> >
> > > +}
> > > +
> > > TEST_F(protocol, bind_unspec)
> > > {
> > > const struct landlock_ruleset_attr ruleset_attr = {
> > > --
> > > 2.34.1
> > >
> >
> > —Günther
>
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [RFC PATCH v2 4/9] selftests/landlock: Test listening restriction
2024-09-25 18:31 ` Mickaël Salaün
@ 2024-09-26 13:51 ` Mikhail Ivanov
0 siblings, 0 replies; 40+ messages in thread
From: Mikhail Ivanov @ 2024-09-26 13:51 UTC (permalink / raw)
To: Mickaël Salaün
Cc: Günther Noack, willemdebruijn.kernel, gnoack3000,
linux-security-module, netdev, netfilter-devel, yusongping,
artem.kuzin, konstantin.meskhidze, Matthieu Buffet
On 9/25/2024 9:31 PM, Mickaël Salaün wrote:
> On Tue, Aug 20, 2024 at 09:46:56PM +0300, Mikhail Ivanov wrote:
>> 8/20/2024 3:31 PM, Günther Noack wrote:
>>> On Wed, Aug 14, 2024 at 11:01:46AM +0800, Mikhail Ivanov wrote:
>>>> Add a test for listening restriction. It's similar to protocol.bind and
>>>> protocol.connect tests.
>>>>
>>>> Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
>>>> ---
>>>> tools/testing/selftests/landlock/net_test.c | 44 +++++++++++++++++++++
>>>> 1 file changed, 44 insertions(+)
>>>>
>>>> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
>>>> index 8126f5c0160f..b6fe9bde205f 100644
>>>> --- a/tools/testing/selftests/landlock/net_test.c
>>>> +++ b/tools/testing/selftests/landlock/net_test.c
>>>> @@ -689,6 +689,50 @@ TEST_F(protocol, connect)
>>>> restricted, restricted);
>>>> }
>>>> +TEST_F(protocol, listen)
>>>> +{
>>>> + if (variant->sandbox == TCP_SANDBOX) {
>>>> + const struct landlock_ruleset_attr ruleset_attr = {
>>>> + .handled_access_net = ACCESS_ALL,
>>>> + };
>>>> + const struct landlock_net_port_attr tcp_not_restricted_p0 = {
>>>> + .allowed_access = ACCESS_ALL,
>>>> + .port = self->srv0.port,
>>>> + };
>>>> + const struct landlock_net_port_attr tcp_denied_listen_p1 = {
>>>> + .allowed_access = ACCESS_ALL &
>>>> + ~LANDLOCK_ACCESS_NET_LISTEN_TCP,
>>>> + .port = self->srv1.port,
>>>> + };
>>>> + int ruleset_fd;
>>>> +
>>>> + ruleset_fd = landlock_create_ruleset(&ruleset_attr,
>>>> + sizeof(ruleset_attr), 0);
>>>
>>> Nit: The declaration and the assignment of ruleset_fd can be merged into one
>>> line and made const. (Not a big deal, but it was done a bit more consistently
>>> in the rest of the code, I think.)
>>
>> Current variant is performed in every TEST_F() method. I assume that
>> this is required in order to not make a mess by combining the
>> ruleset_attr and several rule structures with the operation of creating
>> ruleset. WDYT?
>
> Using variant->sandbox helps identify test scenarios.
Sorry, I'm not sure I understand if this advice can be applied to the
discussed nit.
>
>>
>>>
>>>> + ASSERT_LE(0, ruleset_fd);
>>>> +
>>>> + /* Allows all actions for the first port. */
>>>> + ASSERT_EQ(0,
>>>> + landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>>>> + &tcp_not_restricted_p0, 0));
>>>> +
>>>> + /* Allows all actions despite listen. */
>>>> + ASSERT_EQ(0,
>>>> + landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>>>> + &tcp_denied_listen_p1, 0));
>>>> +
>>>> + enforce_ruleset(_metadata, ruleset_fd);
>>>> + EXPECT_EQ(0, close(ruleset_fd));
>>>> + }
>>>
>>> This entire "if (variant->sandbox == TCP_SANDBOX)" conditional does the exact
>>> same thing as the one from patch 5/9. Should that (or parts of it) get
>>> extracted into a suitable helper?
>>
>> I don't think replacing
>> if (variant->sandbox == TCP_SANDBOX)
>> with
>> if (is_tcp_sandbox(variant))
>> will change anything, this condition is already quite simple. If
>> you think that such helper is more convenient, I can add it.
>
> The variant->sandbox check is OK, but the following code block should
> not be duplicated because it makes more code to review and we may wonder
> if it does the same thing. Intead we can have something like this:
>
> if (variant->sandbox == TCP_SANDBOX)
> restrict_tcp_listen(_metadata, self);
Good suggestion, thank you! Probably it would be more simple to make a
single restrict_tcp() helper and pass appropriate access right to it:
if (variant->sandbox == TCP_SANDBOX)
restrict_tcp(_metadata, self,
LANDLOCK_ACCESS_NET_{LISTEN|BIND|CONNECT}_TCP);
>
>>
>>>
>>>> + bool restricted = is_restricted(&variant->prot, variant->sandbox);
>>>> +
>>>> + test_restricted_net_fixture(_metadata, &self->srv0, false, false,
>>>> + false);
>>>> + test_restricted_net_fixture(_metadata, &self->srv1, false, false,
>>>> + restricted);
>>>> + test_restricted_net_fixture(_metadata, &self->srv2, restricted,
>>>> + restricted, restricted);
>>>
>>> If we start having logic and conditionals in the test implementation (in this
>>> case, in test_restricted_test_fixture()), this might be a sign that that test
>>> implementation should maybe be split apart? Once the test is as complicated as
>>> the code under test, it does not simplify our confidence in the code much any
>>> more?
>>>
>>> (It is often considered bad practice to put conditionals in tests, e.g. in
>>> https://testing.googleblog.com/2014/07/testing-on-toilet-dont-put-logic-in.html)
>>>
>>> Do you think we have a way to simplify that?
>>
>> I agree.. using 3 external booleans to control behavior of the
>> test is really messy. I believe the best we can do to avoid this is to
>> split "test_restricted_net_fixture()" into few independent tests. For
>> example we can turn this call:
>>
>> test_restricted_net_fixture(_metadata, &self->srv0, false,
>> false, false);
>>
>> into multiple smaller tests:
>>
>> /* Tries to bind with invalid and minimal addrlen. */
>> EXPECT_EQ(0, TEST_BIND(&self->srv0));
>>
>> /* Tries to connect with invalid and minimal addrlen. */
>> EXPECT_EQ(0, TEST_CONNECT(&self->srv0));
>>
>> /* Tries to listen. */
>> EXPECT_EQ(0, TEST_LISTEN(&self->srv0));
>>
>> /* Connection tests. */
>> EXPECT_EQ(0, TEST_CLIENT_SERVER(&self->srv0));
>
> These standalone bind/connect/listen/client_server looks good.
>
>>
>> Each test is wrapped in a macro that implicitly passes _metadata argument.
>
> I'd prefer to not use macros to pass argument because it makes it more
> difficult to understand what is going on. Just create a
> test_*(_metadata, ...) helper.
Ok, agreed
>
>>
>> This case in which every access is allowed can be wrapped in a macro:
>>
>> TEST_UNRESTRICTED_NET_FIXTURE(&self->srv0);
>
> Let's try to avoid macros as much as possible.
Ok, using such macros in tests might be really confusing.
>
>>
>> Such approach has following cons though:
>> * A lot of duplicated code. These small helpers should be added to every
>> test that uses "test_restricted_net_fixture()". Currently there
>> are 16 calls of this helper.
>
> We can start by calling these test_listen()-like helpers in
> test_bind_and_connect(). We should be careful to not change too much
> the existing test code to be able to run them against older kernels
> without too much changes.
Yeah, ofc we can do this if you think we need a smoother refactoring
process. We can discuss initial changes in dedicated topic [1].
[1] https://github.com/landlock-lsm/linux/issues/34
>
>>
>> * There is wouldn't be a single entity that is used to test a network
>> under different sandbox scenarios. If we split the helper each test
>> should care about (1) sandboxing, (2) running all required tests. For
>> example TEST_LISTEN() and TEST_CLIENT_SERVER() could not be called if
>> bind is restricted.
>
> Yes, this might be an issue, but for this specific case we may write a
> dedicated test if it helps.
Agreed
>
>>
>> For example protocol.bind test would have following lines after
>> "test_restricted_net_fixture()" is removed:
>>
>> TEST_UNRESTRICTED_NET_FIXTURE(&self->srv0);
>>
>> if (is_restricted(&variant->prot, variant->sandbox)) {
>> EXPECT_EQ(-EACCES, TEST_BIND(&self->srv1));
>> EXPECT_EQ(0, TEST_CONNECT(&self->srv1));
>>
>> EXPECT_EQ(-EACCES, TEST_BIND(&self->srv2));
>> EXPECT_EQ(-EACCES, TEST_CONNECT(&self->srv2));
>> } else {
>> TEST_UNRESTRICTED_NET_FIXTURE(&self->srv1);
>> TEST_UNRESTRICTED_NET_FIXTURE(&self->srv2);
>> }
>>
>> I suggest leaving "test_restricted_net_fixture()" and refactor this
>> booleans (in the way you suggested) in order not to lose simplicity in
>> the testing:
>>
>> bool restricted = is_restricted(&variant->prot,
>> variant->sandbox);
>>
>> test_restricted_net_fixture(_metadata, &self->srv0,
>> (struct expected_net_enforcement){
>> .deny_bind = false,
>> .deny_connect = false,
>> .deny_listen = false
>> });
>> test_restricted_net_fixture(_metadata, &self->srv1,
>> (struct expected_net_enforcement){
>> .deny_bind = false,
>> .deny_connect = restricted,
>> .deny_listen = false
>> });
>> test_restricted_net_fixture(_metadata, &self->srv2,
>> (struct expected_net_enforcement){
>> .deny_bind = restricted,
>> .deny_connect = restricted,
>> .deny_listen = restricted
>> });
>>
>> But it's really not obvious design issue and splitting helper can really
>> be a better solution. WDYT?
>>
>>>
>>>
>>> Readability remark: I am not that strongly invested in this idea, but in the
>>> call to test_restricted_net_fixture(), it is difficult to understand "false,
>>> false, false", without jumping around in the file. Should we try to make this
>>> more explicit?
>>>
>>> I wonder whether we should just pass a struct, so that everything at least has a
>>> name?
>>>
>>> test_restricted_net_fixture((struct expected_net_enforcement){
>>> .deny_bind = false,
>>> .deny_connect = false,
>>> .deny_listen = false,
>>> });
>>>
>>> Then it would be clearer which boolean is which,
>>> and you could use the fact that unspecified struct fields are zero-initialized?
>>>
>>> (Alternatively, you could also spell out error codes here, instead of booleans.)
>>
>> Agreed, this is a best option for refactoring.
>>
>> I've also tried adding access_mask field to the service_fixture struct
>> with all accesses allowed by default. In a test, then you just need to
>> remove the necessary accesses after sandboxing:
>>
>> if (is_restricted(&variant->prot, variant->sandbox))
>> clear_access(&self->srv2,
>> LANDLOCK_ACCESS_NET_BIND_TCP |
>> LANDLOCK_ACCESS_NET_CONNECT_TCP);
>>
>> test_restricted_net_fixture(_metadata, &self->srv2);
>>
>> But this solution is too implicit for the helper. Passing struct would
>> be better.
>
> What about passing the variant to these tests and creating more
> fine-grained is_restricted_*() helpers?
Do you mean making is_restricted_{bind|connect|listen}()? We can't
identify which access rights are restricted for the port if we pass only
`variant` to test_bind_and_connect().
>
>>
>>>
>>>> +}
>>>> +
>>>> TEST_F(protocol, bind_unspec)
>>>> {
>>>> const struct landlock_ruleset_attr ruleset_attr = {
>>>> --
>>>> 2.34.1
>>>>
>>>
>>> —Günther
>>
^ permalink raw reply [flat|nested] 40+ messages in thread
* [RFC PATCH v2 5/9] selftests/landlock: Test listen on connected socket
2024-08-14 3:01 [RFC PATCH v2 0/9] Support TCP listen access-control Mikhail Ivanov
` (3 preceding siblings ...)
2024-08-14 3:01 ` [RFC PATCH v2 4/9] selftests/landlock: Test listening restriction Mikhail Ivanov
@ 2024-08-14 3:01 ` Mikhail Ivanov
2024-08-20 13:01 ` Günther Noack
2024-08-14 3:01 ` [RFC PATCH v2 6/9] selftests/landlock: Test listening without explicit bind restriction Mikhail Ivanov
` (4 subsequent siblings)
9 siblings, 1 reply; 40+ messages in thread
From: Mikhail Ivanov @ 2024-08-14 3:01 UTC (permalink / raw)
To: mic
Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
netfilter-devel, yusongping, artem.kuzin, konstantin.meskhidze
Test checks that listen(2) doesn't wrongfully return -EACCES instead
of -EINVAL when trying to listen for an incorrect socket state.
Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
---
Changes since v1:
* Uses 'protocol' fixture instead of 'ipv4_tcp'.
* Minor fixes.
---
tools/testing/selftests/landlock/net_test.c | 74 +++++++++++++++++++++
1 file changed, 74 insertions(+)
diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
index b6fe9bde205f..551891b18b7a 100644
--- a/tools/testing/selftests/landlock/net_test.c
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -926,6 +926,80 @@ TEST_F(protocol, connect_unspec)
EXPECT_EQ(0, close(bind_fd));
}
+TEST_F(protocol, listen_on_connected)
+{
+ int bind_fd, status;
+ pid_t child;
+
+ if (variant->sandbox == TCP_SANDBOX) {
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_net = ACCESS_ALL,
+ };
+ const struct landlock_net_port_attr tcp_not_restricted_p0 = {
+ .allowed_access = ACCESS_ALL,
+ .port = self->srv0.port,
+ };
+ const struct landlock_net_port_attr tcp_denied_listen_p1 = {
+ .allowed_access = ACCESS_ALL &
+ ~LANDLOCK_ACCESS_NET_LISTEN_TCP,
+ .port = self->srv1.port,
+ };
+ int ruleset_fd;
+
+ ruleset_fd = landlock_create_ruleset(&ruleset_attr,
+ sizeof(ruleset_attr), 0);
+ ASSERT_LE(0, ruleset_fd);
+
+ /* Allows all actions for the first port. */
+ ASSERT_EQ(0,
+ landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
+ &tcp_not_restricted_p0, 0));
+
+ /* Denies listening for the second port. */
+ ASSERT_EQ(0,
+ landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
+ &tcp_denied_listen_p1, 0));
+
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+ }
+
+ if (variant->prot.type != SOCK_STREAM)
+ SKIP(return, "listen(2) is supported only on stream sockets");
+
+ /* Initializes listening socket. */
+ bind_fd = socket_variant(&self->srv0);
+ ASSERT_LE(0, bind_fd);
+ EXPECT_EQ(0, bind_variant(bind_fd, &self->srv0));
+ EXPECT_EQ(0, listen_variant(bind_fd, backlog));
+
+ child = fork();
+ ASSERT_LE(0, child);
+ if (child == 0) {
+ int connect_fd;
+
+ /* Closes listening socket for the child. */
+ EXPECT_EQ(0, close(bind_fd));
+
+ connect_fd = socket_variant(&self->srv1);
+ ASSERT_LE(0, connect_fd);
+ EXPECT_EQ(0, connect_variant(connect_fd, &self->srv0));
+
+ /* Tries to listen on connected socket. */
+ EXPECT_EQ(-EINVAL, listen_variant(connect_fd, backlog));
+
+ EXPECT_EQ(0, close(connect_fd));
+ _exit(_metadata->exit_code);
+ return;
+ }
+
+ EXPECT_EQ(child, waitpid(child, &status, 0));
+ EXPECT_EQ(1, WIFEXITED(status));
+ EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
+
+ EXPECT_EQ(0, close(bind_fd));
+}
+
FIXTURE(ipv4)
{
struct service_fixture srv0, srv1;
--
2.34.1
^ permalink raw reply related [flat|nested] 40+ messages in thread
* Re: [RFC PATCH v2 5/9] selftests/landlock: Test listen on connected socket
2024-08-14 3:01 ` [RFC PATCH v2 5/9] selftests/landlock: Test listen on connected socket Mikhail Ivanov
@ 2024-08-20 13:01 ` Günther Noack
2024-08-20 13:42 ` Mikhail Ivanov
0 siblings, 1 reply; 40+ messages in thread
From: Günther Noack @ 2024-08-20 13:01 UTC (permalink / raw)
To: Mikhail Ivanov
Cc: mic, willemdebruijn.kernel, gnoack3000, linux-security-module,
netdev, netfilter-devel, yusongping, artem.kuzin,
konstantin.meskhidze
On Wed, Aug 14, 2024 at 11:01:47AM +0800, Mikhail Ivanov wrote:
> Test checks that listen(2) doesn't wrongfully return -EACCES instead
> of -EINVAL when trying to listen for an incorrect socket state.
>
> Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
> ---
>
> Changes since v1:
> * Uses 'protocol' fixture instead of 'ipv4_tcp'.
> * Minor fixes.
> ---
> tools/testing/selftests/landlock/net_test.c | 74 +++++++++++++++++++++
> 1 file changed, 74 insertions(+)
>
> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
> index b6fe9bde205f..551891b18b7a 100644
> --- a/tools/testing/selftests/landlock/net_test.c
> +++ b/tools/testing/selftests/landlock/net_test.c
> @@ -926,6 +926,80 @@ TEST_F(protocol, connect_unspec)
> EXPECT_EQ(0, close(bind_fd));
> }
>
> +TEST_F(protocol, listen_on_connected)
> +{
> + int bind_fd, status;
> + pid_t child;
> +
> + if (variant->sandbox == TCP_SANDBOX) {
> + const struct landlock_ruleset_attr ruleset_attr = {
> + .handled_access_net = ACCESS_ALL,
> + };
> + const struct landlock_net_port_attr tcp_not_restricted_p0 = {
> + .allowed_access = ACCESS_ALL,
> + .port = self->srv0.port,
> + };
> + const struct landlock_net_port_attr tcp_denied_listen_p1 = {
> + .allowed_access = ACCESS_ALL &
> + ~LANDLOCK_ACCESS_NET_LISTEN_TCP,
> + .port = self->srv1.port,
> + };
> + int ruleset_fd;
> +
> + ruleset_fd = landlock_create_ruleset(&ruleset_attr,
> + sizeof(ruleset_attr), 0);
> + ASSERT_LE(0, ruleset_fd);
> +
> + /* Allows all actions for the first port. */
> + ASSERT_EQ(0,
> + landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> + &tcp_not_restricted_p0, 0));
> +
> + /* Denies listening for the second port. */
> + ASSERT_EQ(0,
> + landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> + &tcp_denied_listen_p1, 0));
> +
> + enforce_ruleset(_metadata, ruleset_fd);
> + EXPECT_EQ(0, close(ruleset_fd));
> + }
Same remarks as in the previous commit apply here as well:
- The if condition does the same thing, can maybe be deduplicated.
- Can merge ruleset_fd declaration and assignment into one line.
(This happens in a few more tests in later commits as well,
please double check these as well.)
> +
> + if (variant->prot.type != SOCK_STREAM)
> + SKIP(return, "listen(2) is supported only on stream sockets");
> +
> + /* Initializes listening socket. */
> + bind_fd = socket_variant(&self->srv0);
> + ASSERT_LE(0, bind_fd);
> + EXPECT_EQ(0, bind_variant(bind_fd, &self->srv0));
> + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
I believe if bind() or listen() fail here, it does not make sense to continue
the test execution, so ASSERT_EQ would be more appropriate than EXPECT_EQ.
> +
> + child = fork();
> + ASSERT_LE(0, child);
> + if (child == 0) {
> + int connect_fd;
> +
> + /* Closes listening socket for the child. */
> + EXPECT_EQ(0, close(bind_fd));
You don't need to do this from a child process, you can just connect() from the
same process to the listening port. (Since you are not calling accept(), the
server won't pick up the phone on the other end, but that is still enough to
connect successfully.) It would simplify the story of correctly propagating
test exit statuses as well.
> +
> + connect_fd = socket_variant(&self->srv1);
> + ASSERT_LE(0, connect_fd);
> + EXPECT_EQ(0, connect_variant(connect_fd, &self->srv0));
> +
> + /* Tries to listen on connected socket. */
> + EXPECT_EQ(-EINVAL, listen_variant(connect_fd, backlog));
Since this assertion is the actual point of the test,
maybe we could emphasize it a bit more with a comment here?
e.g:
/*
* Checks that we always return EINVAL
* and never accidentally return EACCES, if listen(2) fails.
*/
> +
> + EXPECT_EQ(0, close(connect_fd));
> + _exit(_metadata->exit_code);
> + return;
> + }
> +
> + EXPECT_EQ(child, waitpid(child, &status, 0));
> + EXPECT_EQ(1, WIFEXITED(status));
> + EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
> +
> + EXPECT_EQ(0, close(bind_fd));
> +}
> +
> FIXTURE(ipv4)
> {
> struct service_fixture srv0, srv1;
> --
> 2.34.1
>
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [RFC PATCH v2 5/9] selftests/landlock: Test listen on connected socket
2024-08-20 13:01 ` Günther Noack
@ 2024-08-20 13:42 ` Mikhail Ivanov
0 siblings, 0 replies; 40+ messages in thread
From: Mikhail Ivanov @ 2024-08-20 13:42 UTC (permalink / raw)
To: Günther Noack
Cc: mic, willemdebruijn.kernel, gnoack3000, linux-security-module,
netdev, netfilter-devel, yusongping, artem.kuzin,
konstantin.meskhidze
8/20/2024 4:01 PM, Günther Noack wrote:
> On Wed, Aug 14, 2024 at 11:01:47AM +0800, Mikhail Ivanov wrote:
>> Test checks that listen(2) doesn't wrongfully return -EACCES instead
>> of -EINVAL when trying to listen for an incorrect socket state.
>>
>> Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
>> ---
>>
>> Changes since v1:
>> * Uses 'protocol' fixture instead of 'ipv4_tcp'.
>> * Minor fixes.
>> ---
>> tools/testing/selftests/landlock/net_test.c | 74 +++++++++++++++++++++
>> 1 file changed, 74 insertions(+)
>>
>> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
>> index b6fe9bde205f..551891b18b7a 100644
>> --- a/tools/testing/selftests/landlock/net_test.c
>> +++ b/tools/testing/selftests/landlock/net_test.c
>> @@ -926,6 +926,80 @@ TEST_F(protocol, connect_unspec)
>> EXPECT_EQ(0, close(bind_fd));
>> }
>>
>> +TEST_F(protocol, listen_on_connected)
>> +{
>> + int bind_fd, status;
>> + pid_t child;
>> +
>> + if (variant->sandbox == TCP_SANDBOX) {
>> + const struct landlock_ruleset_attr ruleset_attr = {
>> + .handled_access_net = ACCESS_ALL,
>> + };
>> + const struct landlock_net_port_attr tcp_not_restricted_p0 = {
>> + .allowed_access = ACCESS_ALL,
>> + .port = self->srv0.port,
>> + };
>> + const struct landlock_net_port_attr tcp_denied_listen_p1 = {
>> + .allowed_access = ACCESS_ALL &
>> + ~LANDLOCK_ACCESS_NET_LISTEN_TCP,
>> + .port = self->srv1.port,
>> + };
>> + int ruleset_fd;
>> +
>> + ruleset_fd = landlock_create_ruleset(&ruleset_attr,
>> + sizeof(ruleset_attr), 0);
>> + ASSERT_LE(0, ruleset_fd);
>> +
>> + /* Allows all actions for the first port. */
>> + ASSERT_EQ(0,
>> + landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>> + &tcp_not_restricted_p0, 0));
>> +
>> + /* Denies listening for the second port. */
>> + ASSERT_EQ(0,
>> + landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>> + &tcp_denied_listen_p1, 0));
>> +
>> + enforce_ruleset(_metadata, ruleset_fd);
>> + EXPECT_EQ(0, close(ruleset_fd));
>> + }
>
> Same remarks as in the previous commit apply here as well:
>
> - The if condition does the same thing, can maybe be deduplicated.
> - Can merge ruleset_fd declaration and assignment into one line.
> (This happens in a few more tests in later commits as well,
> please double check these as well.)
Thanks for mentioning! You can check my reply in the previous commit
discussion.
>
>> +
>> + if (variant->prot.type != SOCK_STREAM)
>> + SKIP(return, "listen(2) is supported only on stream sockets");
>> +
>> + /* Initializes listening socket. */
>> + bind_fd = socket_variant(&self->srv0);
>> + ASSERT_LE(0, bind_fd);
>> + EXPECT_EQ(0, bind_variant(bind_fd, &self->srv0));
>> + EXPECT_EQ(0, listen_variant(bind_fd, backlog));
>
> I believe if bind() or listen() fail here, it does not make sense to continue
> the test execution, so ASSERT_EQ would be more appropriate than EXPECT_EQ.
Will be fixed, thanks.
>
>
>> +
>> + child = fork();
>> + ASSERT_LE(0, child);
>> + if (child == 0) {
>> + int connect_fd;
>> +
>> + /* Closes listening socket for the child. */
>> + EXPECT_EQ(0, close(bind_fd));
>
> You don't need to do this from a child process, you can just connect() from the
> same process to the listening port. (Since you are not calling accept(), the
> server won't pick up the phone on the other end, but that is still enough to
> connect successfully.) It would simplify the story of correctly propagating
> test exit statuses as well.
Thanks, I'll fix this.
>
>> +
>> + connect_fd = socket_variant(&self->srv1);
>> + ASSERT_LE(0, connect_fd);
>> + EXPECT_EQ(0, connect_variant(connect_fd, &self->srv0));
>> +
>> + /* Tries to listen on connected socket. */
>> + EXPECT_EQ(-EINVAL, listen_variant(connect_fd, backlog));
>
> Since this assertion is the actual point of the test,
> maybe we could emphasize it a bit more with a comment here?
>
> e.g:
>
> /*
> * Checks that we always return EINVAL
> * and never accidentally return EACCES, if listen(2) fails.
> */
You're right.. current description doesn't give an understanding of why
this test is needed at all. I'll change it.
>
>> +
>> + EXPECT_EQ(0, close(connect_fd));
>> + _exit(_metadata->exit_code);
>> + return;
>> + }
>> +
>> + EXPECT_EQ(child, waitpid(child, &status, 0));
>> + EXPECT_EQ(1, WIFEXITED(status));
>> + EXPECT_EQ(EXIT_SUCCESS, WEXITSTATUS(status));
>> +
>> + EXPECT_EQ(0, close(bind_fd));
>> +}
>> +
>> FIXTURE(ipv4)
>> {
>> struct service_fixture srv0, srv1;
>> --
>> 2.34.1
>>
^ permalink raw reply [flat|nested] 40+ messages in thread
* [RFC PATCH v2 6/9] selftests/landlock: Test listening without explicit bind restriction
2024-08-14 3:01 [RFC PATCH v2 0/9] Support TCP listen access-control Mikhail Ivanov
` (4 preceding siblings ...)
2024-08-14 3:01 ` [RFC PATCH v2 5/9] selftests/landlock: Test listen on connected socket Mikhail Ivanov
@ 2024-08-14 3:01 ` Mikhail Ivanov
2024-08-20 13:02 ` Günther Noack
2024-08-14 3:01 ` [RFC PATCH v2 7/9] selftests/landlock: Test listen on ULP socket without clone method Mikhail Ivanov
` (3 subsequent siblings)
9 siblings, 1 reply; 40+ messages in thread
From: Mikhail Ivanov @ 2024-08-14 3:01 UTC (permalink / raw)
To: mic
Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
netfilter-devel, yusongping, artem.kuzin, konstantin.meskhidze
Test scenarios where listen(2) call without explicit bind(2) is allowed
and forbidden.
Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
---
tools/testing/selftests/landlock/net_test.c | 83 +++++++++++++++++++++
1 file changed, 83 insertions(+)
diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
index 551891b18b7a..92c042349596 100644
--- a/tools/testing/selftests/landlock/net_test.c
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -1851,6 +1851,89 @@ TEST_F(port_specific, bind_connect_zero)
EXPECT_EQ(0, close(bind_fd));
}
+TEST_F(port_specific, listen_without_bind_allowed)
+{
+ if (variant->sandbox == TCP_SANDBOX) {
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
+ LANDLOCK_ACCESS_NET_LISTEN_TCP
+ };
+ const struct landlock_net_port_attr tcp_listen_zero = {
+ .allowed_access = LANDLOCK_ACCESS_NET_LISTEN_TCP,
+ .port = 0,
+ };
+ int ruleset_fd;
+
+ ruleset_fd = landlock_create_ruleset(&ruleset_attr,
+ sizeof(ruleset_attr), 0);
+ ASSERT_LE(0, ruleset_fd);
+
+ /*
+ * Allow listening without explicit bind
+ * (cf. landlock_net_port_attr).
+ */
+ EXPECT_EQ(0,
+ landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
+ &tcp_listen_zero, 0));
+
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+ }
+ int listen_fd, connect_fd;
+ __u64 port;
+
+ listen_fd = socket_variant(&self->srv0);
+ ASSERT_LE(0, listen_fd);
+
+ connect_fd = socket_variant(&self->srv0);
+ ASSERT_LE(0, connect_fd);
+ /*
+ * Allow listen(2) to select a random port for the socket,
+ * since bind(2) wasn't called.
+ */
+ EXPECT_EQ(0, listen_variant(listen_fd, backlog));
+
+ /* Connects on the binded port. */
+ port = get_binded_port(listen_fd, &variant->prot);
+ EXPECT_NE(0, port);
+ set_port(&self->srv0, port);
+ EXPECT_EQ(0, connect_variant(connect_fd, &self->srv0));
+
+ EXPECT_EQ(0, close(connect_fd));
+ EXPECT_EQ(0, close(listen_fd));
+}
+
+TEST_F(port_specific, listen_without_bind_denied)
+{
+ if (variant->sandbox == TCP_SANDBOX) {
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_net = LANDLOCK_ACCESS_NET_LISTEN_TCP
+ };
+ int ruleset_fd;
+
+ ruleset_fd = landlock_create_ruleset(&ruleset_attr,
+ sizeof(ruleset_attr), 0);
+ ASSERT_LE(0, ruleset_fd);
+
+ /* Deny listening. */
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+ }
+ int listen_fd, ret;
+
+ listen_fd = socket_variant(&self->srv0);
+ ASSERT_LE(0, listen_fd);
+
+ /* Checks that listening without explicit binding is prohibited. */
+ ret = listen_variant(listen_fd, backlog);
+ if (is_restricted(&variant->prot, variant->sandbox)) {
+ /* Denied by Landlock. */
+ EXPECT_EQ(-EACCES, ret);
+ } else {
+ EXPECT_EQ(0, ret);
+ }
+}
+
TEST_F(port_specific, port_1023)
{
int bind_fd, connect_fd, ret;
--
2.34.1
^ permalink raw reply related [flat|nested] 40+ messages in thread
* Re: [RFC PATCH v2 6/9] selftests/landlock: Test listening without explicit bind restriction
2024-08-14 3:01 ` [RFC PATCH v2 6/9] selftests/landlock: Test listening without explicit bind restriction Mikhail Ivanov
@ 2024-08-20 13:02 ` Günther Noack
2024-08-20 13:46 ` Mikhail Ivanov
0 siblings, 1 reply; 40+ messages in thread
From: Günther Noack @ 2024-08-20 13:02 UTC (permalink / raw)
To: Mikhail Ivanov
Cc: mic, willemdebruijn.kernel, gnoack3000, linux-security-module,
netdev, netfilter-devel, yusongping, artem.kuzin,
konstantin.meskhidze
On Wed, Aug 14, 2024 at 11:01:48AM +0800, Mikhail Ivanov wrote:
> Test scenarios where listen(2) call without explicit bind(2) is allowed
> and forbidden.
>
> Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
> ---
> tools/testing/selftests/landlock/net_test.c | 83 +++++++++++++++++++++
> 1 file changed, 83 insertions(+)
>
> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
> index 551891b18b7a..92c042349596 100644
> --- a/tools/testing/selftests/landlock/net_test.c
> +++ b/tools/testing/selftests/landlock/net_test.c
> @@ -1851,6 +1851,89 @@ TEST_F(port_specific, bind_connect_zero)
> EXPECT_EQ(0, close(bind_fd));
> }
>
> +TEST_F(port_specific, listen_without_bind_allowed)
> +{
> + if (variant->sandbox == TCP_SANDBOX) {
> + const struct landlock_ruleset_attr ruleset_attr = {
> + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
> + LANDLOCK_ACCESS_NET_LISTEN_TCP
> + };
> + const struct landlock_net_port_attr tcp_listen_zero = {
> + .allowed_access = LANDLOCK_ACCESS_NET_LISTEN_TCP,
> + .port = 0,
> + };
> + int ruleset_fd;
> +
> + ruleset_fd = landlock_create_ruleset(&ruleset_attr,
> + sizeof(ruleset_attr), 0);
> + ASSERT_LE(0, ruleset_fd);
> +
> + /*
> + * Allow listening without explicit bind
> + * (cf. landlock_net_port_attr).
> + */
> + EXPECT_EQ(0,
> + landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
> + &tcp_listen_zero, 0));
> +
> + enforce_ruleset(_metadata, ruleset_fd);
> + EXPECT_EQ(0, close(ruleset_fd));
> + }
> + int listen_fd, connect_fd;
> + __u64 port;
> +
> + listen_fd = socket_variant(&self->srv0);
> + ASSERT_LE(0, listen_fd);
> +
> + connect_fd = socket_variant(&self->srv0);
> + ASSERT_LE(0, connect_fd);
> + /*
> + * Allow listen(2) to select a random port for the socket,
> + * since bind(2) wasn't called.
> + */
> + EXPECT_EQ(0, listen_variant(listen_fd, backlog));
> +
> + /* Connects on the binded port. */
> + port = get_binded_port(listen_fd, &variant->prot);
Please rename "binded" to "bound" when you come across it.
> + EXPECT_NE(0, port);
> + set_port(&self->srv0, port);
> + EXPECT_EQ(0, connect_variant(connect_fd, &self->srv0));
> +
> + EXPECT_EQ(0, close(connect_fd));
> + EXPECT_EQ(0, close(listen_fd));
> +}
> +
> +TEST_F(port_specific, listen_without_bind_denied)
> +{
> + if (variant->sandbox == TCP_SANDBOX) {
> + const struct landlock_ruleset_attr ruleset_attr = {
> + .handled_access_net = LANDLOCK_ACCESS_NET_LISTEN_TCP
> + };
> + int ruleset_fd;
> +
> + ruleset_fd = landlock_create_ruleset(&ruleset_attr,
> + sizeof(ruleset_attr), 0);
> + ASSERT_LE(0, ruleset_fd);
> +
> + /* Deny listening. */
> + enforce_ruleset(_metadata, ruleset_fd);
> + EXPECT_EQ(0, close(ruleset_fd));
> + }
> + int listen_fd, ret;
> +
> + listen_fd = socket_variant(&self->srv0);
> + ASSERT_LE(0, listen_fd);
> +
> + /* Checks that listening without explicit binding is prohibited. */
> + ret = listen_variant(listen_fd, backlog);
> + if (is_restricted(&variant->prot, variant->sandbox)) {
> + /* Denied by Landlock. */
> + EXPECT_EQ(-EACCES, ret);
> + } else {
> + EXPECT_EQ(0, ret);
> + }
> +}
> +
> TEST_F(port_specific, port_1023)
> {
> int bind_fd, connect_fd, ret;
> --
> 2.34.1
>
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [RFC PATCH v2 6/9] selftests/landlock: Test listening without explicit bind restriction
2024-08-20 13:02 ` Günther Noack
@ 2024-08-20 13:46 ` Mikhail Ivanov
2024-08-21 11:52 ` Mikhail Ivanov
0 siblings, 1 reply; 40+ messages in thread
From: Mikhail Ivanov @ 2024-08-20 13:46 UTC (permalink / raw)
To: Günther Noack
Cc: mic, willemdebruijn.kernel, gnoack3000, linux-security-module,
netdev, netfilter-devel, yusongping, artem.kuzin,
konstantin.meskhidze
8/20/2024 4:02 PM, Günther Noack wrote:
> On Wed, Aug 14, 2024 at 11:01:48AM +0800, Mikhail Ivanov wrote:
>> Test scenarios where listen(2) call without explicit bind(2) is allowed
>> and forbidden.
>>
>> Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
>> ---
>> tools/testing/selftests/landlock/net_test.c | 83 +++++++++++++++++++++
>> 1 file changed, 83 insertions(+)
>>
>> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
>> index 551891b18b7a..92c042349596 100644
>> --- a/tools/testing/selftests/landlock/net_test.c
>> +++ b/tools/testing/selftests/landlock/net_test.c
>> @@ -1851,6 +1851,89 @@ TEST_F(port_specific, bind_connect_zero)
>> EXPECT_EQ(0, close(bind_fd));
>> }
>>
>> +TEST_F(port_specific, listen_without_bind_allowed)
>> +{
>> + if (variant->sandbox == TCP_SANDBOX) {
>> + const struct landlock_ruleset_attr ruleset_attr = {
>> + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
>> + LANDLOCK_ACCESS_NET_LISTEN_TCP
>> + };
>> + const struct landlock_net_port_attr tcp_listen_zero = {
>> + .allowed_access = LANDLOCK_ACCESS_NET_LISTEN_TCP,
>> + .port = 0,
>> + };
>> + int ruleset_fd;
>> +
>> + ruleset_fd = landlock_create_ruleset(&ruleset_attr,
>> + sizeof(ruleset_attr), 0);
>> + ASSERT_LE(0, ruleset_fd);
>> +
>> + /*
>> + * Allow listening without explicit bind
>> + * (cf. landlock_net_port_attr).
>> + */
>> + EXPECT_EQ(0,
>> + landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>> + &tcp_listen_zero, 0));
>> +
>> + enforce_ruleset(_metadata, ruleset_fd);
>> + EXPECT_EQ(0, close(ruleset_fd));
>> + }
>> + int listen_fd, connect_fd;
>> + __u64 port;
>> +
>> + listen_fd = socket_variant(&self->srv0);
>> + ASSERT_LE(0, listen_fd);
>> +
>> + connect_fd = socket_variant(&self->srv0);
>> + ASSERT_LE(0, connect_fd);
>> + /*
>> + * Allow listen(2) to select a random port for the socket,
>> + * since bind(2) wasn't called.
>> + */
>> + EXPECT_EQ(0, listen_variant(listen_fd, backlog));
>> +
>> + /* Connects on the binded port. */
>> + port = get_binded_port(listen_fd, &variant->prot);
>
> Please rename "binded" to "bound" when you come across it.
Can I do such refactoring in the 3/9 patch?
>
>
>> + EXPECT_NE(0, port);
>> + set_port(&self->srv0, port);
>> + EXPECT_EQ(0, connect_variant(connect_fd, &self->srv0));
>> +
>> + EXPECT_EQ(0, close(connect_fd));
>> + EXPECT_EQ(0, close(listen_fd));
>> +}
>> +
>> +TEST_F(port_specific, listen_without_bind_denied)
>> +{
>> + if (variant->sandbox == TCP_SANDBOX) {
>> + const struct landlock_ruleset_attr ruleset_attr = {
>> + .handled_access_net = LANDLOCK_ACCESS_NET_LISTEN_TCP
>> + };
>> + int ruleset_fd;
>> +
>> + ruleset_fd = landlock_create_ruleset(&ruleset_attr,
>> + sizeof(ruleset_attr), 0);
>> + ASSERT_LE(0, ruleset_fd);
>> +
>> + /* Deny listening. */
>> + enforce_ruleset(_metadata, ruleset_fd);
>> + EXPECT_EQ(0, close(ruleset_fd));
>> + }
>> + int listen_fd, ret;
>> +
>> + listen_fd = socket_variant(&self->srv0);
>> + ASSERT_LE(0, listen_fd);
>> +
>> + /* Checks that listening without explicit binding is prohibited. */
>> + ret = listen_variant(listen_fd, backlog);
>> + if (is_restricted(&variant->prot, variant->sandbox)) {
>> + /* Denied by Landlock. */
>> + EXPECT_EQ(-EACCES, ret);
>> + } else {
>> + EXPECT_EQ(0, ret);
>> + }
>> +}
>> +
>> TEST_F(port_specific, port_1023)
>> {
>> int bind_fd, connect_fd, ret;
>> --
>> 2.34.1
>>
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [RFC PATCH v2 6/9] selftests/landlock: Test listening without explicit bind restriction
2024-08-20 13:46 ` Mikhail Ivanov
@ 2024-08-21 11:52 ` Mikhail Ivanov
0 siblings, 0 replies; 40+ messages in thread
From: Mikhail Ivanov @ 2024-08-21 11:52 UTC (permalink / raw)
To: Günther Noack
Cc: mic, willemdebruijn.kernel, gnoack3000, linux-security-module,
netdev, netfilter-devel, yusongping, artem.kuzin,
konstantin.meskhidze
8/20/2024 4:46 PM, Mikhail Ivanov wrote:
> 8/20/2024 4:02 PM, Günther Noack wrote:
>> On Wed, Aug 14, 2024 at 11:01:48AM +0800, Mikhail Ivanov wrote:
>>> Test scenarios where listen(2) call without explicit bind(2) is allowed
>>> and forbidden.
>>>
>>> Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
>>> ---
>>> tools/testing/selftests/landlock/net_test.c | 83 +++++++++++++++++++++
>>> 1 file changed, 83 insertions(+)
>>>
>>> diff --git a/tools/testing/selftests/landlock/net_test.c
>>> b/tools/testing/selftests/landlock/net_test.c
>>> index 551891b18b7a..92c042349596 100644
>>> --- a/tools/testing/selftests/landlock/net_test.c
>>> +++ b/tools/testing/selftests/landlock/net_test.c
>>> @@ -1851,6 +1851,89 @@ TEST_F(port_specific, bind_connect_zero)
>>> EXPECT_EQ(0, close(bind_fd));
>>> }
>>> +TEST_F(port_specific, listen_without_bind_allowed)
>>> +{
>>> + if (variant->sandbox == TCP_SANDBOX) {
>>> + const struct landlock_ruleset_attr ruleset_attr = {
>>> + .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
>>> + LANDLOCK_ACCESS_NET_LISTEN_TCP
>>> + };
>>> + const struct landlock_net_port_attr tcp_listen_zero = {
>>> + .allowed_access = LANDLOCK_ACCESS_NET_LISTEN_TCP,
>>> + .port = 0,
>>> + };
>>> + int ruleset_fd;
>>> +
>>> + ruleset_fd = landlock_create_ruleset(&ruleset_attr,
>>> + sizeof(ruleset_attr), 0);
>>> + ASSERT_LE(0, ruleset_fd);
>>> +
>>> + /*
>>> + * Allow listening without explicit bind
>>> + * (cf. landlock_net_port_attr).
>>> + */
>>> + EXPECT_EQ(0,
>>> + landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
>>> + &tcp_listen_zero, 0));
>>> +
>>> + enforce_ruleset(_metadata, ruleset_fd);
>>> + EXPECT_EQ(0, close(ruleset_fd));
>>> + }
>>> + int listen_fd, connect_fd;
>>> + __u64 port;
>>> +
>>> + listen_fd = socket_variant(&self->srv0);
>>> + ASSERT_LE(0, listen_fd);
>>> +
>>> + connect_fd = socket_variant(&self->srv0);
>>> + ASSERT_LE(0, connect_fd);
>>> + /*
>>> + * Allow listen(2) to select a random port for the socket,
>>> + * since bind(2) wasn't called.
>>> + */
>>> + EXPECT_EQ(0, listen_variant(listen_fd, backlog));
>>> +
>>> + /* Connects on the binded port. */
>>> + port = get_binded_port(listen_fd, &variant->prot);
>>
>> Please rename "binded" to "bound" when you come across it.
>
> Can I do such refactoring in the 3/9 patch?
I mean, can I replace all "binded" occurrences in net_test in 3/9 patch?
>
>>
>>
>>> + EXPECT_NE(0, port);
>>> + set_port(&self->srv0, port);
>>> + EXPECT_EQ(0, connect_variant(connect_fd, &self->srv0));
>>> +
>>> + EXPECT_EQ(0, close(connect_fd));
>>> + EXPECT_EQ(0, close(listen_fd));
>>> +}
>>> +
>>> +TEST_F(port_specific, listen_without_bind_denied)
>>> +{
>>> + if (variant->sandbox == TCP_SANDBOX) {
>>> + const struct landlock_ruleset_attr ruleset_attr = {
>>> + .handled_access_net = LANDLOCK_ACCESS_NET_LISTEN_TCP
>>> + };
>>> + int ruleset_fd;
>>> +
>>> + ruleset_fd = landlock_create_ruleset(&ruleset_attr,
>>> + sizeof(ruleset_attr), 0);
>>> + ASSERT_LE(0, ruleset_fd);
>>> +
>>> + /* Deny listening. */
>>> + enforce_ruleset(_metadata, ruleset_fd);
>>> + EXPECT_EQ(0, close(ruleset_fd));
>>> + }
>>> + int listen_fd, ret;
>>> +
>>> + listen_fd = socket_variant(&self->srv0);
>>> + ASSERT_LE(0, listen_fd);
>>> +
>>> + /* Checks that listening without explicit binding is prohibited. */
>>> + ret = listen_variant(listen_fd, backlog);
>>> + if (is_restricted(&variant->prot, variant->sandbox)) {
>>> + /* Denied by Landlock. */
>>> + EXPECT_EQ(-EACCES, ret);
>>> + } else {
>>> + EXPECT_EQ(0, ret);
>>> + }
>>> +}
>>> +
>>> TEST_F(port_specific, port_1023)
>>> {
>>> int bind_fd, connect_fd, ret;
>>> --
>>> 2.34.1
>>>
^ permalink raw reply [flat|nested] 40+ messages in thread
* [RFC PATCH v2 7/9] selftests/landlock: Test listen on ULP socket without clone method
2024-08-14 3:01 [RFC PATCH v2 0/9] Support TCP listen access-control Mikhail Ivanov
` (5 preceding siblings ...)
2024-08-14 3:01 ` [RFC PATCH v2 6/9] selftests/landlock: Test listening without explicit bind restriction Mikhail Ivanov
@ 2024-08-14 3:01 ` Mikhail Ivanov
2024-08-14 3:01 ` [RFC PATCH v2 8/9] selftests/landlock: Test changing socket backlog with listen(2) Mikhail Ivanov
` (2 subsequent siblings)
9 siblings, 0 replies; 40+ messages in thread
From: Mikhail Ivanov @ 2024-08-14 3:01 UTC (permalink / raw)
To: mic
Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
netfilter-devel, yusongping, artem.kuzin, konstantin.meskhidze
Test checks that listen(2) doesn't wrongfully return -EACCES instead of
-EINVAL when trying to listen on a socket which is set to ULP that doesn't
have clone method in inet_csk(sk)->icsk_ulp_ops (espintcp).
Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
---
Changes since v1:
* Uses 'protocol' fixture instead of 'ipv4_tcp'.
* Adds missing CONFIG_INET_ESP dependency in config.
---
tools/testing/selftests/landlock/config | 4 ++
tools/testing/selftests/landlock/net_test.c | 50 +++++++++++++++++++++
2 files changed, 54 insertions(+)
diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config
index 29af19c4e9f9..72de73d1ee4c 100644
--- a/tools/testing/selftests/landlock/config
+++ b/tools/testing/selftests/landlock/config
@@ -1,7 +1,11 @@
CONFIG_CGROUPS=y
CONFIG_CGROUP_SCHED=y
CONFIG_INET=y
+CONFIG_INET_ESPINTCP=y
+CONFIG_INET_ESP=y
CONFIG_IPV6=y
+CONFIG_INET6_ESPINTCP=y
+CONFIG_INET6_ESP=y
CONFIG_KEYS=y
CONFIG_NET=y
CONFIG_NET_NS=y
diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
index 92c042349596..6831d8a2e9aa 100644
--- a/tools/testing/selftests/landlock/net_test.c
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -12,6 +12,7 @@
#include <fcntl.h>
#include <linux/landlock.h>
#include <linux/in.h>
+#include <linux/tcp.h>
#include <sched.h>
#include <stdint.h>
#include <string.h>
@@ -1000,6 +1001,55 @@ TEST_F(protocol, listen_on_connected)
EXPECT_EQ(0, close(bind_fd));
}
+TEST_F(protocol, espintcp_listen)
+{
+ int listen_fd;
+ int domain, type;
+
+ if (variant->sandbox == TCP_SANDBOX) {
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_net = ACCESS_ALL,
+ };
+ const struct landlock_net_port_attr tcp_denied_listen_p0 = {
+ .allowed_access = ACCESS_ALL &
+ ~LANDLOCK_ACCESS_NET_LISTEN_TCP,
+ .port = self->srv0.port,
+ };
+ int ruleset_fd;
+
+ ruleset_fd = landlock_create_ruleset(&ruleset_attr,
+ sizeof(ruleset_attr), 0);
+ ASSERT_LE(0, ruleset_fd);
+
+ /* Deny listen. */
+ ASSERT_EQ(0,
+ landlock_add_rule(ruleset_fd, LANDLOCK_RULE_NET_PORT,
+ &tcp_denied_listen_p0, 0));
+
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+ }
+
+ domain = variant->prot.domain;
+ type = variant->prot.type;
+
+ if (!((domain == AF_INET || domain == AF_INET6) && type == SOCK_STREAM))
+ SKIP(return, "espintcp is available only for TCP socket");
+
+ listen_fd = socket_variant(&self->srv0);
+ ASSERT_LE(0, listen_fd);
+
+ /* Set espintcp ULP. */
+ EXPECT_EQ(0, setsockopt(listen_fd, IPPROTO_TCP, TCP_ULP, "espintcp",
+ sizeof("espintcp")));
+
+ EXPECT_EQ(0, bind_variant(listen_fd, &self->srv0));
+
+ /* Espintcp ULP doesn't have clone method, so listen is denied. */
+ EXPECT_EQ(-EINVAL, listen_variant(listen_fd, backlog));
+ EXPECT_EQ(0, close(listen_fd));
+}
+
FIXTURE(ipv4)
{
struct service_fixture srv0, srv1;
--
2.34.1
^ permalink raw reply related [flat|nested] 40+ messages in thread
* [RFC PATCH v2 8/9] selftests/landlock: Test changing socket backlog with listen(2)
2024-08-14 3:01 [RFC PATCH v2 0/9] Support TCP listen access-control Mikhail Ivanov
` (6 preceding siblings ...)
2024-08-14 3:01 ` [RFC PATCH v2 7/9] selftests/landlock: Test listen on ULP socket without clone method Mikhail Ivanov
@ 2024-08-14 3:01 ` Mikhail Ivanov
2024-10-05 16:57 ` Günther Noack
2024-08-14 3:01 ` [RFC PATCH v2 9/9] samples/landlock: Support LANDLOCK_ACCESS_NET_LISTEN Mikhail Ivanov
2024-08-20 13:11 ` [RFC PATCH v2 0/9] Support TCP listen access-control Günther Noack
9 siblings, 1 reply; 40+ messages in thread
From: Mikhail Ivanov @ 2024-08-14 3:01 UTC (permalink / raw)
To: mic
Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
netfilter-devel, yusongping, artem.kuzin, konstantin.meskhidze
listen(2) can be used to change length of the pending connections queue
of the listening socket. Such scenario shouldn't be restricted by Landlock
since socket doesn't change its state.
* Implement test that validates this case.
Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
---
tools/testing/selftests/landlock/net_test.c | 26 +++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
index 6831d8a2e9aa..dafc433a0068 100644
--- a/tools/testing/selftests/landlock/net_test.c
+++ b/tools/testing/selftests/landlock/net_test.c
@@ -1768,6 +1768,32 @@ TEST_F(ipv4_tcp, with_fs)
EXPECT_EQ(-EACCES, bind_variant(bind_fd, &self->srv1));
}
+TEST_F(ipv4_tcp, double_listen)
+{
+ const struct landlock_ruleset_attr ruleset_attr = {
+ .handled_access_net = LANDLOCK_ACCESS_NET_LISTEN_TCP,
+ };
+ int ruleset_fd;
+ int listen_fd;
+
+ listen_fd = socket_variant(&self->srv0);
+ ASSERT_LE(0, listen_fd);
+
+ EXPECT_EQ(0, bind_variant(listen_fd, &self->srv0));
+ EXPECT_EQ(0, listen_variant(listen_fd, backlog));
+
+ ruleset_fd =
+ landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+ ASSERT_LE(0, ruleset_fd);
+
+ /* Denies listen. */
+ enforce_ruleset(_metadata, ruleset_fd);
+ EXPECT_EQ(0, close(ruleset_fd));
+
+ /* Tries to change backlog value of listening socket. */
+ EXPECT_EQ(0, listen_variant(listen_fd, backlog + 1));
+}
+
FIXTURE(port_specific)
{
struct service_fixture srv0;
--
2.34.1
^ permalink raw reply related [flat|nested] 40+ messages in thread
* Re: [RFC PATCH v2 8/9] selftests/landlock: Test changing socket backlog with listen(2)
2024-08-14 3:01 ` [RFC PATCH v2 8/9] selftests/landlock: Test changing socket backlog with listen(2) Mikhail Ivanov
@ 2024-10-05 16:57 ` Günther Noack
2024-10-05 17:29 ` Mikhail Ivanov
0 siblings, 1 reply; 40+ messages in thread
From: Günther Noack @ 2024-10-05 16:57 UTC (permalink / raw)
To: Mikhail Ivanov
Cc: mic, willemdebruijn.kernel, linux-security-module, netdev,
netfilter-devel, yusongping, artem.kuzin, konstantin.meskhidze
On Wed, Aug 14, 2024 at 11:01:50AM +0800, Mikhail Ivanov wrote:
> listen(2) can be used to change length of the pending connections queue
> of the listening socket. Such scenario shouldn't be restricted by Landlock
> since socket doesn't change its state.
Yes, this behavior makes sense to me as well. 👍 __inet_listen_sk()
only changes sk->sk_max_ack_backlog when listen() gets called a second
time.
> * Implement test that validates this case.
>
> Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
> ---
> tools/testing/selftests/landlock/net_test.c | 26 +++++++++++++++++++++
> 1 file changed, 26 insertions(+)
>
> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
> index 6831d8a2e9aa..dafc433a0068 100644
> --- a/tools/testing/selftests/landlock/net_test.c
> +++ b/tools/testing/selftests/landlock/net_test.c
> @@ -1768,6 +1768,32 @@ TEST_F(ipv4_tcp, with_fs)
> EXPECT_EQ(-EACCES, bind_variant(bind_fd, &self->srv1));
> }
>
> +TEST_F(ipv4_tcp, double_listen)
> +{
> + const struct landlock_ruleset_attr ruleset_attr = {
> + .handled_access_net = LANDLOCK_ACCESS_NET_LISTEN_TCP,
> + };
> + int ruleset_fd;
> + int listen_fd;
> +
> + listen_fd = socket_variant(&self->srv0);
> + ASSERT_LE(0, listen_fd);
> +
> + EXPECT_EQ(0, bind_variant(listen_fd, &self->srv0));
> + EXPECT_EQ(0, listen_variant(listen_fd, backlog));
> +
> + ruleset_fd =
> + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
> + ASSERT_LE(0, ruleset_fd);
> +
> + /* Denies listen. */
> + enforce_ruleset(_metadata, ruleset_fd);
> + EXPECT_EQ(0, close(ruleset_fd));
> +
> + /* Tries to change backlog value of listening socket. */
> + EXPECT_EQ(0, listen_variant(listen_fd, backlog + 1));
For test clarity: Without reading the commit message, I believe it
might not be obvious to the reader *why* the second listen() is
supposed to work. This might be worth a comment.
> +}
> +
> FIXTURE(port_specific)
> {
> struct service_fixture srv0;
> --
> 2.34.1
>
Reviewed-by: Günther Noack <gnoack3000@gmail.com>
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [RFC PATCH v2 8/9] selftests/landlock: Test changing socket backlog with listen(2)
2024-10-05 16:57 ` Günther Noack
@ 2024-10-05 17:29 ` Mikhail Ivanov
0 siblings, 0 replies; 40+ messages in thread
From: Mikhail Ivanov @ 2024-10-05 17:29 UTC (permalink / raw)
To: Günther Noack
Cc: mic, willemdebruijn.kernel, linux-security-module, netdev,
netfilter-devel, yusongping, artem.kuzin, konstantin.meskhidze
On 10/5/2024 7:57 PM, Günther Noack wrote:
> On Wed, Aug 14, 2024 at 11:01:50AM +0800, Mikhail Ivanov wrote:
>> listen(2) can be used to change length of the pending connections queue
>> of the listening socket. Such scenario shouldn't be restricted by Landlock
>> since socket doesn't change its state.
>
> Yes, this behavior makes sense to me as well. 👍 __inet_listen_sk()
> only changes sk->sk_max_ack_backlog when listen() gets called a second
> time.
>
>> * Implement test that validates this case.
>>
>> Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
>> ---
>> tools/testing/selftests/landlock/net_test.c | 26 +++++++++++++++++++++
>> 1 file changed, 26 insertions(+)
>>
>> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
>> index 6831d8a2e9aa..dafc433a0068 100644
>> --- a/tools/testing/selftests/landlock/net_test.c
>> +++ b/tools/testing/selftests/landlock/net_test.c
>> @@ -1768,6 +1768,32 @@ TEST_F(ipv4_tcp, with_fs)
>> EXPECT_EQ(-EACCES, bind_variant(bind_fd, &self->srv1));
>> }
>>
>> +TEST_F(ipv4_tcp, double_listen)
>> +{
>> + const struct landlock_ruleset_attr ruleset_attr = {
>> + .handled_access_net = LANDLOCK_ACCESS_NET_LISTEN_TCP,
>> + };
>> + int ruleset_fd;
>> + int listen_fd;
>> +
>> + listen_fd = socket_variant(&self->srv0);
>> + ASSERT_LE(0, listen_fd);
>> +
>> + EXPECT_EQ(0, bind_variant(listen_fd, &self->srv0));
>> + EXPECT_EQ(0, listen_variant(listen_fd, backlog));
>> +
>> + ruleset_fd =
>> + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
>> + ASSERT_LE(0, ruleset_fd);
>> +
>> + /* Denies listen. */
>> + enforce_ruleset(_metadata, ruleset_fd);
>> + EXPECT_EQ(0, close(ruleset_fd));
>> +
>> + /* Tries to change backlog value of listening socket. */
>> + EXPECT_EQ(0, listen_variant(listen_fd, backlog + 1));
>
> For test clarity: Without reading the commit message, I believe it
> might not be obvious to the reader *why* the second listen() is
> supposed to work. This might be worth a comment.
Ofc, thanks!
>
>> +}
>> +
>> FIXTURE(port_specific)
>> {
>> struct service_fixture srv0;
>> --
>> 2.34.1
>>
>
> Reviewed-by: Günther Noack <gnoack3000@gmail.com>
^ permalink raw reply [flat|nested] 40+ messages in thread
* [RFC PATCH v2 9/9] samples/landlock: Support LANDLOCK_ACCESS_NET_LISTEN
2024-08-14 3:01 [RFC PATCH v2 0/9] Support TCP listen access-control Mikhail Ivanov
` (7 preceding siblings ...)
2024-08-14 3:01 ` [RFC PATCH v2 8/9] selftests/landlock: Test changing socket backlog with listen(2) Mikhail Ivanov
@ 2024-08-14 3:01 ` Mikhail Ivanov
2024-10-05 16:57 ` Günther Noack
2024-08-20 13:11 ` [RFC PATCH v2 0/9] Support TCP listen access-control Günther Noack
9 siblings, 1 reply; 40+ messages in thread
From: Mikhail Ivanov @ 2024-08-14 3:01 UTC (permalink / raw)
To: mic
Cc: willemdebruijn.kernel, gnoack3000, linux-security-module, netdev,
netfilter-devel, yusongping, artem.kuzin, konstantin.meskhidze
Extend sample with TCP listen control logic.
Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
---
samples/landlock/sandboxer.c | 31 ++++++++++++++++++++++++++-----
1 file changed, 26 insertions(+), 5 deletions(-)
diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
index e8223c3e781a..3f50cb3f8039 100644
--- a/samples/landlock/sandboxer.c
+++ b/samples/landlock/sandboxer.c
@@ -55,6 +55,7 @@ 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_TCP_LISTEN_NAME "LL_TCP_LISTEN"
#define ENV_DELIMITER ":"
static int parse_path(char *env_path, const char ***const path_list)
@@ -208,7 +209,7 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
/* clang-format on */
-#define LANDLOCK_ABI_LAST 5
+#define LANDLOCK_ABI_LAST 6
int main(const int argc, char *const argv[], char *const *const envp)
{
@@ -222,15 +223,16 @@ int main(const int argc, char *const argv[], char *const *const envp)
struct landlock_ruleset_attr ruleset_attr = {
.handled_access_fs = access_fs_rw,
.handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
- LANDLOCK_ACCESS_NET_CONNECT_TCP,
+ LANDLOCK_ACCESS_NET_CONNECT_TCP |
+ LANDLOCK_ACCESS_NET_LISTEN_TCP,
};
if (argc < 2) {
fprintf(stderr,
- "usage: %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\"%s "
+ "usage: %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\"%s "
"<cmd> [args]...\n\n",
ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME,
- ENV_TCP_CONNECT_NAME, argv[0]);
+ ENV_TCP_CONNECT_NAME, ENV_TCP_LISTEN_NAME, argv[0]);
fprintf(stderr,
"Execute a command in a restricted environment.\n\n");
fprintf(stderr,
@@ -251,15 +253,19 @@ int main(const int argc, char *const argv[], char *const *const envp)
fprintf(stderr,
"* %s: list of ports allowed to connect (client).\n",
ENV_TCP_CONNECT_NAME);
+ fprintf(stderr,
+ "* %s: list of ports allowed to listen (server).\n",
+ ENV_TCP_LISTEN_NAME);
fprintf(stderr,
"\nexample:\n"
"%s=\"${PATH}:/lib:/usr:/proc:/etc:/dev/urandom\" "
"%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
"%s=\"9418\" "
"%s=\"80:443\" "
+ "%s=\"9418\" "
"%s bash -i\n\n",
ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME,
- ENV_TCP_CONNECT_NAME, argv[0]);
+ ENV_TCP_CONNECT_NAME, ENV_TCP_LISTEN_NAME, argv[0]);
fprintf(stderr,
"This sandboxer can use Landlock features "
"up to ABI version %d.\n",
@@ -326,6 +332,11 @@ int main(const int argc, char *const argv[], char *const *const envp)
case 4:
/* Removes LANDLOCK_ACCESS_FS_IOCTL_DEV for ABI < 5 */
ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_IOCTL_DEV;
+ __attribute__((fallthrough));
+ case 5:
+ /* Removes LANDLOCK_ACCESS_NET_LISTEN support for ABI < 6 */
+ ruleset_attr.handled_access_net &=
+ ~(LANDLOCK_ACCESS_NET_LISTEN_TCP);
fprintf(stderr,
"Hint: You should update the running kernel "
@@ -357,6 +368,12 @@ int main(const int argc, char *const argv[], char *const *const envp)
ruleset_attr.handled_access_net &=
~LANDLOCK_ACCESS_NET_CONNECT_TCP;
}
+ /* Removes listen access attribute if not supported by a user. */
+ env_port_name = getenv(ENV_TCP_LISTEN_NAME);
+ if (!env_port_name) {
+ ruleset_attr.handled_access_net &=
+ ~LANDLOCK_ACCESS_NET_LISTEN_TCP;
+ }
ruleset_fd =
landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
@@ -380,6 +397,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_net(ENV_TCP_LISTEN_NAME, ruleset_fd,
+ LANDLOCK_ACCESS_NET_LISTEN_TCP)) {
+ goto err_close_ruleset;
+ }
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
perror("Failed to restrict privileges");
--
2.34.1
^ permalink raw reply related [flat|nested] 40+ messages in thread
* Re: [RFC PATCH v2 9/9] samples/landlock: Support LANDLOCK_ACCESS_NET_LISTEN
2024-08-14 3:01 ` [RFC PATCH v2 9/9] samples/landlock: Support LANDLOCK_ACCESS_NET_LISTEN Mikhail Ivanov
@ 2024-10-05 16:57 ` Günther Noack
2024-10-05 17:30 ` Mikhail Ivanov
0 siblings, 1 reply; 40+ messages in thread
From: Günther Noack @ 2024-10-05 16:57 UTC (permalink / raw)
To: Mikhail Ivanov
Cc: mic, willemdebruijn.kernel, linux-security-module, netdev,
netfilter-devel, yusongping, artem.kuzin, konstantin.meskhidze
On Wed, Aug 14, 2024 at 11:01:51AM +0800, Mikhail Ivanov wrote:
> Extend sample with TCP listen control logic.
>
> Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
> ---
> samples/landlock/sandboxer.c | 31 ++++++++++++++++++++++++++-----
> 1 file changed, 26 insertions(+), 5 deletions(-)
>
> diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
> index e8223c3e781a..3f50cb3f8039 100644
> --- a/samples/landlock/sandboxer.c
> +++ b/samples/landlock/sandboxer.c
> @@ -55,6 +55,7 @@ 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_TCP_LISTEN_NAME "LL_TCP_LISTEN"
> #define ENV_DELIMITER ":"
>
> static int parse_path(char *env_path, const char ***const path_list)
> @@ -208,7 +209,7 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
>
> /* clang-format on */
>
> -#define LANDLOCK_ABI_LAST 5
> +#define LANDLOCK_ABI_LAST 6
>
> int main(const int argc, char *const argv[], char *const *const envp)
> {
> @@ -222,15 +223,16 @@ int main(const int argc, char *const argv[], char *const *const envp)
> struct landlock_ruleset_attr ruleset_attr = {
> .handled_access_fs = access_fs_rw,
> .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
> + LANDLOCK_ACCESS_NET_CONNECT_TCP |
> + LANDLOCK_ACCESS_NET_LISTEN_TCP,
> };
>
> if (argc < 2) {
> fprintf(stderr,
> - "usage: %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\"%s "
> + "usage: %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\"%s "
> "<cmd> [args]...\n\n",
> ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME,
> - ENV_TCP_CONNECT_NAME, argv[0]);
> + ENV_TCP_CONNECT_NAME, ENV_TCP_LISTEN_NAME, argv[0]);
> fprintf(stderr,
> "Execute a command in a restricted environment.\n\n");
> fprintf(stderr,
> @@ -251,15 +253,19 @@ int main(const int argc, char *const argv[], char *const *const envp)
> fprintf(stderr,
> "* %s: list of ports allowed to connect (client).\n",
> ENV_TCP_CONNECT_NAME);
> + fprintf(stderr,
> + "* %s: list of ports allowed to listen (server).\n",
> + ENV_TCP_LISTEN_NAME);
> fprintf(stderr,
> "\nexample:\n"
> "%s=\"${PATH}:/lib:/usr:/proc:/etc:/dev/urandom\" "
> "%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
> "%s=\"9418\" "
> "%s=\"80:443\" "
> + "%s=\"9418\" "
> "%s bash -i\n\n",
> ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME,
> - ENV_TCP_CONNECT_NAME, argv[0]);
> + ENV_TCP_CONNECT_NAME, ENV_TCP_LISTEN_NAME, argv[0]);
> fprintf(stderr,
> "This sandboxer can use Landlock features "
> "up to ABI version %d.\n",
> @@ -326,6 +332,11 @@ int main(const int argc, char *const argv[], char *const *const envp)
> case 4:
> /* Removes LANDLOCK_ACCESS_FS_IOCTL_DEV for ABI < 5 */
> ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_IOCTL_DEV;
> + __attribute__((fallthrough));
> + case 5:
> + /* Removes LANDLOCK_ACCESS_NET_LISTEN support for ABI < 6 */
> + ruleset_attr.handled_access_net &=
> + ~(LANDLOCK_ACCESS_NET_LISTEN_TCP);
(same remark as on other patch set)
ABI version has shifted by one in the meantime.
>
> fprintf(stderr,
> "Hint: You should update the running kernel "
> @@ -357,6 +368,12 @@ int main(const int argc, char *const argv[], char *const *const envp)
> ruleset_attr.handled_access_net &=
> ~LANDLOCK_ACCESS_NET_CONNECT_TCP;
> }
> + /* Removes listen access attribute if not supported by a user. */
(also same remark as on other patch set)
Please s/supported/requested/, for consistency.
> + env_port_name = getenv(ENV_TCP_LISTEN_NAME);
> + if (!env_port_name) {
> + ruleset_attr.handled_access_net &=
> + ~LANDLOCK_ACCESS_NET_LISTEN_TCP;
> + }
>
> ruleset_fd =
> landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
> @@ -380,6 +397,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_net(ENV_TCP_LISTEN_NAME, ruleset_fd,
> + LANDLOCK_ACCESS_NET_LISTEN_TCP)) {
> + goto err_close_ruleset;
> + }
>
> if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
> perror("Failed to restrict privileges");
> --
> 2.34.1
>
Reviewed-by: Günther Noack <gnoack3000@gmail.com>
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [RFC PATCH v2 9/9] samples/landlock: Support LANDLOCK_ACCESS_NET_LISTEN
2024-10-05 16:57 ` Günther Noack
@ 2024-10-05 17:30 ` Mikhail Ivanov
0 siblings, 0 replies; 40+ messages in thread
From: Mikhail Ivanov @ 2024-10-05 17:30 UTC (permalink / raw)
To: Günther Noack
Cc: mic, willemdebruijn.kernel, linux-security-module, netdev,
netfilter-devel, yusongping, artem.kuzin, konstantin.meskhidze
On 10/5/2024 7:57 PM, Günther Noack wrote:
> On Wed, Aug 14, 2024 at 11:01:51AM +0800, Mikhail Ivanov wrote:
>> Extend sample with TCP listen control logic.
>>
>> Signed-off-by: Mikhail Ivanov <ivanov.mikhail1@huawei-partners.com>
>> ---
>> samples/landlock/sandboxer.c | 31 ++++++++++++++++++++++++++-----
>> 1 file changed, 26 insertions(+), 5 deletions(-)
>>
>> diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c
>> index e8223c3e781a..3f50cb3f8039 100644
>> --- a/samples/landlock/sandboxer.c
>> +++ b/samples/landlock/sandboxer.c
>> @@ -55,6 +55,7 @@ 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_TCP_LISTEN_NAME "LL_TCP_LISTEN"
>> #define ENV_DELIMITER ":"
>>
>> static int parse_path(char *env_path, const char ***const path_list)
>> @@ -208,7 +209,7 @@ static int populate_ruleset_net(const char *const env_var, const int ruleset_fd,
>>
>> /* clang-format on */
>>
>> -#define LANDLOCK_ABI_LAST 5
>> +#define LANDLOCK_ABI_LAST 6
>>
>> int main(const int argc, char *const argv[], char *const *const envp)
>> {
>> @@ -222,15 +223,16 @@ int main(const int argc, char *const argv[], char *const *const envp)
>> struct landlock_ruleset_attr ruleset_attr = {
>> .handled_access_fs = access_fs_rw,
>> .handled_access_net = LANDLOCK_ACCESS_NET_BIND_TCP |
>> - LANDLOCK_ACCESS_NET_CONNECT_TCP,
>> + LANDLOCK_ACCESS_NET_CONNECT_TCP |
>> + LANDLOCK_ACCESS_NET_LISTEN_TCP,
>> };
>>
>> if (argc < 2) {
>> fprintf(stderr,
>> - "usage: %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\"%s "
>> + "usage: %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\" %s=\"...\"%s "
>> "<cmd> [args]...\n\n",
>> ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME,
>> - ENV_TCP_CONNECT_NAME, argv[0]);
>> + ENV_TCP_CONNECT_NAME, ENV_TCP_LISTEN_NAME, argv[0]);
>> fprintf(stderr,
>> "Execute a command in a restricted environment.\n\n");
>> fprintf(stderr,
>> @@ -251,15 +253,19 @@ int main(const int argc, char *const argv[], char *const *const envp)
>> fprintf(stderr,
>> "* %s: list of ports allowed to connect (client).\n",
>> ENV_TCP_CONNECT_NAME);
>> + fprintf(stderr,
>> + "* %s: list of ports allowed to listen (server).\n",
>> + ENV_TCP_LISTEN_NAME);
>> fprintf(stderr,
>> "\nexample:\n"
>> "%s=\"${PATH}:/lib:/usr:/proc:/etc:/dev/urandom\" "
>> "%s=\"/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp\" "
>> "%s=\"9418\" "
>> "%s=\"80:443\" "
>> + "%s=\"9418\" "
>> "%s bash -i\n\n",
>> ENV_FS_RO_NAME, ENV_FS_RW_NAME, ENV_TCP_BIND_NAME,
>> - ENV_TCP_CONNECT_NAME, argv[0]);
>> + ENV_TCP_CONNECT_NAME, ENV_TCP_LISTEN_NAME, argv[0]);
>> fprintf(stderr,
>> "This sandboxer can use Landlock features "
>> "up to ABI version %d.\n",
>> @@ -326,6 +332,11 @@ int main(const int argc, char *const argv[], char *const *const envp)
>> case 4:
>> /* Removes LANDLOCK_ACCESS_FS_IOCTL_DEV for ABI < 5 */
>> ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_IOCTL_DEV;
>> + __attribute__((fallthrough));
>> + case 5:
>> + /* Removes LANDLOCK_ACCESS_NET_LISTEN support for ABI < 6 */
>> + ruleset_attr.handled_access_net &=
>> + ~(LANDLOCK_ACCESS_NET_LISTEN_TCP);
>
> (same remark as on other patch set)
>
> ABI version has shifted by one in the meantime.
Thanks, I'll update it for the next version.
>
>>
>> fprintf(stderr,
>> "Hint: You should update the running kernel "
>> @@ -357,6 +368,12 @@ int main(const int argc, char *const argv[], char *const *const envp)
>> ruleset_attr.handled_access_net &=
>> ~LANDLOCK_ACCESS_NET_CONNECT_TCP;
>> }
>> + /* Removes listen access attribute if not supported by a user. */
>
> (also same remark as on other patch set)
>
> Please s/supported/requested/, for consistency.
Ok, thanks!
>
>> + env_port_name = getenv(ENV_TCP_LISTEN_NAME);
>> + if (!env_port_name) {
>> + ruleset_attr.handled_access_net &=
>> + ~LANDLOCK_ACCESS_NET_LISTEN_TCP;
>> + }
>>
>> ruleset_fd =
>> landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
>> @@ -380,6 +397,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_net(ENV_TCP_LISTEN_NAME, ruleset_fd,
>> + LANDLOCK_ACCESS_NET_LISTEN_TCP)) {
>> + goto err_close_ruleset;
>> + }
>>
>> if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
>> perror("Failed to restrict privileges");
>> --
>> 2.34.1
>>
>
> Reviewed-by: Günther Noack <gnoack3000@gmail.com>
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [RFC PATCH v2 0/9] Support TCP listen access-control
2024-08-14 3:01 [RFC PATCH v2 0/9] Support TCP listen access-control Mikhail Ivanov
` (8 preceding siblings ...)
2024-08-14 3:01 ` [RFC PATCH v2 9/9] samples/landlock: Support LANDLOCK_ACCESS_NET_LISTEN Mikhail Ivanov
@ 2024-08-20 13:11 ` Günther Noack
2024-08-20 13:23 ` Günther Noack
9 siblings, 1 reply; 40+ messages in thread
From: Günther Noack @ 2024-08-20 13:11 UTC (permalink / raw)
To: Mikhail Ivanov
Cc: mic, willemdebruijn.kernel, gnoack3000, linux-security-module,
netdev, netfilter-devel, yusongping, artem.kuzin,
konstantin.meskhidze
Hello!
Thanks for sending v2 of this patchset!
On Wed, Aug 14, 2024 at 11:01:42AM +0800, Mikhail Ivanov wrote:
> Hello! This is v2 RFC patch dedicated to restriction of listening sockets.
>
> It is based on the landlock's mic-next branch on top of 6.11-rc1 kernel
> version.
>
> Description
> ===========
> LANDLOCK_ACCESS_NET_BIND_TCP is useful to limit the scope of "bindable"
> ports to forbid a malicious sandboxed process to impersonate a legitimate
> server process. However, bind(2) might be used by (TCP) clients to set the
> source port to a (legitimate) value. Controlling the ports that can be
> used for listening would allow (TCP) clients to explicitly bind to ports
> that are forbidden for listening.
>
> Such control is implemented with a new LANDLOCK_ACCESS_NET_LISTEN_TCP
> access right that restricts listening on undesired ports with listen(2).
>
> It's worth noticing that this access right doesn't affect changing
> backlog value using listen(2) on already listening socket. For this case
> test ipv4_tcp.double_listen is provided.
This is a good catch, btw, that seems like the right thing to do. 👍
I am overall happy with this patch set, but left a few remarks in the tests so
far. There are a few style nits here and there.
A thing that makes me uneasy is that the tests have a lot of logic in
test_restricted_net_fixture(), where instead of the test logic being
straightforward, there are conditionals to tell apart different scenarios and
expect different results. I wish that the style of these tests was more linear.
This patch set is making it a little bit worse, because the logic in
test_restricted_net_fixture() increases.
I have also made some restructuring suggestions for the kernel code, in the hope
that they simplify things. If they don't because I overlooked something, we can
skip that though.
—Günther
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [RFC PATCH v2 0/9] Support TCP listen access-control
2024-08-20 13:11 ` [RFC PATCH v2 0/9] Support TCP listen access-control Günther Noack
@ 2024-08-20 13:23 ` Günther Noack
2024-08-20 13:53 ` Mikhail Ivanov
0 siblings, 1 reply; 40+ messages in thread
From: Günther Noack @ 2024-08-20 13:23 UTC (permalink / raw)
To: Mikhail Ivanov
Cc: mic, willemdebruijn.kernel, gnoack3000, linux-security-module,
netdev, netfilter-devel, yusongping, artem.kuzin,
konstantin.meskhidze
On Tue, Aug 20, 2024 at 03:11:07PM +0200, Günther Noack wrote:
> Hello!
>
> Thanks for sending v2 of this patchset!
>
> On Wed, Aug 14, 2024 at 11:01:42AM +0800, Mikhail Ivanov wrote:
> > Hello! This is v2 RFC patch dedicated to restriction of listening sockets.
> >
> > It is based on the landlock's mic-next branch on top of 6.11-rc1 kernel
> > version.
> >
> > Description
> > ===========
> > LANDLOCK_ACCESS_NET_BIND_TCP is useful to limit the scope of "bindable"
> > ports to forbid a malicious sandboxed process to impersonate a legitimate
> > server process. However, bind(2) might be used by (TCP) clients to set the
> > source port to a (legitimate) value. Controlling the ports that can be
> > used for listening would allow (TCP) clients to explicitly bind to ports
> > that are forbidden for listening.
> >
> > Such control is implemented with a new LANDLOCK_ACCESS_NET_LISTEN_TCP
> > access right that restricts listening on undesired ports with listen(2).
> >
> > It's worth noticing that this access right doesn't affect changing
> > backlog value using listen(2) on already listening socket. For this case
> > test ipv4_tcp.double_listen is provided.
>
> This is a good catch, btw, that seems like the right thing to do. 👍
>
>
> I am overall happy with this patch set, but left a few remarks in the tests so
> far. There are a few style nits here and there.
>
> A thing that makes me uneasy is that the tests have a lot of logic in
> test_restricted_net_fixture(), where instead of the test logic being
> straightforward, there are conditionals to tell apart different scenarios and
> expect different results. I wish that the style of these tests was more linear.
> This patch set is making it a little bit worse, because the logic in
> test_restricted_net_fixture() increases.
>
> I have also made some restructuring suggestions for the kernel code, in the hope
> that they simplify things. If they don't because I overlooked something, we can
> skip that though.
I missed to mention it -- the documentation in
Documentation/userspace-api/landlock.rst needs updating as well.
—Günther
^ permalink raw reply [flat|nested] 40+ messages in thread
* Re: [RFC PATCH v2 0/9] Support TCP listen access-control
2024-08-20 13:23 ` Günther Noack
@ 2024-08-20 13:53 ` Mikhail Ivanov
0 siblings, 0 replies; 40+ messages in thread
From: Mikhail Ivanov @ 2024-08-20 13:53 UTC (permalink / raw)
To: Günther Noack
Cc: mic, willemdebruijn.kernel, gnoack3000, linux-security-module,
netdev, netfilter-devel, yusongping, artem.kuzin,
konstantin.meskhidze
8/20/2024 4:23 PM, Günther Noack wrote:
> On Tue, Aug 20, 2024 at 03:11:07PM +0200, Günther Noack wrote:
>> Hello!
>>
>> Thanks for sending v2 of this patchset!
>>
>> On Wed, Aug 14, 2024 at 11:01:42AM +0800, Mikhail Ivanov wrote:
>>> Hello! This is v2 RFC patch dedicated to restriction of listening sockets.
>>>
>>> It is based on the landlock's mic-next branch on top of 6.11-rc1 kernel
>>> version.
>>>
>>> Description
>>> ===========
>>> LANDLOCK_ACCESS_NET_BIND_TCP is useful to limit the scope of "bindable"
>>> ports to forbid a malicious sandboxed process to impersonate a legitimate
>>> server process. However, bind(2) might be used by (TCP) clients to set the
>>> source port to a (legitimate) value. Controlling the ports that can be
>>> used for listening would allow (TCP) clients to explicitly bind to ports
>>> that are forbidden for listening.
>>>
>>> Such control is implemented with a new LANDLOCK_ACCESS_NET_LISTEN_TCP
>>> access right that restricts listening on undesired ports with listen(2).
>>>
>>> It's worth noticing that this access right doesn't affect changing
>>> backlog value using listen(2) on already listening socket. For this case
>>> test ipv4_tcp.double_listen is provided.
>>
>> This is a good catch, btw, that seems like the right thing to do. 👍
>>
>>
>> I am overall happy with this patch set, but left a few remarks in the tests so
>> far. There are a few style nits here and there.
>>
>> A thing that makes me uneasy is that the tests have a lot of logic in
>> test_restricted_net_fixture(), where instead of the test logic being
>> straightforward, there are conditionals to tell apart different scenarios and
>> expect different results. I wish that the style of these tests was more linear.
>> This patch set is making it a little bit worse, because the logic in
>> test_restricted_net_fixture() increases.
>>
>> I have also made some restructuring suggestions for the kernel code, in the hope
>> that they simplify things. If they don't because I overlooked something, we can
>> skip that though.
>
> I missed to mention it -- the documentation in
> Documentation/userspace-api/landlock.rst needs updating as well.
I'll do it. Thank you for reviewing this patch set!
>
> —Günther
^ permalink raw reply [flat|nested] 40+ messages in thread