diff mbox series

[4/5] Add landlock08 test

Message ID 20240919-landlock_network-v1-4-9c997f03bd0a@suse.com
State New
Headers show
Series landlock network coverage support | expand

Commit Message

Andrea Cervesato Sept. 19, 2024, 10:23 a.m. UTC
From: Andrea Cervesato <andrea.cervesato@suse.com>

Verify the landlock support for bind()/connect() syscalls in IPV4
and IPV6 protocols. In particular, check that bind() is assigning
the address only on the TCP port enforced by
LANDLOCK_ACCESS_NET_BIND_TCP and check that connect() is connecting
only to a specific TCP port enforced by
LANDLOCK_ACCESS_NET_CONNECT_TCP.

Signed-off-by: Andrea Cervesato <andrea.cervesato@suse.com>
---
 include/lapi/capability.h                          |   4 +
 runtest/syscalls                                   |   1 +
 testcases/kernel/syscalls/landlock/.gitignore      |   1 +
 testcases/kernel/syscalls/landlock/landlock08.c    | 199 +++++++++++++++++++++
 .../kernel/syscalls/landlock/landlock_common.h     |  93 +++++++++-
 5 files changed, 297 insertions(+), 1 deletion(-)
diff mbox series

Patch

diff --git a/include/lapi/capability.h b/include/lapi/capability.h
index 0f317d6d7..14d2d3c12 100644
--- a/include/lapi/capability.h
+++ b/include/lapi/capability.h
@@ -20,6 +20,10 @@ 
 # endif
 #endif
 
+#ifndef CAP_NET_BIND_SERVICE
+# define CAP_NET_BIND_SERVICE 10
+#endif
+
 #ifndef CAP_NET_RAW
 # define CAP_NET_RAW          13
 #endif
diff --git a/runtest/syscalls b/runtest/syscalls
index 02e721df9..e1e9765c7 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -708,6 +708,7 @@  landlock04 landlock04
 landlock05 landlock05
 landlock06 landlock06
 landlock07 landlock07
+landlock08 landlock08
 
 lchown01 lchown01
 lchown01_16 lchown01_16
diff --git a/testcases/kernel/syscalls/landlock/.gitignore b/testcases/kernel/syscalls/landlock/.gitignore
index db11bff2f..fc7317394 100644
--- a/testcases/kernel/syscalls/landlock/.gitignore
+++ b/testcases/kernel/syscalls/landlock/.gitignore
@@ -6,3 +6,4 @@  landlock04
 landlock05
 landlock06
 landlock07
