CVE-2019-20805: Integer Overflow or Wraparound (CWE-190)
value: p_lx_elf.cpp in UPX before 3.96 has an integer overflow during unpacking via crafted values in a PT_DYNAMIC segment.
CWE description: The product performs a calculation that can
         produce an integer overflow or wraparound when the logic
         assumes that the resulting value will always be larger than
         the original value. This occurs when an integer value is
         incremented to a value that is too large to store in the
         associated representation. When this occurs, the value may
         become a very small or negative number.
CVSS v3 base score: 5.5
Published: 2020-06-01T14:15Z
Repository: https://github.com/upx/upx
Language: C++
Files changed: src/p_lx_elf.cpp
Commit date: 2019-11-23 20:06:14 -0800
Commit message: Avoid bogus values in PT_DYNAMIC segment.

Detect duplicate DT_*.
Detect out-of-bounds hashtab and gashtab.
Detect missing DT_REL, DT_RELA.
Detect out-of-bounds d_val.
https://github.com/upx/upx/issues/317
	modified:   p_lx_elf.cpp

Diff: diff --git a/src/p_lx_elf.cpp b/src/p_lx_elf.cpp
index 4605ec59..52a0cb14 100644
--- a/src/p_lx_elf.cpp
+++ b/src/p_lx_elf.cpp
@@ -1572,10 +1572,18 @@ PackLinuxElf32::invert_pt_dynamic(Elf32_Dyn const *dynp)
     unsigned ndx = 1+ 0;
     if (dynp)
     for (; ; ++ndx, ++dynp) {
         unsigned const d_tag = get_te32(&dynp->d_tag);
         if (d_tag < DT_NUM) {
+            if (dt_table[d_tag]
+            &&    get_te32(&dynp->d_val)
+               != get_te32(&dynp0[-1+ dt_table[d_tag]].d_val)) {
+                char msg[50]; snprintf(msg, sizeof(msg),
+                    "duplicate DT_%#x: [%#x] [%#x]",
+                    d_tag, -1+ dt_table[d_tag], -1+ ndx);
+                throwCantPack(msg);
+            }
             dt_table[d_tag] = ndx;
         }
         if (Elf32_Dyn::DT_NULL == d_tag) {
             break;  // check here so that dt_table[DT_NULL] is set
         }
@@ -1605,13 +1613,18 @@ PackLinuxElf32::invert_pt_dynamic(Elf32_Dyn const *dynp)
         if (v_sym < v_str) {
             symnum_end = (v_str - v_sym) / sz_sym;
         }
     }
     // DT_HASH often ends at DT_SYMTAB
