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

Reply via email to