+landlock08
diff --git a/testcases/kernel/syscalls/landlock/landlock08.c b/testcases/kernel/syscalls/landlock/landlock08.c
new file mode 100644
index 000000000..c886971dc
--- /dev/null
+++ b/testcases/kernel/syscalls/landlock/landlock08.c
@@ -0,0 +1,199 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2024 SUSE LLC Andrea Cervesato <andrea.cervesato@suse.com>
+ */
+
+/*\
+ * [Description]
+ *
+ * Verify the landlock support for bind()/connect() syscalls in IPV4 and IPV6
+ * protocols. In particular, check that bind() is assigning the address only on
+ * the TCP port enforced by LANDLOCK_ACCESS_NET_BIND_TCP and check that
+ * connect() is connecting only to a specific TCP port enforced by
+ * LANDLOCK_ACCESS_NET_CONNECT_TCP.
+ *
+ * [Algorithm]
+ *
+ * Repeat the following procedure for IPV4 and IPV6:
+ *
+ * - create a socket on PORT1, bind() it and check if it passes
+ * - enforce the current sandbox with LANDLOCK_ACCESS_NET_BIND_TCP on PORT1
+ * - create a socket on PORT1, bind() it and check if it passes
+ * - create a socket on PORT2, bind() it and check if it fails
+ *
+ * - create a server listening on PORT1
+ * - create a socket on PORT1, connect() to it and check if it passes
+ * - enforce the current sandbox with LANDLOCK_ACCESS_NET_CONNECT_TCP on PORT1
+ * - create a socket on PORT1, connect() to it and check if it passes
+ * - create a socket on PORT2, connect() to it and check if it fails
+ */
+
+#include "landlock_common.h"
+
+#define ADDRESS_PORT 0x7c90
+
+static int variants[] = {
+	AF_INET,
+	AF_INET6,
+};
+
+static struct tst_landlock_ruleset_attr *ruleset_attr;
+static struct landlock_net_port_attr *net_port_attr;
+static in_port_t *server_port;
+
+static void create_server(const int addr_family)
+{
+	struct socket_data socket;
+	struct sockaddr *addr = NULL;
+
+	create_socket(&socket, addr_family, 0);
+	getsocket_addr(&socket, addr_family, &addr);
+
+	SAFE_BIND(socket.fd, addr, socket.address_size);
+	SAFE_LISTEN(socket.fd, 1);
+
+	*server_port = getsocket_port(&socket, addr_family);
+
+	tst_res(TDEBUG, "Server listening on port %u", *server_port);
+
+	TST_CHECKPOINT_WAKE_AND_WAIT(0);
+
+	SAFE_CLOSE(socket.fd);
+}
+
+static void test_bind(const int addr_family, const in_port_t port,
+	const int exp_err)
+{
+	struct socket_data socket;
+	struct sockaddr *addr = NULL;
+
+	create_socket(&socket, addr_family, port);
+	getsocket_addr(&socket, addr_family, &addr);
+
+	if (exp_err) {
+		TST_EXP_FAIL(
+			bind(socket.fd, addr, socket.address_size),
+			exp_err, "bind() access on port %u", port);
+	} else {
+		TST_EXP_PASS(
+			bind(socket.fd, addr, socket.address_size),
+			"bind() access on port %u", port);
+	}
+
+	SAFE_CLOSE(socket.fd);
+}
+
+static void test_connect(const int addr_family, const in_port_t port,
+	const int exp_err)
+{
+	struct socket_data socket;
+	struct sockaddr *addr = NULL;
+
+	create_socket(&socket, addr_family, port);
+	getsocket_addr(&socket, addr_family, &addr);
+
+	if (exp_err) {
+		TST_EXP_FAIL(
+			connect(socket.fd, addr, socket.address_size),
+			exp_err, "connect() on port %u", port);
+	} else {
+		TST_EXP_PASS(
+			connect(socket.fd, addr, socket.address_size),
+			"connect() on port %u", port);
+	}
+
+	SAFE_CLOSE(socket.fd);
+}
+
+static void run(void)
+{
+	int addr_family = variants[tst_variant];
+
+	tst_res(TINFO, "Using %s protocol",
+		addr_family == AF_INET ? "IPV4" : "IPV6");
+
+	if (!SAFE_FORK()) {
+		create_server(addr_family);
+		exit(0);
+	}
+
+	TST_CHECKPOINT_WAIT(0);
+
+	/* verify bind() syscall accessibility */
+	if (!SAFE_FORK()) {
+		ruleset_attr->data.handled_access_net =
+			LANDLOCK_ACCESS_NET_BIND_TCP;
+
+		test_bind(addr_family, ADDRESS_PORT, 0);
+
+		tst_res(TINFO, "Enable bind() access only for port %u",
+			ADDRESS_PORT);
+
+		apply_landlock_net_layer(ruleset_attr, net_port_attr,
+			ADDRESS_PORT, LANDLOCK_ACCESS_NET_BIND_TCP);
+
+		test_bind(addr_family, ADDRESS_PORT, 0);
+		test_bind(addr_family, ADDRESS_PORT + 0x80, EACCES);
+
+		exit(0);
+	}
+
+	/* verify connect() syscall accessibility */
+	if (!SAFE_FORK()) {
+		ruleset_attr->data.handled_access_net =
+			LANDLOCK_ACCESS_NET_CONNECT_TCP;
+
+		test_connect(addr_family, *server_port, 0);
+
+		tst_res(TINFO, "Enable connect() access only on port %u",
+			*server_port);
+
+		apply_landlock_net_layer(ruleset_attr, net_port_attr,
+			*server_port, LANDLOCK_ACCESS_NET_CONNECT_TCP);
+
+		test_connect(addr_family, *server_port, 0);
+		test_connect(addr_family, *server_port + 0x80, EACCES);
+
+		TST_CHECKPOINT_WAKE(0);
+
+		exit(0);
+	}
+}
+
+static void setup(void)
+{
+	verify_landlock_is_enabled();
+
+	server_port = SAFE_MMAP(NULL, sizeof(in_port_t), PROT_READ | PROT_WRITE,
+		MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+}
+
+static void cleanup(void)
+{
+	if (server_port)
+		SAFE_MUNMAP(server_port, sizeof(in_port_t));
+}
+
+static struct tst_test test = {
+	.test_all = run,
+	.setup = setup,
+	.cleanup = cleanup,
+	.needs_root = 1,
+	.needs_checkpoints = 1,
+	.forks_child = 1,
+	.test_variants = ARRAY_SIZE(variants),
+	.bufs = (struct tst_buffers[]) {
+		{&ruleset_attr, .size = sizeof(struct tst_landlock_ruleset_attr)},
+		{&net_port_attr, .size = sizeof(struct landlock_net_port_attr)},
+		{},
+	},
+	.caps = (struct tst_cap []) {
+		TST_CAP(TST_CAP_REQ, CAP_SYS_ADMIN),
+		TST_CAP(TST_CAP_REQ, CAP_NET_BIND_SERVICE),
+		{}
+	},
+	.needs_kconfigs = (const char *[]) {
+		"CONFIG_INET=y",
+		NULL
+	},
+};
diff --git a/testcases/kernel/syscalls/landlock/landlock_common.h b/testcases/kernel/syscalls/landlock/landlock_common.h
index a955340bf..db500deb8 100644
--- a/testcases/kernel/syscalls/landlock/landlock_common.h
+++ b/testcases/kernel/syscalls/landlock/landlock_common.h
@@ -11,6 +11,16 @@ 
 #include "lapi/fcntl.h"
 #include "lapi/landlock.h"
 
+#define IPV4_ADDRESS "127.0.0.1"
+#define IPV6_ADDRESS "::1"
+
+struct socket_data {
+	struct sockaddr_in addr_ipv4;
+	struct sockaddr_in6 addr_ipv6;
+	size_t address_size;
+	int fd;
+};
+
 static inline int verify_landlock_is_enabled(void)
 {
 	int abi;
@@ -93,7 +103,7 @@  static inline void apply_landlock_fs_layer(
 static inline void apply_landlock_net_layer(
 	struct tst_landlock_ruleset_attr *ruleset_attr,
 	struct landlock_net_port_attr *net_port_attr,
-	const uint64_t port,
+	const in_port_t port,
 	const uint64_t access)
 {
 	int ruleset_fd;
@@ -107,4 +117,85 @@  static inline void apply_landlock_net_layer(
 	SAFE_CLOSE(ruleset_fd);
 }
 
+static inline in_port_t getsocket_port(struct socket_data *socket,
+	const int addr_family)
+{
+	struct sockaddr_in addr_ipv4;
+	struct sockaddr_in6 addr_ipv6;
+	socklen_t len;
+	in_port_t port = 0;
+
+	switch (addr_family) {
+	case AF_INET:
+		len = sizeof(addr_ipv4);
+		memset(&addr_ipv4, 0, len);
+
+		SAFE_GETSOCKNAME(socket->fd, (struct sockaddr *)&addr_ipv4, &len);
+		port = ntohs(addr_ipv4.sin_port);
+		break;
+	case AF_INET6:
+		len = sizeof(addr_ipv6);
+		memset(&addr_ipv6, 0, len);
+
+		SAFE_GETSOCKNAME(socket->fd, (struct sockaddr *)&addr_ipv6, &len);
+		port = ntohs(addr_ipv6.sin6_port);
+		break;
+	default:
+		tst_brk(TBROK, "Unsupported protocol");
+		break;
+	};
+
+	return port;
+}
+
+static inline void create_socket(struct socket_data *socket,
+	const int addr_family, const in_port_t port)
+{
+	memset(socket, 0, sizeof(struct socket_data));
+
+	switch (addr_family) {
+	case AF_INET:
+		if (!port) {
+			tst_init_sockaddr_inet_bin(&socket->addr_ipv4,
+				INADDR_ANY, 0);
+		} else {
+			tst_init_sockaddr_inet(&socket->addr_ipv4,
+				IPV4_ADDRESS, port);
+		}
+
+		socket->address_size = sizeof(struct sockaddr_in);
+		break;
+	case AF_INET6:
+		if (!port) {
+			tst_init_sockaddr_inet6_bin(&socket->addr_ipv6,
+				&in6addr_any, 0);
+		} else {
+			tst_init_sockaddr_inet6(&socket->addr_ipv6,
+				IPV6_ADDRESS, port);
+		}
+
+		socket->address_size = sizeof(struct sockaddr_in6);
+		break;
+	default:
+		tst_brk(TBROK, "Unsupported protocol");
+		return;
+	};
+
+	socket->fd = SAFE_SOCKET(addr_family, SOCK_STREAM | SOCK_CLOEXEC, 0);
+}
+
+static inline void getsocket_addr(struct socket_data *socket,
+	const int addr_family, struct sockaddr **addr)
+{
+	switch (addr_family) {
+	case AF_INET:
+		*addr = (struct sockaddr *)&socket->addr_ipv4;
+		break;
+	case AF_INET6:
+		*addr = (struct sockaddr *)&socket->addr_ipv6;
+		break;
+	default:
+		break;
+	};
+}
 #endif /* LANDLOCK_COMMON_H__ */