[PATCH 6/6] selftests/coredump: test failed handshakes

Christian Brauner <[email protected]>
Newsgroups org.ozlabs.lists.linuxppc-dev,org.kernel.vger.linux-fsdevel,org.kernel.vger.linux-kernel,org.kernel.vger.linux-kselftest,org.kvack.linux-mm
Message-ID <[email protected]>
Add more coredump refusal tests.

Signed-off-by: Christian Brauner (Amutable) <[email protected]>
---
 .../coredump/coredump_socket_protocol_test.c       | 211 ++++++++++++++++++++-
 .../selftests/coredump/coredump_test_helpers.c     |  32 +++-
 .../selftests/coredump/coredump_test_helpers.h     |   1 +
 3 files changed, 238 insertions(+), 6 deletions(-)

diff --git a/tools/testing/selftests/coredump/coredump_socket_protocol_test.c b/tools/testing/selftests/coredump/coredump_socket_protocol_test.c
index 6c7327832d44..f5c9bad87546 100644
--- a/tools/testing/selftests/coredump/coredump_socket_protocol_test.c
+++ b/tools/testing/selftests/coredump/coredump_socket_protocol_test.c
@@ -510,14 +510,15 @@ TEST_F(coredump, socket_request_reject)
 
 /* An ack the kernel must refuse and how. */
 struct refused_ack {
-	/* The ack and how many bytes of it the server sends. */
+	/* The ack, and how many bytes of it the server sends before it hangs up. */
 	struct coredump_ack ack;
 	size_t bytes;
-	/* The marker the kernel answers with. */
+	/* The marker the kernel answers with, or none if @no_marker. */
 	enum coredump_mark mark;
+	bool no_marker;
 };
 
-/* Send @refused, expect the kernel to refuse it with the marker. */
+/* Send @refused, expect the kernel to refuse it and hang up. */
 static void check_refused_ack(struct __test_metadata *const _metadata,
 			      FIXTURE_DATA(coredump) *self,
 			      const struct refused_ack *refused)
@@ -577,7 +578,16 @@ static void check_refused_ack(struct __test_metadata *const _metadata,
 					     refused->bytes))
 			goto out;
 
-		if (!read_marker(fd_coredump, refused->mark))
+		/* Nothing more to say. A server that died looks the same. */
+		if (shutdown(fd_coredump, SHUT_WR))
+			goto out;
+
+		if (!refused->no_marker &&
+		    !read_marker(fd_coredump, refused->mark))
+			goto out;
+
+		/* The kernel hangs up after a refusal, marker or not. */
+		if (!read_hangup(fd_coredump))
 			goto out;
 
 		exit_code = EXIT_SUCCESS;
@@ -2409,4 +2419,197 @@ TEST_F(coredump, socket_request_negotiate_ver1)
 	check_memory_dump(_metadata, self, &choice);
 }
 
