summaryrefslogtreecommitdiffstats
path: root/libdw
diff options
context:
space:
mode:
authorMark Wielaard <[email protected]>2015-01-07 23:39:28 +0100
committerMark Wielaard <[email protected]>2015-01-15 14:25:35 +0100
commitb4b2de9f1f1fb694b77371a61f808a1641bbafea (patch)
tree7da33fc1555daac5853a41fe6bea883e4f86b012 /libdw
parent975a37d11d1a14229ada9293fac88a493bf83cfa (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/ChangeLog15
-rw-r--r--libdw/cfi.h3
-rw-r--r--libdw/dwarf_getcfi_elf.c27
-rw-r--r--libdw/encoded-value.h69
-rw-r--r--libdw/fde.c13
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,