handle_core_item () alloca ()s n_descsz bytes when a core note is a
single bit-array ("b"/"B") item, such as NT_386_IOPERM. n_descsz is
taken straight from the untrusted ELF/core file with no upper bound, so
a malformed core claiming a multi-megabyte descriptor exhausts the
process stack and crashes readelf (DoS).
Cap the allocation at 64 KiB and derive count/convsize from the capped
size, so convert () cannot write past data either. Real ioperm
bitmaps (one bit per I/O port, at most 8 KiB) are unaffected; only
pathological inputs are truncated.
* src/readelf.c (handle_core_item): Cap the bit-array alloca at
64 KiB; derive count and convsize from the capped size.
* tests/core-ioperm-huge.c: New generator for a core with a
16 MiB NT_386_IOPERM note (sparse descriptor).
* tests/run-readelf-core-ioperm-huge.sh: New test.
* tests/Makefile.am (check_PROGRAMS, TESTS, EXTRA_DIST): Add
core-ioperm-huge and run-readelf-core-ioperm-huge.sh.
(core_ioperm_huge_LDADD): New.
* tests/.gitignore: Add core-ioperm-huge.
Signed-off-by: Kane Wang <[email protected]>
---
src/readelf.c | 10 ++-
tests/.gitignore | 1 +
tests/Makefile.am | 4 +
tests/core-ioperm-huge.c | 122 ++++++++++++++++++++++++++
tests/run-readelf-core-ioperm-huge.sh | 40 +++++++++
5 files changed, 175 insertions(+), 2 deletions(-)
create mode 100644 tests/core-ioperm-huge.c
create mode 100755 tests/run-readelf-core-ioperm-huge.sh
diff --git a/src/readelf.c b/src/readelf.c
index 9d3bbbf6..631c2112 100644
--- a/src/readelf.c
+++ b/src/readelf.c
@@ -13018,8 +13018,14 @@ handle_core_item (Elf *core, const GElf_Ehdr *ehdr,
{
if (*repeated_size > size && (item->format == 'b' || item->format ==
'B'))
{
- data = alloca (*repeated_size);
- count *= *repeated_size / size;
+ /* The descriptor size of a core note comes from an untrusted
+ ELF file. Cap the stack allocation so a malformed note that
+ claims a very large n_descsz cannot exhaust the process
+ stack and crash readelf. count and convsize are derived
+ from the capped size so convert () cannot write past data. */
+ size_t alloc_size = MIN (*repeated_size, 64 * 1024);
+ data = alloca (alloc_size);
+ count *= alloc_size / size;
convsize = count * size;
*repeated_size -= convsize;
}
diff --git a/tests/.gitignore b/tests/.gitignore
index 72b02a5f..37ffe9ce 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -13,6 +13,7 @@
/arsymtest
/ar-extract-ar
/ar-getarsym-mem
+/core-ioperm-huge
/asm-tst1
/asm-tst2
/asm-tst3
diff --git a/tests/Makefile.am b/tests/Makefile.am
index e5e03737..08c0a23d 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -69,6 +69,7 @@ check_PROGRAMS = arextract arsymtest newfile saridx scnnames
sectiondump \
eu_search_cfi eu_search_macros \
eu_search_lines eu_search_die \
ar-extract-ar elf-from-memory ar-getarsym-mem \
+ core-ioperm-huge \
$(asm_TESTS)
asm_TESTS = asm-tst1 asm-tst2 asm-tst3 asm-tst4 asm-tst5 \
@@ -150,6 +151,7 @@ TESTS = run-arextract.sh run-arsymtest.sh run-ar.sh newfile
test-nlist \
run-readelf-compressed.sh \
run-readelf-const-values.sh \
run-readelf-cu-index.sh \
+ run-readelf-core-ioperm-huge.sh \
run-varlocs-self.sh run-exprlocs-self.sh \
run-readelf-test1.sh run-readelf-test2.sh run-readelf-test3.sh \
run-readelf-test4.sh run-readelf-twofiles.sh \
@@ -372,6 +374,7 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh run-ar.sh \
run-readelf-compressed-zstd.sh \
run-readelf-const-values.sh testfile-const-values.debug.bz2 \
run-readelf-cu-index.sh \
+ run-readelf-core-ioperm-huge.sh \
run-addrcfi.sh run-dwarfcfi.sh \
testfile11-debugframe.bz2 testfile12-debugframe.bz2 \
testfileaarch64-debugframe.bz2 testfilearm-debugframe.bz2 \
@@ -787,6 +790,7 @@ arextract_LDADD = $(libelf)
arsymtest_LDADD = $(libelf)
ar_extract_ar_LDADD = $(libelf)
ar_getarsym_mem_LDADD = $(libelf)
+core_ioperm_huge_LDADD =
newfile_LDADD = $(libelf)
saridx_LDADD = $(libeu) $(libelf)
scnnames_LDADD = $(libelf)
diff --git a/tests/core-ioperm-huge.c b/tests/core-ioperm-huge.c
new file mode 100644
index 00000000..c50ba73e
--- /dev/null
+++ b/tests/core-ioperm-huge.c
@@ -0,0 +1,122 @@
+/* Build a minimal ET_CORE ELF with a single NT_386_IOPERM note whose
+ n_descsz is far larger than a typical process stack, to exercise the
+ bit-array ("b"/"B") path in handle_core_item.
+ Copyright (C) 2026 KylinSoft Co., Ltd.
+ This file is part of elfutils.
+
+ This file is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ elfutils is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* NT_386_IOPERM lives in elfutils' own elf.h but not necessarily in the
+ system <elf.h>, so define it when missing. */
+#include <elf.h>
+#ifndef NT_386_IOPERM
+# define NT_386_IOPERM 0x201
+#endif
+
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+/* Larger than any plausible process stack so an unfixed readelf, which
+ alloca()s n_descsz bytes, crashes; a fixed one caps the allocation. */
+#define DESCSZ (16 * 1024 * 1024)
+
+static void
+put (int fd, const void *buf, size_t n)
+{
+ if (write (fd, buf, n) != (ssize_t) n)
+ {
+ perror ("write");
+ _exit (2);
+ }
+}
+
+int
+main (int argc, char **argv)
+{
+ if (argc != 2)
+ {
+ fprintf (stderr, "usage: %s output.core\n", argv[0]);
+ return 2;
+ }
+
+ int fd = open (argv[1], O_WRONLY | O_CREAT | O_TRUNC, 0600);
+ if (fd < 0)
+ {
+ perror ("open");
+ return 2;
+ }
+
+ /* Layout: Elf64_Ehdr (64) | Elf64_Phdr (56) | Elf64_Nhdr (12)
+ * | "CORE\0" padded to 8 | desc (DESCSZ, sparse). */
+ const size_t phoff = sizeof (Elf64_Ehdr);
+ const size_t note_off = phoff + sizeof (Elf64_Phdr);
+ const char name[] = "CORE";
+ const size_t name_padded = (sizeof (name) + 3) & ~(size_t) 3; /* 8 */
+ const size_t filesz = sizeof (Elf64_Nhdr) + name_padded + DESCSZ;
+
+ Elf64_Ehdr ehdr;
+ memset (&ehdr, 0, sizeof ehdr);
+ memcpy (ehdr.e_ident, ELFMAG, SELFMAG);
+ ehdr.e_ident[EI_CLASS] = ELFCLASS64;
+ ehdr.e_ident[EI_DATA] = ELFDATA2LSB;
+ ehdr.e_ident[EI_VERSION] = EV_CURRENT;
+ ehdr.e_type = ET_CORE;
+ ehdr.e_machine = EM_X86_64;
+ ehdr.e_version = EV_CURRENT;
+ ehdr.e_phoff = phoff;
+ ehdr.e_ehsize = sizeof (Elf64_Ehdr);
+ ehdr.e_phentsize = sizeof (Elf64_Phdr);
+ ehdr.e_phnum = 1;
+ put (fd, &ehdr, sizeof ehdr);
+
+ Elf64_Phdr phdr;
+ memset (&phdr, 0, sizeof phdr);
+ phdr.p_type = PT_NOTE;
+ phdr.p_flags = PF_R;
+ phdr.p_offset = note_off;
+ phdr.p_filesz = filesz;
+ phdr.p_memsz = filesz;
+ phdr.p_align = 4;
+ put (fd, &phdr, sizeof phdr);
+
+ Elf64_Nhdr nhdr;
+ memset (&nhdr, 0, sizeof nhdr);
+ nhdr.n_namesz = sizeof (name); /* 5, includes the NUL */
+ nhdr.n_descsz = DESCSZ;
+ nhdr.n_type = NT_386_IOPERM;
+ put (fd, &nhdr, sizeof nhdr);
+
+ char namebuf[8];
+ memset (namebuf, 0, sizeof namebuf);
+ memcpy (namebuf, name, sizeof (name));
+ put (fd, namebuf, name_padded);
+
+ /* Make the descriptor a sparse hole instead of writing 16 MiB. */
+ if (lseek (fd, (off_t) (note_off + filesz - 1), SEEK_SET) == (off_t) -1
+ || write (fd, "", 1) != 1)
+ {
+ perror ("lseek/write");
+ _exit (2);
+ }
+
+ if (close (fd) != 0)
+ {
+ perror ("close");
+ return 2;
+ }
+ return 0;
+}
diff --git a/tests/run-readelf-core-ioperm-huge.sh
b/tests/run-readelf-core-ioperm-huge.sh
new file mode 100755
index 00000000..e0a6a8bf
--- /dev/null
+++ b/tests/run-readelf-core-ioperm-huge.sh
@@ -0,0 +1,40 @@
+#! /usr/bin/env bash
+# Copyright (C) 2026 KylinSoft Co., Ltd.
+# This file is part of elfutils.
+#
+# This file is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# elfutils is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+. $srcdir/test-subr.sh
+
+# A core note's n_descsz is taken straight from the untrusted ELF file.
+# For a bit-array ("b"/"B") core item such as NT_386_IOPERM, handle_core_item
+# in readelf used to alloca() n_descsz bytes. A malformed core claiming a
+# 16 MiB descriptor would exhaust the process stack and crash readelf.
+# The descriptor is kept as a sparse hole, so the input is tiny on disk.
+
+tempfiles core-ioperm-huge.core
+
+testrun ${abs_builddir}/core-ioperm-huge core-ioperm-huge.core
+
+# readelf must not crash; it caps the stack allocation and prints the
+# (here empty) ioperm bitmap normally.
+testrun_compare ${abs_top_builddir}/src/readelf -n core-ioperm-huge.core <<EOF
+
+Note segment of 16777236 bytes at offset 0x78:
+ Owner Data size Type
+ CORE 16777216 386_IOPERM
+ ioperm: <>
+EOF
+
+exit 0
--
2.43.0