CVE-2019-20805 (published 2020-06-01T14:15Z)

p_lx_elf.cpp in UPX before 3.96 has an integer overflow during unpacking via crafted values in a PT_DYNAMIC segment.

Severity: MEDIUM
CVSS v2 base score: 4.3 (AV:N/AC:M/Au:N/C:N/I:N/A:P)
CVSS v3 base score: 5.5, MEDIUM (CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: CWE-190
cwe_name: Integer Overflow or Wraparound
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.
extended_description: 
            
            
            
            
         
         

Fix information:
repo_url: https://github.com/upx/upx
commit_details: repo_url: https://github.com/upx/upx
author: John Reiser
author_date: 2019-11-23 20:06:14-08:00
msg: 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
repository: repo_url: https://github.com/upx/upx
repo_name: upx/upx
description: UPX - the Ultimate Packer for eXecutables
repo_language: C++
owner: upx
file_changes: filename: p_lx_elf.cpp
change_type: ModificationType.MODIFY
diff: @@ -1574,6 +1574,14 @@ PackLinuxElf32::invert_pt_dynamic(Elf32_Dyn const *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) {
@@ -1607,9 +1615,14 @@ PackLinuxElf32::invert_pt_dynamic(Elf32_Dyn const *dynp)
         }
     }
     // 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;
@@ -1632,6 +1645,11 @@ PackLinuxElf32::invert_pt_dynamic(Elf32_Dyn const *dynp)
     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]);
@@ -1795,7 +1813,7 @@ char const *PackLinuxElf64::get_dynsym_name(unsigned symnum, unsigned relnum) co
 
 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) {
@@ -1832,7 +1850,7 @@ char const *PackLinuxElf32::get_dynsym_name(unsigned symnum, unsigned relnum) co
 
 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) {
@@ -4901,7 +4919,7 @@ PackLinuxElf32::elf_find_dynamic(unsigned int key) const
     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;
@@ -5011,6 +5029,14 @@ PackLinuxElf64::invert_pt_dynamic(Elf64_Dyn const *dynp)
             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) {
@@ -5049,6 +5075,11 @@ PackLinuxElf64::invert_pt_dynamic(Elf64_Dyn const *dynp)
     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;
@@ -5071,6 +5102,11 @@ PackLinuxElf64::invert_pt_dynamic(Elf64_Dyn const *dynp)
     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]);
@@ -5116,8 +5152,8 @@ PackLinuxElf64::elf_find_dynamic(unsigned int key) const
     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;
     }

programming_language: C++