[PATCH 10/11] coredump: describe the holes when COREDUMP_SPARSE is negotiated

From: Christian Brauner

Date: Tue Aug 11 2026 - 11:34:50 EST


Offer COREDUMP_SPARSE in coredump_req->mask now that there is something
behind it. Refuse it without COREDUMP_HEADER. A zero frame cannot exist
outside a framed stream.

A trailing hole is flushed by coredump_write() the way it always was. So
its last byte goes out as a one-byte data frame and the frames cover the
whole coredump.

Signed-off-by: Christian Brauner (Amutable) <brauner@xxxxxxxxxx>
---
fs/coredump.c | 34 +++++++++++++++++++++-
.../selftests/coredump/coredump_test_helpers.c | 3 +-
2 files changed, 35 insertions(+), 2 deletions(-)

diff --git a/fs/coredump.c b/fs/coredump.c
index 364c89c5f82a..bdf2eae948d0 100644
--- a/fs/coredump.c
+++ b/fs/coredump.c
@@ -799,7 +799,7 @@ static bool coredump_sock_request(struct core_name *cn, struct coredump_params *
.size = sizeof(struct coredump_req),
.mask = COREDUMP_KERNEL | COREDUMP_USERSPACE |
COREDUMP_REJECT | COREDUMP_WAIT |
- COREDUMP_HEADER,
+ COREDUMP_HEADER | COREDUMP_SPARSE,
.size_ack = sizeof(struct coredump_ack),
};
struct coredump_ack ack = {};
@@ -854,6 +854,12 @@ static bool coredump_sock_request(struct core_name *cn, struct coredump_params *
return false;
}

+ /* Zero frames only exist inside a framed stream. */
+ if ((ack.mask & COREDUMP_SPARSE) && !(ack.mask & COREDUMP_HEADER)) {
+ coredump_sock_mark(cprm->file, COREDUMP_MARK_CONFLICTING);
+ return false;
+ }
+
if (ack.spare) {
coredump_sock_mark(cprm->file, COREDUMP_MARK_UNSUPPORTED);
return false;
@@ -1229,6 +1235,11 @@ static bool dump_framed(const struct coredump_params *cprm)
return cprm->mask & COREDUMP_HEADER;
}

+static bool dump_sparse(const struct coredump_params *cprm)
+{
+ return cprm->mask & COREDUMP_SPARSE;
+}
+
/* Describe the next @len bytes of the coredump. Returns the header size. */
static size_t dump_frame_init(struct coredump_params *cprm,
enum coredump_frame_type type, u64 len)
@@ -1292,11 +1303,32 @@ static int __dump_emit(struct coredump_params *cprm, const void *addr, int nr)
return dump_write_iter(cprm, &iter, nr);
}

+/* Hand the server the length of the hole instead of the hole itself. */
+static int dump_skip_frame(struct coredump_params *cprm, size_t nr)
+{
+ struct kvec kvec;
+ struct iov_iter iter;
+ size_t hdr;
+
+ if (dump_interrupted())
+ return 0;
+
+ hdr = dump_frame_init(cprm, COREDUMP_FRAME_ZERO, nr);
+ kvec.iov_base = cprm->frame;
+ kvec.iov_len = hdr;
+ iov_iter_kvec(&iter, ITER_SOURCE, &kvec, 1, hdr);
+
+ return dump_write_iter(cprm, &iter, nr);
+}
+
static int __dump_skip(struct coredump_params *cprm, size_t nr)
{
static char zeroes[PAGE_SIZE];
struct file *file = cprm->file;

+ if (dump_sparse(cprm))
+ return dump_skip_frame(cprm, nr);
+
if (file->f_mode & FMODE_LSEEK) {
if (dump_interrupted() || vfs_llseek(file, nr, SEEK_CUR) < 0)
return 0;
diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.c b/tools/testing/selftests/coredump/coredump_test_helpers.c
index bfe10bc51618..ee669969605b 100644
--- a/tools/testing/selftests/coredump/coredump_test_helpers.c
+++ b/tools/testing/selftests/coredump/coredump_test_helpers.c
@@ -290,7 +290,8 @@ bool send_coredump_ack(int fd, const struct coredump_req *req,
/* Every option the kernel is expected to advertise in coredump_req->mask. */
#define COREDUMP_REQ_MASK_ALL \
(COREDUMP_KERNEL | COREDUMP_USERSPACE | \
- COREDUMP_REJECT | COREDUMP_WAIT | COREDUMP_HEADER)
+ COREDUMP_REJECT | COREDUMP_WAIT | \
+ COREDUMP_HEADER | COREDUMP_SPARSE)

bool check_coredump_req(const struct coredump_req *req)
{

--
2.53.0