[PATCH] coredump: add filesz truncation and filter

Jacob Lalonde <[email protected]> Fri, 31 Jul 2026 10:13:36 -0700
Newsgroups org.kernel.vger.linux-fsdevel,org.kernel.vger.linux-kernel,org.kvack.linux-mm
Message-ID <[email protected]>
From: Jacob Lalonde <[email protected]>

This adds opt-in truncation of Coredump segments to
`/proc/pid/coredump_filter`. The primary motivation here is to reduce the
number of zero pages written via pipe to a user space coredump process. In
my testing this reduces coredump size, and thus IO by ~40% at Meta. With
some outliers such as PyTorch's TBE weights being loaded in right before a
crash being closer to 99%.

We achieve this by having the Kernel not emit the trailing zeros of any
VMA, resulting in a PT_LOAD with filesz < memsz. I verified that both GDB
from v16 onward and trunk LLDB support loading cores with truncated
sections, however LLDB won't report zeros for the truncated region

I elected to iterate page by page for simplicity, and because we
short-circuit when encountering the first faulted in page, meaning time
spent iterating should directly replace slower copying and pipe IPC.

Below is an example program where the entire 1 GB VMA would be emitted,
where as with the patch only the first page would be written.

int main(void) {
     void *p = mmap(NULL, MMAP_SIZE, PROT_READ | PROT_WRITE,
                    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

    int* p2 = (int*)p;
    /* write a sentinel to make sure it's not fully sparse */
    p2[0] = 0xDEADBEEF;

    abort();
}

Signed-off-by: Jacob Lalonde <[email protected]>
---
 fs/coredump.c            | 44 +++++++++++++++++++++++++++++++++++++---
 include/linux/mm_types.h |  3 ++-
 2 files changed, 43 insertions(+), 4 deletions(-)

diff --git a/fs/coredump.c b/fs/coredump.c
index e68a76ff92a3..b6cee412eb99 100644
--- a/fs/coredump.c
+++ b/fs/coredump.c
@@ -1588,6 +1588,42 @@ static bool always_dump_vma(struct vm_area_struct *vma)
 
 #define DUMP_SIZE_MAYBE_ELFHDR_PLACEHOLDER 1
 
+/*
+ * Truncate the file_sz of the VMA to the last faulted page
+ */
+static unsigned long truncate_vma(struct vm_area_struct *vma)
+{
+	/*
+	 * Same logic as dump_user_range, where we enumerate the pages
+	 * in a VMA, but instead of skipping un-faulted pages, we move the VMA
+	 * end
+	 */
+	struct page *page;
+	unsigned long truncated_end;
+
+	/* We're already under the mmap lock */
+	int locked = 1;
+
+	for (truncated_end = vma->vm_end; truncated_end > vma->vm_start; truncated_end -= PAGE_SIZE) {
+		/*
+		 * Because we're iterating backwards, we need to
+		 * look at the page before the current address
+		 */
+		unsigned long probe_addr = truncated_end - PAGE_SIZE;
+
+		page = get_dump_page(probe_addr, &locked);
+		/*
+		 * We hit a faulted page, exit
+		 */
+		if (page) {
+			put_page(page);
+			break;
+		}
+	}
+
+	return truncated_end - vma->vm_start;
+}
+
 /*
  * Decide how much of @vma's contents should be included in a core dump.
  */
@@ -1662,13 +1698,15 @@ static unsigned long vma_dump_size(struct vm_area_struct *vma,
 		return DUMP_SIZE_MAYBE_ELFHDR_PLACEHOLDER;
 	}
 
-#undef	FILTER
-
 	return 0;
 
 whole:
-	return vma->vm_end - vma->vm_start;
+	if (FILTER(TRUNCATE_SPARSE_VMAS))
+		return truncate_vma(vma);
+	else
+		return vma->vm_end - vma->vm_start;
 }
+#undef	FILTER
 
 /*
  * Helper function for iterating across a vma list.  It ensures that the caller
diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index b18c2b2e7d2c..18cf8304f698 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -1932,9 +1932,10 @@ enum {
 #define MMF_DUMP_HUGETLB_SHARED  8
 #define MMF_DUMP_DAX_PRIVATE	9
 #define MMF_DUMP_DAX_SHARED	10
+#define MMF_DUMP_TRUNCATE_SPARSE_VMAS 11
 
 #define MMF_DUMP_FILTER_SHIFT	MMF_DUMPABLE_BITS
-#define MMF_DUMP_FILTER_BITS	9
+#define MMF_DUMP_FILTER_BITS	10
 #define MMF_DUMP_FILTER_MASK \
 	((BIT(MMF_DUMP_FILTER_BITS) - 1) << MMF_DUMP_FILTER_SHIFT)
 #define MMF_DUMP_FILTER_DEFAULT \
-- 
2.53.0-Meta