[RFC PATCH v1 2/2] selftests/landlock: Test non-TCP INET connection-based protocols

Mikhail Ivanov ivanov.mikhail1 at huawei-partners.com
Thu Oct 3 21:22:42 UTC 2024



On 10/3/2024 8:45 PM, Mickaël Salaün wrote:
> On Thu, Oct 03, 2024 at 10:39:32PM +0800, Mikhail Ivanov wrote:
>> Extend protocol fixture with test suits for MPTCP, SCTP and SMC protocols.
>> Add all options required by this protocols in config.
> 
> Great coverage!  It's nice to check against SCTP and MPTCP, but as you
> were wondering, I think you can remove the SMC protocol to simplify
> tests. MPTCP seems to work similarly as TCP wrt AF_UNSPEC, so it might
> be worth keeping it, and we might want to control these protocols too
> one day.

Thanks! I'll remove SMC then.

> 
>>
>> Extend protocol_variant structure with protocol field (Cf. socket(2)).
>>
>> Refactor is_restricted() helper and add few helpers to check struct
>> protocol_variant on specific protocols.
> 
>>
>> Signed-off-by: Mikhail Ivanov <ivanov.mikhail1 at huawei-partners.com>
>> ---
>>   tools/testing/selftests/landlock/common.h   |   1 +
>>   tools/testing/selftests/landlock/config     |   5 +
>>   tools/testing/selftests/landlock/net_test.c | 212 ++++++++++++++++++--
>>   3 files changed, 198 insertions(+), 20 deletions(-)
>>
>> diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h
>> index 61056fa074bb..40a2def50b83 100644
>> --- a/tools/testing/selftests/landlock/common.h
>> +++ b/tools/testing/selftests/landlock/common.h
>> @@ -234,6 +234,7 @@ enforce_ruleset(struct __test_metadata *const _metadata, const int ruleset_fd)
>>   struct protocol_variant {
>>   	int domain;
>>   	int type;
>> +	int protocol;
>>   };
>>   
>>   struct service_fixture {
>> diff --git a/tools/testing/selftests/landlock/config b/tools/testing/selftests/landlock/config
>> index 29af19c4e9f9..73b01d7d0881 100644
>> --- a/tools/testing/selftests/landlock/config
>> +++ b/tools/testing/selftests/landlock/config
>> @@ -1,8 +1,12 @@
>>   CONFIG_CGROUPS=y
>>   CONFIG_CGROUP_SCHED=y
>>   CONFIG_INET=y
>> +CONFIG_INFINIBAND=y
> 
> Without SMC this infiniband should not be required.

yeap

> 
>> +CONFIG_IP_SCTP=y
>>   CONFIG_IPV6=y
>>   CONFIG_KEYS=y
>> +CONFIG_MPTCP=y
>> +CONFIG_MPTCP_IPV6=y
>>   CONFIG_NET=y
>>   CONFIG_NET_NS=y
>>   CONFIG_OVERLAY_FS=y
>> @@ -10,6 +14,7 @@ CONFIG_PROC_FS=y
>>   CONFIG_SECURITY=y
>>   CONFIG_SECURITY_LANDLOCK=y
>>   CONFIG_SHMEM=y
>> +CONFIG_SMC=y
>>   CONFIG_SYSFS=y
>>   CONFIG_TMPFS=y
>>   CONFIG_TMPFS_XATTR=y
>> diff --git a/tools/testing/selftests/landlock/net_test.c b/tools/testing/selftests/landlock/net_test.c
>> index 4e0aeb53b225..dbe77d436281 100644
>> --- a/tools/testing/selftests/landlock/net_test.c
>> +++ b/tools/testing/selftests/landlock/net_test.c
>> @@ -36,6 +36,17 @@ enum sandbox_type {
>>   	TCP_SANDBOX,
>>   };
>>   
>> +/* Checks if IPPROTO_SMC is present for compatibility reasons. */
>> +#if !defined(__alpha__) && defined(IPPROTO_SMC)
>> +#define SMC_SUPPORTED 1
>> +#else
>> +#define SMC_SUPPORTED 0
>> +#endif
>> +
>> +#ifndef IPPROTO_SMC
>> +#define IPPROTO_SMC 256
>> +#endif
>> +
>>   static int set_service(struct service_fixture *const srv,
>>   		       const struct protocol_variant prot,
>>   		       const unsigned short index)
>> @@ -85,19 +96,37 @@ static void setup_loopback(struct __test_metadata *const _metadata)
>>   	clear_ambient_cap(_metadata, CAP_NET_ADMIN);
>>   }
>>   
>> +static bool prot_is_inet_stream(const struct protocol_variant *const prot)
>> +{
>> +	return (prot->domain == AF_INET || prot->domain == AF_INET6) &&
>> +	       prot->type == SOCK_STREAM;
>> +}
>> +
>> +static bool prot_is_tcp(const struct protocol_variant *const prot)
>> +{
>> +	return prot_is_inet_stream(prot) &&
>> +	       (prot->protocol == IPPROTO_TCP || prot->protocol == IPPROTO_IP);
> 
> Why do we need to check against IPPROTO_IP?

IPPROTO_IP = 0 and can be used as an alias for IPPROTO_TCP (=6) in
socket(2) (also for IPPROTO_UDP(=17), Cf. inet_create).

Since we create TCP sockets in a common way here (with protocol = 0),
checking against IPPROTO_TCP is not necessary, but I decided to leave it
for completeness.

> 
>> +}
>> +
>> +static bool prot_is_sctp(const struct protocol_variant *const prot)
>> +{
>> +	return prot_is_inet_stream(prot) && prot->protocol == IPPROTO_SCTP;
>> +}
>> +
>> +static bool prot_is_smc(const struct protocol_variant *const prot)
>> +{
>> +	return prot_is_inet_stream(prot) && prot->protocol == IPPROTO_SMC;
>> +}
>> +
>> +static bool prot_is_unix_stream(const struct protocol_variant *const prot)
>> +{
>> +	return prot->domain == AF_UNIX && prot->type == SOCK_STREAM;
>> +}
>> +
>>   static bool is_restricted(const struct protocol_variant *const prot,
>>   			  const enum sandbox_type sandbox)
>>   {
>> -	switch (prot->domain) {
>> -	case AF_INET:
>> -	case AF_INET6:
>> -		switch (prot->type) {
>> -		case SOCK_STREAM:
>> -			return sandbox == TCP_SANDBOX;
>> -		}
>> -		break;
>> -	}
>> -	return false;
>> +	return prot_is_tcp(prot) && sandbox == TCP_SANDBOX;
>>   }
>>   
>>   static int socket_variant(const struct service_fixture *const srv)
>> @@ -105,7 +134,7 @@ static int socket_variant(const struct service_fixture *const srv)
>>   	int ret;
>>   
>>   	ret = socket(srv->protocol.domain, srv->protocol.type | SOCK_CLOEXEC,
>> -		     0);
>> +		     srv->protocol.protocol);
>>   	if (ret < 0)
>>   		return -errno;
>>   	return ret;
>> @@ -124,7 +153,7 @@ static socklen_t get_addrlen(const struct service_fixture *const srv,
>>   		return sizeof(srv->ipv4_addr);
>>   
>>   	case AF_INET6:
>> -		if (minimal)
>> +		if (minimal && !prot_is_sctp(&srv->protocol))
> 
> Why SCTP requires this exception?

SCTP implementation (possibly incorrectly) checks that address length is
at least sizeof(struct sockaddr_in6) (Cf. sctp_sockaddr_af() for bind(2)
and in sctp_connect() for connect(2)).

> 
>>   			return SIN6_LEN_RFC2133;
>>   		return sizeof(srv->ipv6_addr);
>>   
>> @@ -271,6 +300,11 @@ FIXTURE_SETUP(protocol)
>>   		.type = SOCK_STREAM,
>>   	};
>>   
>> +#if !SMC_SUPPORTED
>> +	if (prot_is_smc(&variant->prot))
>> +		SKIP(return, "SMC protocol is not supported.");
>> +#endif
>> +
>>   	disable_caps(_metadata);
>>   
>>   	ASSERT_EQ(0, set_service(&self->srv0, variant->prot, 0));
>> @@ -299,6 +333,39 @@ FIXTURE_VARIANT_ADD(protocol, no_sandbox_with_ipv4_tcp) {
>>   	},
>>   };
>>   
>> +/* clang-format off */
>> +FIXTURE_VARIANT_ADD(protocol, no_sandbox_with_ipv4_mptcp) {
>> +	/* clang-format on */
>> +	.sandbox = NO_SANDBOX,
>> +	.prot = {
>> +		.domain = AF_INET,
>> +		.type = SOCK_STREAM,
>> +		.protocol = IPPROTO_MPTCP,
>> +	},
>> +};
>> +
>> +/* clang-format off */
>> +FIXTURE_VARIANT_ADD(protocol, no_sandbox_with_ipv4_sctp) {
>> +	/* clang-format on */
>> +	.sandbox = NO_SANDBOX,
>> +	.prot = {
>> +		.domain = AF_INET,
>> +		.type = SOCK_STREAM,
>> +		.protocol = IPPROTO_SCTP,
>> +	},
>> +};
>> +
>> +/* clang-format off */
>> +FIXTURE_VARIANT_ADD(protocol, no_sandbox_with_ipv4_smc) {
>> +	/* clang-format on */
>> +	.sandbox = NO_SANDBOX,
>> +	.prot = {
>> +		.domain = AF_INET,
>> +		.type = SOCK_STREAM,
>> +		.protocol = IPPROTO_SMC,
>> +	},
>> +};
>> +
>>   /* clang-format off */
>>   FIXTURE_VARIANT_ADD(protocol, no_sandbox_with_ipv6_tcp) {
>>   	/* clang-format on */
>> @@ -309,6 +376,39 @@ FIXTURE_VARIANT_ADD(protocol, no_sandbox_with_ipv6_tcp) {
>>   	},
>>   };
>>   
>> +/* clang-format off */
>> +FIXTURE_VARIANT_ADD(protocol, no_sandbox_with_ipv6_mptcp) {
>> +	/* clang-format on */
>> +	.sandbox = NO_SANDBOX,
>> +	.prot = {
>> +		.domain = AF_INET6,
>> +		.type = SOCK_STREAM,
>> +		.protocol = IPPROTO_MPTCP,
>> +	},
>> +};
>> +
>> +/* clang-format off */
>> +FIXTURE_VARIANT_ADD(protocol, no_sandbox_with_ipv6_sctp) {
>> +	/* clang-format on */
>> +	.sandbox = NO_SANDBOX,
>> +	.prot = {
>> +		.domain = AF_INET6,
>> +		.type = SOCK_STREAM,
>> +		.protocol = IPPROTO_SCTP,
>> +	},
>> +};
>> +
>> +/* clang-format off */
>> +FIXTURE_VARIANT_ADD(protocol, no_sandbox_with_ipv6_smc) {
>> +	/* clang-format on */
>> +	.sandbox = NO_SANDBOX,
>> +	.prot = {
>> +		.domain = AF_INET6,
>> +		.type = SOCK_STREAM,
>> +		.protocol = IPPROTO_SMC,
>> +	},
>> +};
>> +
>>   /* clang-format off */
>>   FIXTURE_VARIANT_ADD(protocol, no_sandbox_with_ipv4_udp) {
>>   	/* clang-format on */
>> @@ -359,6 +459,39 @@ FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_ipv4_tcp) {
>>   	},
>>   };
>>   
>> +/* clang-format off */
>> +FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_ipv4_mptcp) {
>> +	/* clang-format on */
>> +	.sandbox = TCP_SANDBOX,
>> +	.prot = {
>> +		.domain = AF_INET,
>> +		.type = SOCK_STREAM,
>> +		.protocol = IPPROTO_MPTCP,
>> +	},
>> +};
>> +
>> +/* clang-format off */
>> +FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_ipv4_sctp) {
>> +	/* clang-format on */
>> +	.sandbox = TCP_SANDBOX,
>> +	.prot = {
>> +		.domain = AF_INET,
>> +		.type = SOCK_STREAM,
>> +		.protocol = IPPROTO_SCTP,
>> +	},
>> +};
>> +
>> +/* clang-format off */
>> +FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_ipv4_smc) {
>> +	/* clang-format on */
>> +	.sandbox = TCP_SANDBOX,
>> +	.prot = {
>> +		.domain = AF_INET,
>> +		.type = SOCK_STREAM,
>> +		.protocol = IPPROTO_SMC,
>> +	},
>> +};
>> +
>>   /* clang-format off */
>>   FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_ipv6_tcp) {
>>   	/* clang-format on */
>> @@ -369,6 +502,39 @@ FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_ipv6_tcp) {
>>   	},
>>   };
>>   
>> +/* clang-format off */
>> +FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_ipv6_mptcp) {
>> +	/* clang-format on */
>> +	.sandbox = TCP_SANDBOX,
>> +	.prot = {
>> +		.domain = AF_INET6,
>> +		.type = SOCK_STREAM,
>> +		.protocol = IPPROTO_MPTCP,
>> +	},
>> +};
>> +
>> +/* clang-format off */
>> +FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_ipv6_sctp) {
>> +	/* clang-format on */
>> +	.sandbox = TCP_SANDBOX,
>> +	.prot = {
>> +		.domain = AF_INET6,
>> +		.type = SOCK_STREAM,
>> +		.protocol = IPPROTO_SCTP,
>> +	},
>> +};
>> +
>> +/* clang-format off */
>> +FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_ipv6_smc) {
>> +	/* clang-format on */
>> +	.sandbox = TCP_SANDBOX,
>> +	.prot = {
>> +		.domain = AF_INET6,
>> +		.type = SOCK_STREAM,
>> +		.protocol = IPPROTO_SMC,
>> +	},
>> +};
>> +
>>   /* clang-format off */
>>   FIXTURE_VARIANT_ADD(protocol, tcp_sandbox_with_ipv4_udp) {
>>   	/* clang-format on */
>> @@ -663,7 +829,7 @@ TEST_F(protocol, bind_unspec)
>>   
>>   	/* Allowed bind on AF_UNSPEC/INADDR_ANY. */
>>   	ret = bind_variant(bind_fd, &self->unspec_any0);
>> -	if (variant->prot.domain == AF_INET) {
>> +	if (variant->prot.domain == AF_INET && !prot_is_sctp(&variant->prot)) {
>>   		EXPECT_EQ(0, ret)
>>   		{
>>   			TH_LOG("Failed to bind to unspec/any socket: %s",
>> @@ -689,7 +855,7 @@ TEST_F(protocol, bind_unspec)
>>   
>>   	/* Denied bind on AF_UNSPEC/INADDR_ANY. */
>>   	ret = bind_variant(bind_fd, &self->unspec_any0);
>> -	if (variant->prot.domain == AF_INET) {
>> +	if (variant->prot.domain == AF_INET && !prot_is_sctp(&variant->prot)) {
> 
> It looks like we need the same exception for the next bind_variant()
> call.

I ran these tests with active selinux (and few other LSMs) (selinux is 
set by default for x86_64) and it seems that this check was passed
correctly due to SCTP errno inconsistency in selinux_socket_bind().

With selinux_socket_bind() disabled, bind_variant() returns -EINVAL as
it should (Cf. sctp_do_bind).

Such inconsistency happens because sksec->sclass security field can be
initialized with SECCLASS_SOCKET (Cf. socket_type_to_security_class)
in SCTP case, and selinux_socket_bind() provides following check:

	/* Note that SCTP services expect -EINVAL, others -EAFNOSUPPORT. */
	if (sksec->sclass == SECCLASS_SCTP_SOCKET)
		return -EINVAL;
	return -EAFNOSUPPORT;

I'll possibly send a fix for this to selinux.

> 
>>   		if (is_restricted(&variant->prot, variant->sandbox)) {
>>   			EXPECT_EQ(-EACCES, ret);
>>   		} else {
>> @@ -727,6 +893,10 @@ TEST_F(protocol, connect_unspec)
>>   	int bind_fd, client_fd, status;
>>   	pid_t child;
>>   
>> +	if (prot_is_smc(&variant->prot))
>> +		SKIP(return, "SMC does not properly handles disconnect "
>> +			     "in the case of fallback to TCP");
>> +
>>   	/* Specific connection tests. */
>>   	bind_fd = socket_variant(&self->srv0);
>>   	ASSERT_LE(0, bind_fd);
>> @@ -769,17 +939,18 @@ TEST_F(protocol, connect_unspec)
>>   
>>   		/* Disconnects already connected socket, or set peer. */
>>   		ret = connect_variant(connect_fd, &self->unspec_any0);
>> -		if (self->srv0.protocol.domain == AF_UNIX &&
>> -		    self->srv0.protocol.type == SOCK_STREAM) {
>> +		if (prot_is_unix_stream(&variant->prot)) {
>>   			EXPECT_EQ(-EINVAL, ret);
>> +		} else if (prot_is_sctp(&variant->prot)) {
>> +			EXPECT_EQ(-EOPNOTSUPP, ret);
>>   		} else {
>>   			EXPECT_EQ(0, ret);
>>   		}
>>   
>>   		/* Tries to reconnect, or set peer. */
>>   		ret = connect_variant(connect_fd, &self->srv0);
>> -		if (self->srv0.protocol.domain == AF_UNIX &&
>> -		    self->srv0.protocol.type == SOCK_STREAM) {
>> +		if (prot_is_unix_stream(&variant->prot) ||
>> +		    prot_is_sctp(&variant->prot)) {
>>   			EXPECT_EQ(-EISCONN, ret);
>>   		} else {
>>   			EXPECT_EQ(0, ret);
>> @@ -796,9 +967,10 @@ TEST_F(protocol, connect_unspec)
>>   		}
>>   
>>   		ret = connect_variant(connect_fd, &self->unspec_any0);
>> -		if (self->srv0.protocol.domain == AF_UNIX &&
>> -		    self->srv0.protocol.type == SOCK_STREAM) {
>> +		if (prot_is_unix_stream(&variant->prot)) {
>>   			EXPECT_EQ(-EINVAL, ret);
>> +		} else if (prot_is_sctp(&variant->prot)) {
>> +			EXPECT_EQ(-EOPNOTSUPP, ret);
>>   		} else {
>>   			/* Always allowed to disconnect. */
>>   			EXPECT_EQ(0, ret);
>> -- 
>> 2.34.1
>>
>>



More information about the Linux-security-module-archive mailing list