diff options
| author | Mark Wielaard <[email protected]> | 2015-01-07 23:39:28 +0100 |
|---|---|---|
| committer | Mark Wielaard <[email protected]> | 2015-01-15 14:25:35 +0100 |
| commit | b4b2de9f1f1fb694b77371a61f808a1641bbafea (patch) | |
| tree | 7da33fc1555daac5853a41fe6bea883e4f86b012 /libdw | |
| parent | 975a37d11d1a14229ada9293fac88a493bf83cfa (diff) | |
libdw: Robustify eh_frame_hdr and encoded-values reading.
Sanity check and keep track of binary_search_table data buffer length.
Add bounds check to encoded value reading. Also fix a bug when reading
the eh_frame header data from an other endian ELF image. Add a testcase
that would fail the new sanity checks because of the endian bug.
Signed-off-by: Mark Wielaard <[email protected]>
Diffstat (limited to 'libdw')
| -rw-r--r-- | libdw/ChangeLog | 15 | ||||
| -rw-r--r-- | libdw/cfi.h | 3 | ||||
| -rw-r--r-- | libdw/dwarf_getcfi_elf.c | 27 | ||||
| -rw-r--r-- | libdw/encoded-value.h | 69 | ||||
| -rw-r--r-- | libdw/fde.c | 13 |
5 files changed, 104 insertions, 23 deletions
diff --git a/libdw/ChangeLog b/libdw/ChangeLog index 9a46fac8..78c92cfa 100644 --- a/libdw/ChangeLog +++ b/libdw/ChangeLog @@ -1,3 +1,18 @@ +2015-01-07 Mark Wielaard <[email protected]> + + * cfi.h (struct Dwarf_CFI_s): Add search_table_len. + * dwarf_getcfi_elf.c (getcfi_gnu_eh_frame): Check there is enough + room in the search table for all entries. Store search_table_len. + (getcfi_scn_eh_frame): Likewise. + * encoded-value.h (encoded_value_size): Don't abort, return zero. + (__libdw_cfi_read_address_inc): Check there is enough room to read + values. Pass other byte order to read functions. + (read_encoded_value): Check encoded_value_size. Don't abort, but + set libdw errno and report failure. Check there is enough room to + read values. + * fde.c (binary_search_fde): Check encoded value size. Add hdr + data buf and size to dummy_cfi. + 2015-01-04 Mark Wielaard <[email protected]> * dwarf_siblingof.c (dwarf_siblingof): Check sibling attribute diff --git a/libdw/cfi.h b/libdw/cfi.h index 98ac6cfa..1ebf2dc3 100644 --- a/libdw/cfi.h +++ b/libdw/cfi.h @@ -1,5 +1,5 @@ /* Internal definitions for libdw CFI interpreter. - Copyright (C) 2009-2010, 2013 Red Hat, Inc. + Copyright (C) 2009-2010, 2013, 2015 Red Hat, Inc. This file is part of elfutils. This file is free software; you can redistribute it and/or modify @@ -103,6 +103,7 @@ struct Dwarf_CFI_s /* Binary search table in .eh_frame_hdr section. */ const uint8_t *search_table; + size_t search_table_len; Dwarf_Addr search_table_vaddr; size_t search_table_entries; uint8_t search_table_encoding; diff --git a/libdw/dwarf_getcfi_elf.c b/libdw/dwarf_getcfi_elf.c index e58eae67..3e611f8f 100644 --- a/libdw/dwarf_getcfi_elf.c +++ b/libdw/dwarf_getcfi_elf.c @@ -1,5 +1,5 @@ /* Get CFI from ELF file's exception-handling info. - Copyright (C) 2009-2010, 2014 Red Hat, Inc. + Copyright (C) 2009-2010, 2014, 2015 Red Hat, Inc. This file is part of elfutils. This file is free software; you can redistribute it and/or modify @@ -135,6 +135,7 @@ getcfi_gnu_eh_frame (Elf *elf, const GElf_Ehdr *ehdr, const GElf_Phdr *phdr) return NULL; } + size_t vsize, dmax; Dwarf_Addr eh_frame_ptr; size_t search_table_entries = 0; uint8_t search_table_encoding = 0; @@ -143,7 +144,15 @@ getcfi_gnu_eh_frame (Elf *elf, const GElf_Ehdr *ehdr, const GElf_Phdr *phdr) &eh_frame_ptr, &search_table_entries, &search_table_encoding); - if (search_table == (void *) -1l) + + /* Make sure there is enough room for the entries in the table, + each entry consists of 2 encoded values. */ + vsize = encoded_value_size (data, ehdr->e_ident, search_table_encoding, + NULL); + dmax = phdr->p_filesz - (search_table - (const uint8_t *) data->d_buf); + if (unlikely (search_table == (void *) -1l + || vsize == 0 + || search_table_entries > (dmax / vsize) / 2)) goto invalid_hdr; Dwarf_Off eh_frame_offset = eh_frame_ptr - phdr->p_vaddr + phdr->p_offset; @@ -171,6 +180,7 @@ getcfi_gnu_eh_frame (Elf *elf, const GElf_Ehdr *ehdr, const GElf_Phdr *phdr) if (search_table != NULL) { cfi->search_table = search_table; + cfi->search_table_len = phdr->p_filesz; cfi->search_table_vaddr = phdr->p_vaddr; cfi->search_table_encoding = search_table_encoding; cfi->search_table_entries = search_table_entries; @@ -221,6 +231,7 @@ getcfi_scn_eh_frame (Elf *elf, const GElf_Ehdr *ehdr, Elf_Data *hdr_data = elf_rawdata (hdr_scn, NULL); if (hdr_data != NULL && hdr_data->d_buf != NULL) { + size_t vsize, dmax; GElf_Addr eh_frame_vaddr; cfi->search_table_vaddr = hdr_vaddr; cfi->search_table @@ -228,7 +239,17 @@ getcfi_scn_eh_frame (Elf *elf, const GElf_Ehdr *ehdr, hdr_vaddr, ehdr, &eh_frame_vaddr, &cfi->search_table_entries, &cfi->search_table_encoding); - if (cfi->search_table == (void *) -1l) + cfi->search_table_len = hdr_data->d_size; + + /* Make sure there is enough room for the entries in the table, + each entry consists of 2 encoded values. */ + vsize = encoded_value_size (hdr_data, ehdr->e_ident, + cfi->search_table_encoding, NULL); + dmax = hdr_data->d_size - (cfi->search_table + - (const uint8_t *) hdr_data->d_buf); + if (unlikely (cfi->search_table == (void *) -1l + || vsize == 0 + || cfi->search_table_entries > (dmax / vsize) / 2)) { free (cfi); /* XXX might be read error or corrupt phdr */ diff --git a/libdw/encoded-value.h b/libdw/encoded-value.h index ae9a38f9..f953f5ef 100644 --- a/libdw/encoded-value.h +++ b/libdw/encoded-value.h @@ -1,5 +1,5 @@ /* DW_EH_PE_* support for libdw unwinder. - Copyright (C) 2009-2010, 2014 Red Hat, Inc. + Copyright (C) 2009-2010, 2014, 2015 Red Hat, Inc. This file is part of elfutils. This file is free software; you can redistribute it and/or modify @@ -32,8 +32,11 @@ #include <dwarf.h> #include <stdlib.h> #include "libdwP.h" +#include "../libelf/common.h" +/* Returns zero if the value is omitted, the encoding is unknown or + the (leb128) size cannot be determined. */ static size_t __attribute__ ((unused)) encoded_value_size (const Elf_Data *data, const unsigned char e_ident[], uint8_t encoding, const uint8_t *p) @@ -63,11 +66,11 @@ encoded_value_size (const Elf_Data *data, const unsigned char e_ident[], } default: - abort (); return 0; } } +/* Returns zero when value was read successfully, minus one otherwise. */ static inline int __attribute__ ((unused)) __libdw_cfi_read_address_inc (const Dwarf_CFI *cache, const unsigned char **addrp, @@ -82,16 +85,32 @@ __libdw_cfi_read_address_inc (const Dwarf_CFI *cache, /* Only .debug_frame might have relocation to consider. Read plain values from .eh_frame data. */ + const unsigned char *endp = cache->data->d.d_buf + cache->data->d.d_size; + Dwarf eh_dbg = { .other_byte_order = MY_ELFDATA != cache->e_ident[EI_DATA] }; + if (width == 4) - *ret = read_4ubyte_unaligned_inc (cache, *addrp); + { + if (unlikely (*addrp + 4 > endp)) + { + invalid_data: + __libdw_seterrno (DWARF_E_INVALID_CFI); + return -1; + } + *ret = read_4ubyte_unaligned_inc (&eh_dbg, *addrp); + } else - *ret = read_8ubyte_unaligned_inc (cache, *addrp); + { + if (unlikely (*addrp + 8 > endp)) + goto invalid_data; + *ret = read_8ubyte_unaligned_inc (&eh_dbg, *addrp); + } return 0; } +/* Returns true on error, false otherwise. */ static bool __attribute__ ((unused)) -read_encoded_value (const Dwarf_CFI *cache, uint8_t encoding, const uint8_t **p, - Dwarf_Addr *result) +read_encoded_value (const Dwarf_CFI *cache, uint8_t encoding, + const uint8_t **p, Dwarf_Addr *result) { *result = 0; switch (encoding & 0x70) @@ -115,8 +134,11 @@ read_encoded_value (const Dwarf_CFI *cache, uint8_t encoding, const uint8_t **p, break; case DW_EH_PE_aligned: { - const size_t size = encoded_value_size (&cache->data->d, cache->e_ident, + const size_t size = encoded_value_size (&cache->data->d, + cache->e_ident, encoding, *p); + if (unlikely (size == 0)) + return true; size_t align = ((cache->frame_vaddr + (*p - (const uint8_t *) cache->data->d.d_buf)) & (size - 1)); @@ -126,54 +148,63 @@ read_encoded_value (const Dwarf_CFI *cache, uint8_t encoding, const uint8_t **p, } default: - abort (); + __libdw_seterrno (DWARF_E_INVALID_CFI); + return true; } Dwarf_Addr value; + const unsigned char *endp = cache->data->d.d_buf + cache->data->d.d_size; switch (encoding & 0x0f) { case DW_EH_PE_udata2: + if (unlikely (*p + 2 > endp)) + { + invalid_data: + __libdw_seterrno (DWARF_E_INVALID_CFI); + return true; + } value = read_2ubyte_unaligned_inc (cache, *p); break; case DW_EH_PE_sdata2: + if (unlikely (*p + 2 > endp)) + goto invalid_data; value = read_2sbyte_unaligned_inc (cache, *p); break; case DW_EH_PE_udata4: - if (__libdw_cfi_read_address_inc (cache, p, 4, &value)) + if (unlikely (__libdw_cfi_read_address_inc (cache, p, 4, &value) != 0)) return true; break; case DW_EH_PE_sdata4: - if (__libdw_cfi_read_address_inc (cache, p, 4, &value)) + if (unlikely (__libdw_cfi_read_address_inc (cache, p, 4, &value) != 0)) return true; value = (Dwarf_Sword) (Elf32_Sword) value; /* Sign-extend. */ break; case DW_EH_PE_udata8: case DW_EH_PE_sdata8: - if (__libdw_cfi_read_address_inc (cache, p, 8, &value)) + if (unlikely (__libdw_cfi_read_address_inc (cache, p, 8, &value) != 0)) return true; break; case DW_EH_PE_absptr: - if (__libdw_cfi_read_address_inc (cache, p, 0, &value)) + if (unlikely (__libdw_cfi_read_address_inc (cache, p, 0, &value) != 0)) return true; break; case DW_EH_PE_uleb128: - // XXX we trust there is enough data. - get_uleb128 (value, *p, *p + len_leb128 (Dwarf_Addr)); + get_uleb128 (value, *p, endp); break; case DW_EH_PE_sleb128: - // XXX we trust there is enough data. - get_sleb128 (value, *p, *p + len_leb128 (Dwarf_Addr)); + get_sleb128 (value, *p, endp); break; default: - abort (); + __libdw_seterrno (DWARF_E_INVALID_CFI); + return true; } *result += value; @@ -188,7 +219,9 @@ read_encoded_value (const Dwarf_CFI *cache, uint8_t encoding, const uint8_t **p, DW_EH_PE_absptr, NULL)))) return true; const uint8_t *ptr = cache->data->d.d_buf + *result; - return __libdw_cfi_read_address_inc (cache, &ptr, 0, result); + if (unlikely (__libdw_cfi_read_address_inc (cache, &ptr, 0, result) + != 0)) + return true; } return false; diff --git a/libdw/fde.c b/libdw/fde.c index 18a522bd..c8475f3e 100644 --- a/libdw/fde.c +++ b/libdw/fde.c @@ -1,5 +1,5 @@ /* FDE reading. - Copyright (C) 2009-2010, 2014 Red Hat, Inc. + Copyright (C) 2009-2010, 2014, 2015 Red Hat, Inc. This file is part of elfutils. This file is free software; you can redistribute it and/or modify @@ -161,13 +161,22 @@ binary_search_fde (Dwarf_CFI *cache, Dwarf_Addr address) const size_t size = 2 * encoded_value_size (&cache->data->d, cache->e_ident, cache->search_table_encoding, NULL); + if (unlikely (size == 0)) + return (Dwarf_Off) -1l; /* Dummy used by read_encoded_value. */ + Elf_Data_Scn dummy_cfi_hdr_data = + { + .d = { .d_buf = (void *) cache->search_table, + .d_size = cache->search_table_len } + }; + Dwarf_CFI dummy_cfi = { .e_ident = cache->e_ident, .datarel = cache->search_table_vaddr, .frame_vaddr = cache->search_table_vaddr, + .data = &dummy_cfi_hdr_data }; size_t l = 0, u = cache->search_table_entries; @@ -175,6 +184,8 @@ binary_search_fde (Dwarf_CFI *cache, Dwarf_Addr address) { size_t idx = (l + u) / 2; + /* Max idx * size is checked against search_table len when + loading eh_frame_hdr. */ const uint8_t *p = &cache->search_table[idx * size]; Dwarf_Addr start; if (unlikely (read_encoded_value (&dummy_cfi, |
