From: Christian Brauner <brauner@kernel.org>
To: linux-fsdevel@vger.kernel.org
Cc: Jacob Lalonde <jalalonde@meta.com>,
Josef Bacik <josef@toxicpanda.com>, Jann Horn <jannh@google.com>,
Alexander Viro <viro@zeniv.linux.org.uk>,
Jan Kara <jack@suse.cz>,
Andrew Morton <akpm@linux-foundation.org>,
David Hildenbrand <david@kernel.org>,
Lorenzo Stoakes <ljs@kernel.org>,
"Liam R. Howlett" <liam@infradead.org>,
Vlastimil Babka <vbabka@kernel.org>,
Mike Rapoport <rppt@kernel.org>,
Suren Baghdasaryan <surenb@google.com>,
Michal Hocko <mhocko@suse.com>,
Omar Sandoval <osandov@osandov.com>,
Jacob Lalonde <jalalonde@fb.com>, Shuah Khan <shuah@kernel.org>,
linux-kernel@vger.kernel.org, linux-mm@kvack.org,
linux-kselftest@vger.kernel.org, linuxppc-dev@lists.ozlabs.org,
"Christian Brauner (Amutable)" <brauner@kernel.org>
Subject: [PATCH 6/6] selftests/coredump: test failed handshakes
Date: Fri, 21 Aug 2026 13:52:07 +0200 [thread overview]
Message-ID: <20260821-work-coredump-filter-v1-6-91f9a73ef03e@kernel.org> (raw)
In-Reply-To: <20260821-work-coredump-filter-v1-0-91f9a73ef03e@kernel.org>
Add more coredump refusal tests.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
.../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
prev parent reply other threads:[~2026-08-21 11:53 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-21 11:52 [PATCH 0/6] coredump: select memory types per request Christian Brauner
2026-08-21 11:52 ` [PATCH 1/6] coredump: select memory types to include Christian Brauner
2026-08-21 11:52 ` [PATCH 2/6] tools: sync coredump.h header Christian Brauner
2026-08-21 11:52 ` [PATCH 3/6] selftests/coredump: simplify the refusal tests Christian Brauner
2026-08-21 11:52 ` [PATCH 4/6] selftests/coredump: test COREDUMP_MEMORY_TYPES Christian Brauner
2026-08-21 11:52 ` [PATCH 5/6] selftests/coredump: improve coredump size negotiation tests Christian Brauner
2026-08-21 11:52 ` Christian Brauner [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260821-work-coredump-filter-v1-6-91f9a73ef03e@kernel.org \
--to=brauner@kernel.org \
--cc=akpm@linux-foundation.org \
--cc=david@kernel.org \
--cc=jack@suse.cz \
--cc=jalalonde@fb.com \
--cc=jalalonde@meta.com \
--cc=jannh@google.com \
--cc=josef@toxicpanda.com \
--cc=liam@infradead.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=linuxppc-dev@lists.ozlabs.org \
--cc=ljs@kernel.org \
--cc=mhocko@suse.com \
--cc=osandov@osandov.com \
--cc=rppt@kernel.org \
--cc=shuah@kernel.org \
--cc=surenb@google.com \
--cc=vbabka@kernel.org \
--cc=viro@zeniv.linux.org.uk \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox