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. */