-    unsigned const v_hsh = elf_unsigned_dynamic(Elf64_Dyn::DT_HASH);
+    unsigned const v_hsh = elf_unsigned_dynamic(Elf32_Dyn::DT_HASH);
     if (v_hsh && file_image) {
-        hashtab = (unsigned const *)elf_find_dynamic(Elf64_Dyn::DT_HASH);
+        hashtab = (unsigned const *)elf_find_dynamic(Elf32_Dyn::DT_HASH);
+        if (!hashtab) {
+            char msg[40]; snprintf(msg, sizeof(msg),
+               "bad DT_HASH %#x", v_hsh);
+            throwCantPack(msg);
+        }
         unsigned const nbucket = get_te32(&hashtab[0]);
         unsigned const *const buckets = &hashtab[2];
         unsigned const *const chains = &buckets[nbucket]; (void)chains;
 
         unsigned const v_sym = get_te32(&dynp0[-1+ x_sym].d_val);
@@ -1630,10 +1643,15 @@ PackLinuxElf32::invert_pt_dynamic(Elf32_Dyn const *dynp)
     }
     // DT_GNU_HASH often ends at DT_SYMTAB;  FIXME: not for Android?
     unsigned const v_gsh = elf_unsigned_dynamic(Elf32_Dyn::DT_GNU_HASH);
     if (v_gsh && file_image) {
         gashtab = (unsigned const *)elf_find_dynamic(Elf32_Dyn::DT_GNU_HASH);
+        if (!gashtab) {
+            char msg[40]; snprintf(msg, sizeof(msg),
+               "bad DT_GNU_HASH %#x", v_gsh);
+            throwCantPack(msg);
+        }
         unsigned const n_bucket = get_te32(&gashtab[0]);
         unsigned const n_bitmask = get_te32(&gashtab[2]);
         unsigned const gnu_shift = get_te32(&gashtab[3]);
         unsigned const *const bitmask = (unsigned const *)(void const *)&gashtab[4];
         unsigned     const *const buckets = (unsigned const *)&bitmask[n_bitmask];
@@ -1793,11 +1811,11 @@ char const *PackLinuxElf64::get_dynsym_name(unsigned symnum, unsigned relnum) co
     return get_str_name(get_te32(&dynsym[symnum].st_name), symnum);
 }
 
 bool PackLinuxElf64::calls_crt1(Elf64_Rela const *rela, int sz)
 {
-    if (!dynsym || !dynstr) {
+    if (!dynsym || !dynstr || !rela) {
         return false;
     }
     for (unsigned relnum= 0; 0 < sz; (sz -= sizeof(Elf64_Rela)), ++rela, ++relnum) {
         unsigned const symnum = get_te64(&rela->r_info) >> 32;
         char const *const symnam = get_dynsym_name(symnum, relnum);
@@ -1830,11 +1848,11 @@ char const *PackLinuxElf32::get_dynsym_name(unsigned symnum, unsigned relnum) co
     return get_str_name(get_te32(&dynsym[symnum].st_name), symnum);
 }
 
 bool PackLinuxElf32::calls_crt1(Elf32_Rel const *rel, int sz)
 {
-    if (!dynsym || !dynstr) {
+    if (!dynsym || !dynstr || !rel) {
         return false;
     }
     for (unsigned relnum= 0; 0 < sz; (sz -= sizeof(Elf32_Rel)), ++rel, ++relnum) {
         unsigned const symnum = get_te32(&rel->r_info) >> 8;
         char const *const symnam = get_dynsym_name(symnum, relnum);
@@ -4899,11 +4917,11 @@ PackLinuxElf32::elf_find_dynamic(unsigned int key) const
     Elf32_Dyn const *dynp= dynseg;
     if (dynp)
     for (; (unsigned)((char const *)dynp - (char const *)dynseg) < sz_dynseg
             && Elf32_Dyn::DT_NULL!=dynp->d_tag; ++dynp) if (get_te32(&dynp->d_tag)==key) {
         unsigned const t= elf_get_offset_from_address(get_te32(&dynp->d_val));
-        if (t) {
+        if (t && t < file_size) {
             return t + file_image;
         }
         break;
     }
     return 0;
@@ -5009,10 +5027,18 @@ PackLinuxElf64::invert_pt_dynamic(Elf64_Dyn const *dynp)
             char msg[50]; snprintf(msg, sizeof(msg),
                 "bad Elf64_Dyn[%d].d_tag %#lx", -1+ ndx, (long unsigned)d_tag);
             throwCantPack(msg);
         }
         if (d_tag < DT_NUM) {
+            if (dt_table[d_tag]
+            &&    get_te64(&dynp->d_val)
+               != get_te64(&dynp0[-1+ dt_table[d_tag]].d_val)) {
+                char msg[50]; snprintf(msg, sizeof(msg),
+                    "duplicate DT_%#x: [%#x] [%#x]",
+                    (unsigned)d_tag, -1+ dt_table[d_tag], -1+ ndx);
+                throwCantPack(msg);
+            }
             dt_table[d_tag] = ndx;
         }
         if (Elf64_Dyn::DT_NULL == d_tag) {
             break;  // check here so that dt_table[DT_NULL] is set
         }
@@ -5047,10 +5073,15 @@ PackLinuxElf64::invert_pt_dynamic(Elf64_Dyn const *dynp)
     }
     // DT_HASH often ends at DT_SYMTAB
     unsigned const v_hsh = elf_unsigned_dynamic(Elf64_Dyn::DT_HASH);
     if (v_hsh && file_image) {
         hashtab = (unsigned const *)elf_find_dynamic(Elf64_Dyn::DT_HASH);
+        if (!hashtab) {
+            char msg[40]; snprintf(msg, sizeof(msg),
+               "bad DT_HASH %#x", v_hsh);
+            throwCantPack(msg);
+        }
         unsigned const nbucket = get_te32(&hashtab[0]);
         unsigned const *const buckets = &hashtab[2];
         unsigned const *const chains = &buckets[nbucket]; (void)chains;
 
         unsigned const v_sym = get_te32(&dynp0[-1+ x_sym].d_val);
@@ -5069,10 +5100,15 @@ PackLinuxElf64::invert_pt_dynamic(Elf64_Dyn const *dynp)
     }
     // DT_GNU_HASH often ends at DT_SYMTAB;  FIXME: not for Android?
     unsigned const v_gsh = elf_unsigned_dynamic(Elf64_Dyn::DT_GNU_HASH);
     if (v_gsh && file_image) {
         gashtab = (unsigned const *)elf_find_dynamic(Elf64_Dyn::DT_GNU_HASH);
+        if (!gashtab) {
+            char msg[40]; snprintf(msg, sizeof(msg),
+               "bad DT_GNU_HASH %#x", v_gsh);
+            throwCantPack(msg);
+        }
         unsigned const n_bucket = get_te32(&gashtab[0]);
         unsigned const n_bitmask = get_te32(&gashtab[2]);
         unsigned const gnu_shift = get_te32(&gashtab[3]);
         upx_uint64_t const *const bitmask = (upx_uint64_t const *)(void const *)&gashtab[4];
         unsigned     const *const buckets = (unsigned const *)&bitmask[n_bitmask];
@@ -5114,12 +5150,12 @@ PackLinuxElf64::elf_find_dynamic(unsigned int key) const
     Elf64_Dyn const *dynp= dynseg;
     if (dynp)
     for (; (unsigned)((char const *)dynp - (char const *)dynseg) < sz_dynseg
             && Elf64_Dyn::DT_NULL!=dynp->d_tag; ++dynp) if (get_te64(&dynp->d_tag)==key) {
         upx_uint64_t const t= elf_get_offset_from_address(get_te64(&dynp->d_val));
-        if (t) {
-            return &((unsigned char const *)file_image)[(size_t)t];
+        if (t && t < (upx_uint64_t)file_size) {
+            return t + file_image;
         }
         break;
     }
     return 0;
 }

Vulnerable code:     unsigned const v_hsh = elf_unsigned_dynamic(Elf64_Dyn::DT_HASH);
        hashtab = (unsigned const *)elf_find_dynamic(Elf64_Dyn::DT_HASH);
    if (!dynsym || !dynstr) {
    if (!dynsym || !dynstr) {
        if (t) {
        if (t) {
            return &((unsigned char const *)file_image)[(size_t)t];
Fixed code:             if (dt_table[d_tag]
            &&    get_te32(&dynp->d_val)
               != get_te32(&dynp0[-1+ dt_table[d_tag]].d_val)) {
                char msg[50]; snprintf(msg, sizeof(msg),
                    "duplicate DT_%#x: [%#x] [%#x]",
                    d_tag, -1+ dt_table[d_tag], -1+ ndx);
                throwCantPack(msg);
            }
    unsigned const v_hsh = elf_unsigned_dynamic(Elf32_Dyn::DT_HASH);
        hashtab = (unsigned const *)elf_find_dynamic(Elf32_Dyn::DT_HASH);
        if (!hashtab) {
            char msg[40]; snprintf(msg, sizeof(msg),
               "bad DT_HASH %#x", v_hsh);
            throwCantPack(msg);
        }
        if (!gashtab) {
            char msg[40]; snprintf(msg, sizeof(msg),
               "bad DT_GNU_HASH %#x", v_gsh);
            throwCantPack(msg);
        }
    if (!dynsym || !dynstr || !rela) {
    if (!dynsym || !dynstr || !rel) {
        if (t && t < file_size) {
            if (dt_table[d_tag]
            &&    get_te64(&dynp->d_val)
               != get_te64(&dynp0[-1+ dt_table[d_tag]].d_val)) {
                char msg[50]; snprintf(msg, sizeof(msg),
                    "duplicate DT_%#x: [%#x] [%#x]",
                    (unsigned)d_tag, -1+ dt_table[d_tag], -1+ ndx);
                throwCantPack(msg);
            }
        if (!hashtab) {
            char msg[40]; snprintf(msg, sizeof(msg),
               "bad DT_HASH %#x", v_hsh);
            throwCantPack(msg);
        }
        if (!gashtab) {
            char msg[40]; snprintf(msg, sizeof(msg),
               "bad DT_GNU_HASH %#x", v_gsh);
            throwCantPack(msg);
        }
        if (t && t < (upx_uint64_t)file_size) {
            return t + file_image;