Hi Sayed, On Wed, 2026-06-17 at 16:01 +0530, Sayed Kaif wrote: > libdw: A crafted .debug_macro section with opcode 0 makes (opcode - 1) > wrap to 0xFFFFFFFF in the unsigned array index, writing a 16-byte struct > past the 255-element op_protos array. Reject opcode == 0 and add the > missing readp >= endp checks before reading the count and opcode bytes. > > libdwfl: When rebuilding an ELF from process memory, segments_end used > the last PT_LOAD segment's end rather than the largest, so a later > smaller segment could shrink contents_size below an earlier segment's > start, underflowing end - start into a huge length and overflowing the > heap buffer. Skip any segment that starts past contents_size, matching > the guard already used in dwfl_segment_report_module.
Nice finds. Would you mind if I split this patch in two for the two separate issues? So the first would be covered by: > * libdw/dwarf_getmacros.c (get_macinfo_table): Reject opcode 0, > add bounds checks. > * tests/testfile-macros-opcode0.bz2: New test input. > * tests/run-dwarf-getmacros.sh: Test it. > * tests/Makefile.am (EXTRA_DIST): Add new test file. And the second just by: > * libdwfl/elf-from-memory.c (elf_from_remote_memory): Skip > out-of-range segment. > Signed-off-by: Sayed Kaif <[email protected]> > --- > libdw/dwarf_getmacros.c | 12 ++++++++++++ > libdwfl/elf-from-memory.c | 8 ++++++++ > tests/Makefile.am | 2 +- > tests/run-dwarf-getmacros.sh | 8 ++++++++ > tests/testfile-macros-opcode0.bz2 | Bin 0 -> 166 bytes > 5 files changed, 29 insertions(+), 1 deletion(-) > create mode 100644 tests/testfile-macros-opcode0.bz2 > > diff --git a/libdw/dwarf_getmacros.c b/libdw/dwarf_getmacros.c > index d7ed7b58..bb2272ea 100644 > --- a/libdw/dwarf_getmacros.c > +++ b/libdw/dwarf_getmacros.c > @@ -255,11 +255,23 @@ get_table_for_offset (Dwarf *dbg, Dwarf_Word macoff, > > if ((flags & 0x4) != 0) > { > + if (readp >= endp) > + goto invalid_dwarf; > unsigned count = *readp++; Yes, clearly missing guard. > for (unsigned i = 0; i < count; ++i) > { > + if (readp >= endp) > + goto invalid; > unsigned opcode = *readp++; Likewise. > + /* Opcode 0 is not allocated (and 0xff means "not stored"). > + Reject it here: without this check the unsigned expression > + opcode - 1 wraps to UINT_MAX for opcode == 0, and the > + assignment below would write a Dwarf_Macro_Op_Proto far out > + of the bounds of the op_protos[255] stack array. */ > + if (opcode == 0) > + goto invalid; Correct the first opcode is DW_MACRO_define (0x1), stored in op_protos as zero element. > Dwarf_Macro_Op_Proto e; > if (readp >= endp) > goto invalid; > diff --git a/libdwfl/elf-from-memory.c b/libdwfl/elf-from-memory.c > index e9b330fd..14b2f5c8 100644 > --- a/libdwfl/elf-from-memory.c > +++ b/libdwfl/elf-from-memory.c > @@ -304,6 +304,14 @@ elf_from_remote_memory (GElf_Addr ehdr_vma, > > GElf_Off start = offset & -pagesize; > GElf_Off end = (offset + filesz + pagesize - 1) & -pagesize; > + /* The final contents_size is the trimmed last segment's end, which > + may be smaller than an earlier segment's start (segments_end above > + tracks the last PT_LOAD, not the maximum). Skip any segment that > + falls entirely past it: otherwise buffer + start would be out of > + bounds and end - start would underflow into a huge read length. > + Mirrors the file_trimmed_end check in dwfl_segment_report_module. */ > + if (start >= (GElf_Off) contents_size) > + continue; > if (end > (GElf_Off) contents_size) > end = contents_size; > nread = (*read_memory) (arg, buffer + start, Aha, we already cap the end to fall within the contents. Clearly if start is outside the whole segment is invalid. > diff --git a/tests/Makefile.am b/tests/Makefile.am > index 8ae7d126..2881989d 100644 > --- a/tests/Makefile.am > +++ b/tests/Makefile.am > @@ -411,7 +411,7 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh run-ar.sh \ > testfile45.S.bz2 testfile45.expect.bz2 run-disasm-x86-64.sh \ > testfile46.bz2 testfile47.bz2 testfile48.bz2 testfile48.debug.bz2 \ > testfile49.bz2 testfile50.bz2 testfile51.bz2 \ > - testfile-macros-0xff.bz2 \ > + testfile-macros-0xff.bz2 testfile-macros-opcode0.bz2 \ > run-readelf-macro.sh testfilemacro.bz2 testfileclangmacro.bz2 \ > run-readelf-loc.sh testfileloc.bz2 \ > splitdwarf4-not-split4.dwo.bz2 \ > diff --git a/tests/run-dwarf-getmacros.sh b/tests/run-dwarf-getmacros.sh > index 220c2426..bda6690d 100755 > --- a/tests/run-dwarf-getmacros.sh > +++ b/tests/run-dwarf-getmacros.sh > @@ -707,6 +707,14 @@ file /home/petr/proj/elfutils/master/elfutils/x.c > /file > EOF > > +# A .debug_macro opcode_operands_table that defines opcode 0 used to make > +# get_table_for_offset() compute op_protos[(unsigned)0 - 1] and write far > +# out of bounds. It must now be rejected as invalid DWARF. > +testfiles testfile-macros-opcode0 > +testrun_compare ${abs_builddir}/dwarf-getmacros testfile-macros-opcode0 0xb > <<\EOF > +invalid DWARF > +EOF > + > # See testfile-dwp.source. > testfiles testfile-dwp-5 testfile-dwp-5.dwp > testfiles testfile-dwp-4-strict testfile-dwp-4-strict.dwp > diff --git a/tests/testfile-macros-opcode0.bz2 > b/tests/testfile-macros-opcode0.bz2 > new file mode 100644 > index > 0000000000000000000000000000000000000000..5e4cb8684065c20b00b0cbde317cf733c8e7a0e9 > GIT binary patch > literal 166 > zcmZ>Y%CIzaj8qGbRLPmZ%D_<ie?|Sf2OSKK0?ffI4hs7p&u3V`z|f$;FiBuR1_KD( > zY8IL`tE$<ds^@~T0|QgBe`;^e{*tG&%sXesEOc`gVcW4$Erf$<3&&MnAtp(|yESS1 > zV|4@gn65uudF+WtQ?()Ei?+$xjRq_ixOEN1A~kF{KYmWwJ<+}JN!^c@8BCTlt$QYQ > S&xl#X`Cq)q)goX5$Xx(&Fh3Ol > > literal 0 > HcmV?d00001 Thanks for that testcase. It looks like eu-readelf does detect the corruption correctly: eu-readelf --debug-dump=macro ./testfile-macros-opcode0 DWARF section [ 3] '.debug_macro' at offset 0x59: Offset: 0x0 Version: 4 Flag: 0x4 (operands_table) Offset length: 4 extension opcode table, 1 items: [0]eu-readelf: invalid data Cheers, Mark
