forked from pool/systemtap
c888d4e1d8
Copy from home:jones_tony:Factory/systemtap via accept of submit request 25952 revision 3. Request was accepted with message: OBS-URL: https://build.opensuse.org/request/show/25952 OBS-URL: https://build.opensuse.org/package/show/devel:tools/systemtap?expand=0&rev=7
194 lines
7.2 KiB
Diff
194 lines
7.2 KiB
Diff
From: Jan Lieskovsky <jlieskov@redhat.com>
|
|
Subject: Three SystemTap-1.0 denial of service issues
|
|
References: CVE-2009-2911, BNC#548361
|
|
Upstream: yes
|
|
|
|
Three denial of service flaws were found in the SystemTap
|
|
instrumentation system of version 1.0, when the --unprivileged mode was
|
|
activated:
|
|
|
|
c, Absent check(s) for the upper bound of the size of the unwind table
|
|
and for the upper bound of the size of each of the CIE/CFI records, could
|
|
allow an attacker to cause a denial of service (infinite loop).
|
|
|
|
diff --git a/runtime/unwind.c b/runtime/unwind.c
|
|
index 00108a3..7607770 100644
|
|
--- a/runtime/unwind.c
|
|
+++ b/runtime/unwind.c
|
|
@@ -88,7 +88,7 @@ static sleb128_t get_sleb128(const u8 **pcur, const u8 *end)
|
|
|
|
/* given an FDE, find its CIE */
|
|
static const u32 *cie_for_fde(const u32 *fde, void *unwind_data,
|
|
- int is_ehframe)
|
|
+ uint32_t table_len, int is_ehframe)
|
|
{
|
|
const u32 *cie;
|
|
|
|
@@ -118,6 +118,11 @@ static const u32 *cie_for_fde(const u32 *fde, void *unwind_data,
|
|
else
|
|
cie = unwind_data + fde[1];
|
|
|
|
+ /* Make sure address falls in the table */
|
|
+ if (((void *)cie) < ((void*)unwind_data)
|
|
+ || ((void*)cie) > ((void*)(unwind_data + table_len)))
|
|
+ return NULL;
|
|
+
|
|
if (*cie <= sizeof(*cie) + 4 || *cie >= fde[1] - sizeof(*fde)
|
|
|| (*cie & (sizeof(*cie) - 1))
|
|
|| (cie[1] != 0xffffffff && cie[1] != 0)) {
|
|
@@ -200,7 +205,8 @@ static unsigned long read_pointer(const u8 **pLoc, const void *end, signed ptrTy
|
|
return value;
|
|
}
|
|
|
|
-static signed fde_pointer_type(const u32 *cie)
|
|
+static signed fde_pointer_type(const u32 *cie, void *unwind_data,
|
|
+ uint32_t table_len)
|
|
{
|
|
const u8 *ptr = (const u8 *)(cie + 2);
|
|
unsigned version = *ptr;
|
|
@@ -212,11 +218,16 @@ static signed fde_pointer_type(const u32 *cie)
|
|
const u8 *end = (const u8 *)(cie + 1) + *cie;
|
|
uleb128_t len;
|
|
|
|
+ /* end of cie should fall within unwind table. */
|
|
+ if (((void*)end) < ((void *)unwind_data)
|
|
+ || ((void *)end) > ((void *)(unwind_data + table_len)))
|
|
+ return -1;
|
|
+
|
|
/* check if augmentation size is first (and thus present) */
|
|
if (*ptr != 'z')
|
|
return -1;
|
|
/* check if augmentation string is nul-terminated */
|
|
- if ((ptr = memchr(aug = (const void *)ptr, 0, end - ptr)) == NULL)
|
|
+ if ((ptr = memchr(aug = (const void *)ptr, 0, end - ptr)) == NULL)
|
|
return -1;
|
|
++ptr; /* skip terminator */
|
|
get_uleb128(&ptr, end); /* skip code alignment */
|
|
@@ -267,6 +278,10 @@ static void set_rule(uleb128_t reg, enum item_location where, uleb128_t value, s
|
|
}
|
|
}
|
|
|
|
+/* Limit the number of instructions we process. Arbitrary limit.
|
|
+ 512 should be enough for anybody... */
|
|
+#define MAX_CFI 512
|
|
+
|
|
static int processCFI(const u8 *start, const u8 *end, unsigned long targetLoc, signed ptrType, struct unwind_state *state)
|
|
{
|
|
union {
|
|
@@ -276,6 +291,9 @@ static int processCFI(const u8 *start, const u8 *end, unsigned long targetLoc, s
|
|
} ptr;
|
|
int result = 1;
|
|
|
|
+ if (end - start > MAX_CFI)
|
|
+ return 0;
|
|
+
|
|
dbug_unwind(1, "targetLoc=%lx state->loc=%lx\n", targetLoc, state->loc);
|
|
if (start != state->cieStart) {
|
|
state->loc = state->org;
|
|
@@ -606,10 +624,10 @@ static int unwind_frame(struct unwind_frame_info *frame,
|
|
|
|
/* found the fde, now set startLoc and endLoc */
|
|
if (fde != NULL) {
|
|
- cie = cie_for_fde(fde, table, is_ehframe);
|
|
+ cie = cie_for_fde(fde, table, table_len, is_ehframe);
|
|
if (likely(cie != NULL && cie != &bad_cie && cie != ¬_fde)) {
|
|
ptr = (const u8 *)(fde + 2);
|
|
- ptrType = fde_pointer_type(cie);
|
|
+ ptrType = fde_pointer_type(cie, table, table_len);
|
|
startLoc = read_pointer(&ptr, (const u8 *)(fde + 1) + *fde, ptrType);
|
|
startLoc = adjustStartLoc(startLoc, m, s, ptrType, is_ehframe);
|
|
|
|
@@ -632,12 +650,12 @@ static int unwind_frame(struct unwind_frame_info *frame,
|
|
for (fde = table, tableSize = table_len; cie = NULL, tableSize > sizeof(*fde)
|
|
&& tableSize - sizeof(*fde) >= *fde; tableSize -= sizeof(*fde) + *fde, fde += 1 + *fde / sizeof(*fde)) {
|
|
dbug_unwind(3, "fde=%lx tableSize=%d\n", (long)*fde, (int)tableSize);
|
|
- cie = cie_for_fde(fde, table, is_ehframe);
|
|
+ cie = cie_for_fde(fde, table, table_len, is_ehframe);
|
|
if (cie == &bad_cie) {
|
|
cie = NULL;
|
|
break;
|
|
}
|
|
- if (cie == NULL || cie == ¬_fde || (ptrType = fde_pointer_type(cie)) < 0)
|
|
+ if (cie == NULL || cie == ¬_fde || (ptrType = fde_pointer_type(cie, table, table_len)) < 0)
|
|
continue;
|
|
|
|
ptr = (const u8 *)(fde + 2);
|
|
@@ -666,6 +684,12 @@ static int unwind_frame(struct unwind_frame_info *frame,
|
|
state.cieEnd = ptr; /* keep here temporarily */
|
|
ptr = (const u8 *)(cie + 2);
|
|
end = (const u8 *)(cie + 1) + *cie;
|
|
+
|
|
+ /* end should fall within unwind table. */
|
|
+ if (((void *)end) < table
|
|
+ || ((void *)end) > ((void *)(table + table_len)))
|
|
+ goto err;
|
|
+
|
|
frame->call_frame = 1;
|
|
if ((state.version = *ptr) != 1) {
|
|
dbug_unwind(1, "CIE version number is %d. 1 is supported.\n", state.version);
|
|
@@ -723,6 +747,11 @@ static int unwind_frame(struct unwind_frame_info *frame,
|
|
state.cieEnd = end;
|
|
end = (const u8 *)(fde + 1) + *fde;
|
|
|
|
+ /* end should fall within unwind table. */
|
|
+ if (((void*)end) < table
|
|
+ || ((void *)end) > ((void *)(table + table_len)))
|
|
+ goto err;
|
|
+
|
|
/* skip augmentation */
|
|
if (((const char *)(cie + 2))[1] == 'z') {
|
|
uleb128_t augSize = get_uleb128(&ptr, end);
|
|
diff --git a/runtime/unwind/unwind.h b/runtime/unwind/unwind.h
|
|
index 285a3a3..023ea60 100644
|
|
--- a/runtime/unwind/unwind.h
|
|
+++ b/runtime/unwind/unwind.h
|
|
@@ -143,8 +143,10 @@ static unsigned long read_pointer(const u8 **pLoc,
|
|
const void *end,
|
|
signed ptrType);
|
|
static const u32 bad_cie, not_fde;
|
|
-static const u32 *cie_for_fde(const u32 *fde, void *table, int is_ehframe);
|
|
-static signed fde_pointer_type(const u32 *cie);
|
|
+static const u32 *cie_for_fde(const u32 *fde, void *table,
|
|
+ uint32_t table_len, int is_ehframe);
|
|
+static signed fde_pointer_type(const u32 *cie,
|
|
+ void *table, uint32_t table_len);
|
|
|
|
|
|
#endif /* STP_USE_DWARF_UNWINDER */
|
|
diff --git a/translate.cxx b/translate.cxx
|
|
index bc5d615..9d456bc 100644
|
|
--- a/translate.cxx
|
|
+++ b/translate.cxx
|
|
@@ -29,6 +29,11 @@ extern "C" {
|
|
#include <elfutils/libdwfl.h>
|
|
}
|
|
|
|
+// Max unwind table size (debug or eh) per module. Somewhat arbitrary
|
|
+// limit (a bit more than twice the .debug_frame size of my local
|
|
+// vmlinux for 2.6.31.4-83.fc12.x86_64)
|
|
+#define MAX_UNWIND_TABLE_SIZE (3 * 1024 * 1024)
|
|
+
|
|
using namespace std;
|
|
|
|
struct var;
|
|
@@ -4785,6 +4790,9 @@ dump_unwindsyms (Dwfl_Module *m,
|
|
get_unwind_data (m, &debug_frame, &eh_frame, &debug_len, &eh_len, &eh_addr);
|
|
if (debug_frame != NULL && debug_len > 0)
|
|
{
|
|
+ if (debug_len > MAX_UNWIND_TABLE_SIZE)
|
|
+ throw semantic_error ("module debug unwind table size too big");
|
|
+
|
|
c->output << "#if defined(STP_USE_DWARF_UNWINDER) && defined(STP_NEED_UNWIND_DATA)\n";
|
|
c->output << "static uint8_t _stp_module_" << stpmod_idx
|
|
<< "_debug_frame[] = \n";
|
|
@@ -4802,6 +4810,9 @@ dump_unwindsyms (Dwfl_Module *m,
|
|
|
|
if (eh_frame != NULL && eh_len > 0)
|
|
{
|
|
+ if (eh_len > MAX_UNWIND_TABLE_SIZE)
|
|
+ throw semantic_error ("module eh unwind table size too big");
|
|
+
|
|
c->output << "#if defined(STP_USE_DWARF_UNWINDER) && defined(STP_NEED_UNWIND_DATA)\n";
|
|
c->output << "static uint8_t _stp_module_" << stpmod_idx
|
|
<< "_eh_frame[] = \n";
|