Hello,

The copy-in reader allocates two buffers sized directly from archive
header length fields before reading any data:

  - read_name_from_file(): the file name size (namesize; up to nearly
    4 GiB in the new ASCII formats), via cpio_realloc_c_name();
  - get_link_name(): the size of a symbolic link's target (filesize; up
    to nearly 4 GiB in new ASCII, 8 GiB in old ASCII), via xmalloc().

A corrupt header can claim far more data than the archive holds, so a
~110-byte archive forces a multi-gigabyte allocation -- a memory
exhaustion DoS reachable even when only listing (cpio -t / cpio -tv).
It is the same class as CVE-2026-66485 (alloca in make_path), at sites
that fix did not cover. Present in current git HEAD and in 2.15.

The attached patch grows such buffers in bounded increments as bytes
actually arrive (a new helper, read_counted_data(), used by both
sites), so a bogus length fails on the inevitable short read after a
small allocation, while legitimate names and link targets of any
length are still read in full. No new length limits are introduced.

Reproduce (ASan build):
  ASAN_OPTIONS=max_allocation_size_mb=256:allocator_may_return_null=0 cpio
-t  < poc_namesize_dos_114bytes.bin
    -> unpatched: ~284 MB allocation in cpio_realloc_c_name
  ASAN_OPTIONS=max_allocation_size_mb=256:allocator_may_return_null=0 cpio
-tv < poc_symlink_target_dos_116bytes.bin
    -> unpatched: 512 MB malloc in get_link_name
Patched, both stop with "premature end of file".

Testing on a patched build: newc and odc archives containing symlinks
list and extract correctly; a 9000-byte symlink target lists
byte-identically to unpatched cpio; "make check" passes (all 17 tests).

The attached patch is git-am ready.

Thanks,
my GitHub
ghox789

Attachment: poc_symlink_target_dos_116bytes.bin
Description: Binary data

From c620ba470a178358eb7961101ff84a64e963ff5b Mon Sep 17 00:00:00 2001
From: ghox789 <[email protected]>
Date: Wed, 14 Oct 2026 05:36:35 +0400
Subject: [PATCH] Never size archive-read buffers from untrusted header
 lengths.

Two places in the copy-in reader allocated a buffer sized directly from a
length field of the archive header before reading any data:

  - read_name_from_file: the file name size (namesize; up to nearly
    4 GiB in the new ASCII formats), via cpio_realloc_c_name;
  - get_link_name: the size of a symbolic link's target (filesize; up
    to nearly 4 GiB in new ASCII, 8 GiB in old ASCII), via xmalloc.

A corrupt header can claim far more data than the archive holds, so a
110-byte archive could force a multi-gigabyte allocation -- a
memory-exhaustion denial of service reachable even when only listing an
archive (cpio -t / cpio -tv).  This is the same class of flaw as
CVE-2026-66485 (alloca in make_path), at sites that fix did not cover.

Grow such buffers in bounded increments as bytes actually arrive, so a
bogus length fails on the inevitable short read after a small allocation
while legitimate names and link targets of any length are still read in
full.

* src/copyin.c (ARCHIVE_READ_INCREMENT): New macro.
(read_counted_data): New function.
(get_link_name): Use it instead of allocating the claimed size up front.
(read_name_from_file): Read the file name in ARCHIVE_READ_INCREMENT
chunks instead of allocating the claimed size up front.
---
 src/copyin.c | 61 +++++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 56 insertions(+), 5 deletions(-)

diff --git a/src/copyin.c b/src/copyin.c
index 46d5503..36176d5 100644
--- a/src/copyin.c
+++ b/src/copyin.c
@@ -106,6 +106,48 @@ tape_skip_padding (int in_file_des, off_t offset)
     tape_toss_input (in_file_des, pad);
 }
 
+/* Archive headers carry lengths (the file name size, the size of a symbolic
+   link's target) that determine how many bytes are read into a buffer.  A
+   corrupt header can claim far more data than the archive holds, so such
+   buffers must never be allocated up front from the claimed length: they are
+   grown in increments of ARCHIVE_READ_INCREMENT as bytes actually arrive, and
+   a bogus length fails on the inevitable short read after a small allocation.
+   This is not a limit on legitimate lengths.  */
+#define ARCHIVE_READ_INCREMENT 4096
+
+/* Read LEN bytes from IN_DES into a newly allocated buffer of LEN + 1 bytes
+   and NUL-terminate it, growing the buffer as data arrives so that the
+   allocation never exceeds about twice the data actually read.  LEN must be
+   less than SIZE_MAX.  */
+static char *
+read_counted_data (int in_des, uintmax_t len)
+{
+  char *buf = NULL;
+  size_t bufsize = 0;
+  uintmax_t done = 0;
+
+  do
+    {
+      size_t chunk = (len - done < ARCHIVE_READ_INCREMENT
+		      ? (size_t) (len - done) : ARCHIVE_READ_INCREMENT);
+      if (bufsize < done + chunk + 1)
+	{
+	  size_t want = bufsize ? 2 * bufsize : ARCHIVE_READ_INCREMENT;
+	  if (want < done + chunk + 1)
+	    want = done + chunk + 1;
+	  if (want > len + 1)
+	    want = len + 1;
+	  buf = xrealloc (buf, want);
+	  bufsize = want;
+	}
+      tape_buffered_read (buf + done, in_des, chunk);
+      done += chunk;
+    }
+  while (done < len);
+  buf[len] = '\0';
+  return buf;
+}
+
 static char *
 get_link_name (struct cpio_file_stat *file_hdr, int in_file_des)
 {
@@ -119,9 +161,7 @@ get_link_name (struct cpio_file_stat *file_hdr, int in_file_des)
     }
   else
     {
-      link_name = xmalloc (file_hdr->c_filesize + 1);
-      tape_buffered_read (link_name, in_file_des, file_hdr->c_filesize);
-      link_name[file_hdr->c_filesize] = '\0';
+      link_name = read_counted_data (in_file_des, file_hdr->c_filesize);
       tape_skip_padding (in_file_des, file_hdr->c_filesize);
     }
   return link_name;
@@ -1175,8 +1215,19 @@ read_name_from_file (struct cpio_file_stat *file_hdr, int fd, uintmax_t len)
     }
   else
     {
-      cpio_realloc_c_name (file_hdr, len);
-      tape_buffered_read (file_hdr->c_name, fd, len);
+      /* A corrupt header can claim a file name far larger than the archive
+	 actually contains.  Read the name in bounded increments so that such a
+	 bogus size cannot drive a huge speculative allocation (a
+	 memory-exhaustion denial of service) before the inevitable short read
+	 is detected.  A valid name of any length is still read in full.  */
+      uintmax_t done;
+      for (done = 0; done < len; done += ARCHIVE_READ_INCREMENT)
+	{
+	  size_t chunk = (len - done < ARCHIVE_READ_INCREMENT
+			  ? (size_t) (len - done) : ARCHIVE_READ_INCREMENT);
+	  cpio_realloc_c_name (file_hdr, done + chunk);
+	  tape_buffered_read (file_hdr->c_name + done, fd, chunk);
+	}
       if (file_hdr->c_name[len-1] != 0)
 	{
 	  error (0, 0, _("malformed header: file name is not nul-terminated"));
-- 
2.53.0

Attachment: poc_namesize_dos_114bytes.bin
Description: Binary data

Reply via email to