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