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.

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

Reply via email to