+/* An ack that picks none of KERNEL, USERSPACE and REJECT. */
+TEST_F(coredump, socket_request_no_mode)
+{
+	struct refused_ack refused = {
+		.ack = {
+			.size = sizeof(struct coredump_ack),
+			.mask = COREDUMP_WAIT,
+		},
+		.bytes = sizeof(struct coredump_ack),
+		.mark = COREDUMP_MARK_CONFLICTING,
+	};
+
+	check_refused_ack(_metadata, self, &refused);
+}
+
+/* @spare must be zero, like every field that isn't in use. */
+TEST_F(coredump, socket_request_spare)
+{
+	struct refused_ack refused = {
+		.ack = {
+			.size = sizeof(struct coredump_ack),
+			.spare = 1,
+			.mask = COREDUMP_KERNEL,
+		},
+		.bytes = sizeof(struct coredump_ack),
+		.mark = COREDUMP_MARK_UNSUPPORTED,
+	};
+
+	check_refused_ack(_metadata, self, &refused);
+}
+
+/* An ack size is a byte count. One that ends inside a field is valid. */
+#define ACK_SIZE_BETWEEN (COREDUMP_ACK_SIZE_VER0 + sizeof(__u32))
+
+/* Any size from VER0 up to what the kernel accepts works without memory types. */
+TEST_F(coredump, socket_request_ack_size_between)
+{
+	struct memory_choice choice = {
+		.task_filter = COREDUMP_MEMORY_ANON_PRIVATE |
+			       COREDUMP_MEMORY_ANON_SHARED,
+		.mask = COREDUMP_KERNEL,
+		.size_ack = ACK_SIZE_BETWEEN,
+		.shared_dumped = true,
+	};
+
+	check_memory_dump(_metadata, self, &choice);
+}
+
+/* The memory types need the whole field, not the part that happens to fit. */
+TEST_F(coredump, socket_request_memory_types_ack_size_between)
+{
+	struct refused_ack refused = {
+		.ack = {
+			.size = ACK_SIZE_BETWEEN,
+			.mask = COREDUMP_KERNEL | COREDUMP_MEMORY_TYPES,
+		},
+		.bytes = ACK_SIZE_BETWEEN,
+		.mark = COREDUMP_MARK_MINSIZE,
+	};
+
+	check_refused_ack(_metadata, self, &refused);
+}
+
+/* A server that hangs up without acking gets no marker and no coredump. */
+TEST_F(coredump, socket_request_server_hangs_up)
+{
+	struct refused_ack refused = {
+		.bytes = 0,
+		.no_marker = true,
+	};
+
+	check_refused_ack(_metadata, self, &refused);
+}
+
+/* A server that hangs up in the middle of its ack looks the same. */
+TEST_F(coredump, socket_request_ack_truncated)
+{
+	struct refused_ack refused = {
+		.ack = {
+			.size = COREDUMP_ACK_SIZE_VER0,
+			.mask = COREDUMP_KERNEL,
+		},
+		.bytes = COREDUMP_ACK_SIZE_VER0 / 2,
+		.no_marker = true,
+	};
+
+	check_refused_ack(_metadata, self, &refused);
+}
+
+/*
+ * The kernels a server built against this header can't meet here:
+ * negotiate() against their requests, no coredump involved.
+ */
+
+/* The request of a kernel with the first structs and features. */
+static const struct coredump_req req_ver0 = {
+	.size			= COREDUMP_REQ_SIZE_VER0,
+	.size_ack		= COREDUMP_ACK_SIZE_VER0,
+	.mask			= COREDUMP_KERNEL | COREDUMP_USERSPACE |
+				  COREDUMP_REJECT | COREDUMP_WAIT,
+};
+
+/* The request of this kernel. */
+static const struct coredump_req req_ver1 = {
+	.size			= COREDUMP_REQ_SIZE_VER1,
+	.size_ack		= COREDUMP_ACK_SIZE_VER1,
+	.mask			= COREDUMP_KERNEL | COREDUMP_USERSPACE |
+				  COREDUMP_REJECT | COREDUMP_WAIT |
+				  COREDUMP_RECORDS | COREDUMP_SPARSE |
+				  COREDUMP_MEMORY_TYPES,
+	.memory_types		= COREDUMP_MEMORY_ANON_PRIVATE |
+				  COREDUMP_MEMORY_ANON_SHARED,
+	.memory_types_mask	= TEST_MEMORY_ALL,
+};
+
+/* A kernel with the first structs gets the first ack and nothing newer. */
+TEST(negotiate_ver0_kernel)
+{
+	struct coredump_ack ack;
+
+	negotiate(&req_ver0, &server_build_ver1, &ack);
+	ASSERT_EQ(ack.size, COREDUMP_ACK_SIZE_VER0);
+	ASSERT_EQ(ack.mask, COREDUMP_KERNEL);
+	ASSERT_EQ(ack.memory_types, 0);
+}
+
+/* A kernel with records and sparse but the first structs: both, no types. */
+TEST(negotiate_sparse_kernel)
+{
+	struct coredump_req req = req_ver0;
+	struct coredump_ack ack;
+
+	req.mask |= COREDUMP_RECORDS | COREDUMP_SPARSE;
+	negotiate(&req, &server_build_ver1, &ack);
+	ASSERT_EQ(ack.size, COREDUMP_ACK_SIZE_VER0);
+	ASSERT_EQ(ack.mask, COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_SPARSE);
+	ASSERT_EQ(ack.memory_types, 0);
+}
+
+/* Records without sparse: sparse isn't raised on its own. */
+TEST(negotiate_records_without_sparse)
+{
+	struct coredump_req req = req_ver0;
+	struct coredump_ack ack;
+
+	req.mask |= COREDUMP_RECORDS;
+	negotiate(&req, &server_build_ver1, &ack);
+	ASSERT_EQ(ack.mask, COREDUMP_KERNEL | COREDUMP_RECORDS);
+}
+
+/*
+ * A feature whose ack field lies past what the kernel accepts can't be
+ * raised. No kernel offers the memory types without the room for them, so a
+ * request that does stands in for a feature newer than this header.
+ */
+TEST(negotiate_types_need_room)
+{
+	struct coredump_req req = req_ver0;
+	struct coredump_ack ack;
+
+	req.mask |= COREDUMP_MEMORY_TYPES;
+	negotiate(&req, &server_build_ver1, &ack);
+	ASSERT_EQ(ack.size, COREDUMP_ACK_SIZE_VER0);
+	ASSERT_EQ(ack.mask, COREDUMP_KERNEL);
+	ASSERT_EQ(ack.memory_types, 0);
+}
+
+/* This kernel: the policy applied to the task's selection. */
+TEST(negotiate_ver1_kernel)
+{
+	struct coredump_ack ack;
+
+	negotiate(&req_ver1, &server_build_ver1, &ack);
+	ASSERT_EQ(ack.size, COREDUMP_ACK_SIZE_VER1);
+	ASSERT_EQ(ack.mask, COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_SPARSE |
+			    COREDUMP_MEMORY_TYPES);
+	ASSERT_EQ(ack.memory_types, COREDUMP_MEMORY_ANON_PRIVATE |
+				     COREDUMP_MEMORY_ELF_HEADERS);
+}
+
+/* A kernel that doesn't know a type the policy adds isn't asked for it. */
+TEST(negotiate_unknown_type)
+{
+	struct coredump_req req = req_ver1;
+	struct coredump_ack ack;
+
+	req.memory_types_mask &= ~(__u64)COREDUMP_MEMORY_ELF_HEADERS;
+	negotiate(&req, &server_build_ver1, &ack);
+	ASSERT_EQ(ack.mask, COREDUMP_KERNEL | COREDUMP_RECORDS | COREDUMP_SPARSE |
+			    COREDUMP_MEMORY_TYPES);
+	ASSERT_EQ(ack.memory_types, COREDUMP_MEMORY_ANON_PRIVATE);
+}
+
 TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.c b/tools/testing/selftests/coredump/coredump_test_helpers.c
