Hi Matej,

On Tue, 2026-09-08 at 21:16 +0200, Matej Smycka wrote:
> intern_new_cie () re-parses a CIE's augmentation string to record the
> LSDA and FDE pointer encodings.  For the 'L', 'R' and 'P' augmentation
> characters it consumes an encoding byte with
> 
>     encoding = *data++;
> 
> where data starts at info->augmentation_data and is advanced purely by
> the augmentation-string characters.  There is no check that data is
> still within the CFI section, and info->augmentation_data_size is
> ignored.  A crafted CIE whose augmentation string contains one of
> L/R/P while its augmentation data is truncated (or empty) leaves data
> pointing at the end of the .eh_frame/.debug_frame data, so *data++
> reads one byte past the section buffer.
> 
> This is the same defect fixed for dwarf_next_cfi in commit e4d1e627
> ("libdw: Fix out-of-bounds read in dwarf_next_cfi CIE augmentation
> parsing").  intern_new_cie has an independent copy of the augmentation
> loop, dating back to 2009, that was not covered by that fix and still
> reads unchecked.  The path is reachable from dwarf_cfi_addrframe () ->
> __libdw_find_fde () -> __libdw_intern_cie () -> intern_new_cie (), i.e.
> any consumer that unwinds using the libdw CFI on an untrusted ELF.
> 
> Bound the encoding-byte reads against the end of the CFI section and
> reject the CIE with DWARF_E_INVALID_DWARF otherwise, matching the limit
> check dwarf_next_cfi already applies.
> 
> Reproduced with AddressSanitizer.  A 19-byte .eh_frame CIE with a 'P'
> augmentation and no augmentation data makes dwarf_cfi_addrframe read one
> byte past the section:
> 
>     ERROR: AddressSanitizer: heap-buffer-overflow ... READ of size 1
>         #0 intern_new_cie libdw/cie.c:103
>         #1 __libdw_intern_cie libdw/cie.c:197
>         #2 __libdw_find_fde libdw/fde.c:308
>         #3 dwarf_cfi_addrframe libdw/dwarf_cfi_addrframe.c:43
> 
> With the fix dwarf_cfi_addrframe returns DWARF_E_INVALID_DWARF for that
> input, and CFI unwinding of normal binaries is unchanged.

This doesn't apply because it seems to have written against libdw/cie.c
before bug https://sourceware.org/bugzilla/show_bug.cgi?id=34387 was
fixed through:

commit 6c801dd7eab537940d454cf6d4772f5aa3038bc4
Author: Mark Wielaard <[email protected]>
Date:   Sat Jul 11 16:35:56 2026 +0200

    libdw: Only read CIE augmentation data if it is there
    
    dwarf_next_cfi and intern_new_cie used slightly different methods
    parsing the CIE augmentation data. Make sure both only read
    augmentation data when it is there.
    
        * libdw/cie.c (intern_new_cie): Check 'z' is the first char of
        the augmentation. Only read augmentation data if
        cie->sized_augmentation_data is set.
        * libdw/dwarf_next_cfi.c (dwarf_next_cfi): Check limit for 'R'.
    
    https://sourceware.org/bugzilla/show_bug.cgi?id=34387
    
    Reported-by: Karan Kurani <[email protected]>
    Signed-off-by: Mark Wielaard <[email protected]>

https://sourceware.org/cgit/elfutils/commit/?id=6c801dd7eab5

That said, it looks like those checks might need to be augmented with
your limit checks on the *data++ accesses.

Could you check? And rebase your patch on top of the current code?

Thanks,

Mark

> Signed-off-by: Matej Smycka <[email protected]>
> ---
>  libdw/cie.c | 13 +++++++++++++
>  1 file changed, 13 insertions(+)
> 
> diff --git a/libdw/cie.c b/libdw/cie.c
> index 9753d9b9..7681a881 100644
> --- a/libdw/cie.c
> +++ b/libdw/cie.c
> @@ -74,6 +74,8 @@ intern_new_cie (Dwarf_CFI *cache, Dwarf_Off offset, const 
> Dwarf_CIE *info)
>  
>    /* Grok the augmentation string and its data.  */
>    const uint8_t *data = info->augmentation_data;
> +  const uint8_t *const limit = ((const uint8_t *) cache->data->d.d_buf
> +                             + cache->data->d.d_size);
>    for (const char *ap = info->augmentation; *ap != '\0'; ++ap)
>      {
>        uint8_t encoding;
> @@ -88,6 +90,8 @@ intern_new_cie (Dwarf_CFI *cache, Dwarf_Off offset, const 
> Dwarf_CIE *info)
>         continue;
>  
>       case 'L':               /* LSDA pointer encoding byte.  */
> +       if (data >= limit)
> +         goto invalid;
>         cie->lsda_encoding = *data++;
>         if (!cie->sized_augmentation_data)
>           cie->fde_augmentation_data_size
> @@ -96,10 +100,14 @@ intern_new_cie (Dwarf_CFI *cache, Dwarf_Off offset, 
> const Dwarf_CIE *info)
>         continue;
>  
>       case 'R':               /* FDE address encoding byte.  */
> +       if (data >= limit)
> +         goto invalid;
>         cie->fde_encoding = *data++;
>         continue;
>  
>       case 'P':               /* Skip personality routine.  */
> +       if (data >= limit)
> +         goto invalid;
>         encoding = *data++;
>         data += encoded_value_size (&cache->data->d, cache->e_ident,
>                                     encoding, data);
> @@ -152,6 +160,11 @@ intern_new_cie (Dwarf_CFI *cache, Dwarf_Off offset, 
> const Dwarf_CIE *info)
>      }
>  
>    return cie;
> +
> + invalid:
> +  free (cie);
> +  __libdw_seterrno (DWARF_E_INVALID_DWARF);
> +  return NULL;
>  }
>  
>  /* Look up a CIE_pointer for random access.  */

Reply via email to