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.

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.

Add the same "bytes >= limit" check that the surrounding code already
uses for the other reads in this function before consuming the encoding
byte.

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;
-- 
2.52.0

Reply via email to