Hi Sayed,
On Sun, Jun 28, 2026 at 08:29:01PM +0530, Sayed Kaif wrote:
> dwarf_next_cfi parses CFI entries directly from the (untrusted)
> .eh_frame or .debug_frame section data. When a CIE uses a sized ('z')
> augmentation, the loop over the augmentation string characters consumes
> an encoding byte for the 'L' and 'P' directives with
>
> encoding = *bytes++;
>
> without first checking that bytes is still within the entry. The only
> bounds check (bytes > augmentation_data + augmentation_data_size) is done
> after the loop, so the read happens before it is validated.
And so if it isn't the last entry we detect the invalid data. But if...
> A crafted CIE with augmentation "zL" (or "zP") and an empty augmentation
> data area leaves bytes pointing exactly at the end of the section, so
> *bytes++ reads one byte past the section buffer. When the malformed CIE
> is the last entry in the section this is a read past the end of the
> mapped data.
... then we did the invalid read before the sanity check and we do a
possible illegal derefence.
Nicely spotted.
> Add the same "bytes >= limit" check that the surrounding code already
> uses for the other reads in this function before consuming the encoding
> byte.
Yes, that seems the right guard to use here.
> The problem was found with AddressSanitizer. A 16-byte .eh_frame
> containing a single CIE
>
> 0c 00 00 00 length = 12
> 00 00 00 00 CIE_id = 0
> 01 version = 1
> 7a 4c 00 augmentation = "zL"
> 01 code_alignment_factor = 1
> 7f data_alignment_factor = -1
> 00 return_address_register = 0
> 00 augmentation_data_size = 0
>
> makes dwarf_next_cfi read one byte past the end:
>
> ERROR: AddressSanitizer: heap-buffer-overflow ... READ of size 1
> #0 dwarf_next_cfi libdw/dwarf_next_cfi.c:219
>
> With the fix dwarf_next_cfi returns DWARF_E_INVALID_DWARF for that input,
> and parsing of the .eh_frame data of normal binaries is unchanged
> (libc.so, gcc and libelf.so all iterate without errors).
>
> Signed-off-by: Sayed Kaif <[email protected]>
> ---
> diff --git a/libdw/dwarf_next_cfi.c b/libdw/dwarf_next_cfi.c
> index be08984..5bb74b8 100644
> --- a/libdw/dwarf_next_cfi.c
> +++ b/libdw/dwarf_next_cfi.c
> @@ -216,6 +216,8 @@ dwarf_next_cfi (const unsigned char e_ident[],
> if (sized_augmentation)
> {
> /* Skip LSDA pointer encoding byte. */
> + if (bytes >= limit)
> + goto invalid;
> encoding = *bytes++;
> entry->cie.fde_augmentation_data_size
> += encoded_value_size (data, e_ident, encoding, NULL);
> @@ -234,6 +236,8 @@ dwarf_next_cfi (const unsigned char e_ident[],
> if (sized_augmentation)
> {
> /* Skip encoded personality routine pointer. */
> + if (bytes >= limit)
> + goto invalid;
> encoding = *bytes++;
> bytes += encoded_value_size (data, e_ident, encoding, bytes);
> continue;
OK. And the 'R' case doesn't need a limit check because it doesn't
actually try to read the byte.
Thanks, will push.
Cheers,
Mark