index ab94c45cd8be..4e36e3e4fb78 100644
--- a/tools/testing/selftests/coredump/coredump_test_helpers.c
+++ b/tools/testing/selftests/coredump/coredump_test_helpers.c
@@ -1467,6 +1467,29 @@ bool read_marker(int fd, enum coredump_mark mark)
 	return ret == mark;
 }
 
+/*
+ * The kernel hung up without sending anything more: end of stream, or a
+ * reset if it refused the ack on its peeked size and never read it.
+ */
+bool read_hangup(int fd)
+{
+	ssize_t ret;
+	char c;
+
+	ret = recv(fd, &c, sizeof(c), MSG_WAITALL);
+	if (ret == 0) {
+		fprintf(stderr, "Kernel closed the connection\n");
+		return true;
+	}
+	if (ret < 0 && errno == ECONNRESET) {
+		fprintf(stderr, "Kernel closed the connection with the ack unread\n");
+		return true;
+	}
+
+	fprintf(stderr, "%s: expected a hangup, got %zd: %m\n", __func__, ret);
+	return false;
+}
+
 /* Read the request as a server built with a @user_size byte struct does. */
 bool read_coredump_req_sized(int fd, struct coredump_req *req, size_t user_size)
 {
@@ -1593,11 +1616,16 @@ bool send_coredump_ack(int fd, const struct coredump_req *req,
 
 bool check_coredump_req(const struct coredump_req *req)
 {
-	if (req->size < COREDUMP_REQ_SIZE_VER1) {
-		fprintf(stderr, "%s: size %u below minimum %d\n",
+	if (req->size != COREDUMP_REQ_SIZE_VER1) {
+		fprintf(stderr, "%s: size %u, expected %d\n",
 			__func__, req->size, COREDUMP_REQ_SIZE_VER1);
 		return false;
 	}
+	if (req->size_ack != COREDUMP_ACK_SIZE_VER1) {
+		fprintf(stderr, "%s: size_ack %u, expected %d\n",
+			__func__, req->size_ack, COREDUMP_ACK_SIZE_VER1);
+		return false;
+	}
 	if (req->mask != TEST_REQ_MASK_ALL) {
 		fprintf(stderr, "%s: mask 0x%llx, expected 0x%llx\n",
 			__func__, (unsigned long long)req->mask,
diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.h b/tools/testing/selftests/coredump/coredump_test_helpers.h
index 8e0187645c93..3f2f87837558 100644
--- a/tools/testing/selftests/coredump/coredump_test_helpers.h
+++ b/tools/testing/selftests/coredump/coredump_test_helpers.h
@@ -64,6 +64,7 @@ bool get_pidfd_info(int fd_peer_pidfd, struct pidfd_info *info);
 /* Protocol helper function declarations */
 ssize_t recv_marker(int fd);
 bool read_marker(int fd, enum coredump_mark mark);
+bool read_hangup(int fd);
 bool read_coredump_req(int fd, struct coredump_req *req);
 bool read_coredump_req_sized(int fd, struct coredump_req *req, size_t user_size);
 bool send_coredump_ack(int fd, const struct coredump_req *req,

-- 
2.53.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.