Project: upx (upx/upx), language: c++
Vulnerability instance: upx.cve-2021-43317
Sanitizer: address

================= Bug Report (1/1) ==================
## Source: GitHub Issue
## URL: https://github.com/upx/upx/issues/380
## Description:
Issue: upx/upx#380
Title: [bug] multi heap buffer overflows in get_le32()
State: closed
Created by: 14isnot40
Created at: 2020-05-23 14:24:07+00:00

Issue Body:
## What's the problem (or question)?
Multi heap-based buffer overflows were discovered in upx, during the genric pointer 'p' points to an inaccessible address in func get_le32(). The issue can be triggered by different places, which can cause a denial of service. The issue is diff from [issue365](https://github.com/upx/upx/issues/365)

ASAN reports:
```bash
==112024==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x61d00001f3b1 at pc 0x0000005292cb bp 0x7fffc3995640 sp 0x7fffc3995630
READ of size 4 at 0x61d00001f3b1 thread T0
    #0 0x5292ca in get_le32(void const*) /home/test/Desktop/EVAULATION/upx/src/bele.h:164
    #1 0x5292ca in N_BELE_RTP::LEPolicy::get32(void const*) const /home/test/Desktop/EVAULATION/upx/src/bele_policy.h:192
    #2 0x4589c1 in Packer::get_te32(void const*) const /home/test/Desktop/EVAULATION/upx/src/packer.h:296
    #3 0x4589c1 in PackLinuxElf32::elf_lookup(char const*) const /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.cpp:5382
    #4 0x463d30 in PackLinuxElf32::PackLinuxElf32help1(InputFile*) /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.cpp:315
    #5 0x464e96 in PackLinuxElf32Le::PackLinuxElf32Le(InputFile*) /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.h:395
    #6 0x464e96 in PackLinuxElf32x86::PackLinuxElf32x86(InputFile*) /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.cpp:4800
    #7 0x464e96 in PackBSDElf32x86::PackBSDElf32x86(InputFile*) /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.cpp:4817
    #8 0x464e96 in PackFreeBSDElf32x86::PackFreeBSDElf32x86(InputFile*) /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.cpp:4828
    #9 0x4f337a in PackMaster::visitAllPackers(Packer* (*)(Packer*, void*), InputFile*, options_t const*, void*) /home/test/Desktop/EVAULATION/upx/src/packmast.cpp:190
    #10 0x4f50f9 in PackMaster::getUnpacker(InputFile*) /home/test/Desktop/EVAULATION/upx/src/packmast.cpp:248
    #11 0x4f521f in PackMaster::unpack(OutputFile*) /home/test/Desktop/EVAULATION/upx/src/packmast.cpp:266
    #12 0x52a1e6 in do_one_file(char const*, char*) /home/test/Desktop/EVAULATION/upx/src/work.cpp:160
    #13 0x52a69e in do_files(int, int, char**) /home/test/Desktop/EVAULATION/upx/src/work.cpp:271
    #14 0x403ace in main /home/test/Desktop/EVAULATION/upx/src/main.cpp:1538
    #15 0x7efc08e6182f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #16 0x404828 in _start (/home/test/Desktop/EVAULATION/upx/src/upx.out+0x404828)

0x61d00001f3b1 is located 189 bytes to the right of 2164-byte region [0x61d00001ea80,0x61d00001f2f4)
allocated by thread T0 here:
    #0 0x7efc09a55602 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x98602)
    #1 0x42732a in MemBuffer::alloc(unsigned long long) /home/test/Desktop/EVAULATION/upx/src/mem.cpp:194

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/test/Desktop/EVAULATION/upx/src/bele.h:164 get_le32(void const*)
Shadow bytes around the buggy address:
  0x0c3a7fffbe20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c3a7fffbe30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c3a7fffbe40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c3a7fffbe50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 04 fa
  0x0c3a7fffbe60: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x0c3a7fffbe70: fa fa fa fa fa fa[fa]fa fa fa fa fa fa fa fa fa
  0x0c3a7fffbe80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c3a7fffbe90: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c3a7fffbea0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c3a7fffbeb0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c3a7fffbec0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
==112024==ABORTING
```
Debug
```bash
Program received signal SIGSEGV, Segmentation fault.
0x000000000066f8f8 in get_le32 (p=0xa1fffd) at bele.h:164
164        return ACC_UA_GET_LE32(p);
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x0               
$rbx   : 0xa7ce93          
$rcx   : 0x2               
$rdx   : 0xae              
$rsp   : 0x00007fffffffcc18  →  0x000000000051249b  →  <PackLinuxElf32::elf_lookup(char+0> xor eax, ebp
$rbp   : 0x8e8223e2        
$rsi   : 0x0000000000a1fffd  →  0x0000000000a1fffd
$rdi   : 0x00000000009ed3c0  →  0x00000000007cd9c0  →  0x000000000066fe00  →  <N_BELE_RTP::LEPolicy::~LEPolicy()+0> lea rsp, [rsp-0x98]
$rip   : 0x000000000066f8f8  →  <N_BELE_RTP::LEPolicy::get32(void+0> mov eax, DWORD PTR [rsi]
$r8    : 0x1f              
$r9    : 0x3fdf            
$r10   : 0xae              
$r11   : 0x1d9577c0        
$r12   : 0x0000000000a00030  →  0x00000000007267a0  →  <vtable+0> add BYTE PTR [rax], al
$r13   : 0x0000000000a1fffd  →  0x0000000000a1fffd
$r14   : 0x0000000000a00a51  →  0x0000000000000000
$r15   : 0x00000000007cd9c0  →  0x000000000066fe00  →  <N_BELE_RTP::LEPolicy::~LEPolicy()+0> lea rsp, [rsp-0x98]
$eflags: [carry PARITY adjust ZERO sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000 
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffcc18│+0x0000: 0x000000000051249b  →  <PackLinuxElf32::elf_lookup(char+0> xor eax, ebp     ← $rsp
0x00007fffffffcc20│+0x0008: 0x00000000006cdffe  →  "JNI_OnLoad"
0x00007fffffffcc28│+0x0010: 0x0000000200000000
0x00007fffffffcc30│+0x0018: 0x000000000900457f
0x00007fffffffcc38│+0x0020: 0x0000000000000068 ("h"?)
0x00007fffffffcc40│+0x0028: 0x0000000000000000
0x00007fffffffcc48│+0x0030: 0x0000000000070000
0x00007fffffffcc50│+0x0038: 0x0000000000000000
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x66f8e7 <N_BELE_RTP::LEPolicy::get32(void+0> mov    rcx, QWORD PTR [rsp+0x8]
     0x66f8ec <N_BELE_RTP::LEPolicy::get32(void+0> mov    rdx, QWORD PTR [rsp]
     0x66f8f0 <N_BELE_RTP::LEPolicy::get32(void+0> lea    rsp, [rsp+0x98]
 →   0x66f8f8 <N_BELE_RTP::LEPolicy::get32(void+0> mov    eax, DWORD PTR [rsi]
     0x66f8fa <N_BELE_RTP::LEPolicy::get32(void+0> ret    
     0x66f8fb                  nop    DWORD PTR [rax+rax*1+0x0]
     0x66f900 <N_BELE_RTP::LEPolicy::get64(void+0> lea    rsp, [rsp-0x98]
     0x66f908 <N_BELE_RTP::LEPolicy::get64(void+0> mov    QWORD PTR [rsp], rdx
     0x66f90c <N_BELE_RTP::LEPolicy::get64(void+0> mov    QWORD PTR [rsp+0x8], rcx
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── source:bele.h+164 ────
    159     }
    160     
    161     inline unsigned get_le32(const void *p)
    162     {
    163     #if defined(ACC_UA_GET_LE32)
 →  164         return ACC_UA_GET_LE32(p);
    165     #else
    166         return acc_ua_get_le32(p);
    167     #endif
    168     }
    169     
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "upx.out", stopped, reason: SIGSEGV
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x66f8f8 → get_le32(p=0xa1fffd)
[#1] 0x66f8f8 → N_BELE_RTP::LEPolicy::get32(this=0x9ed3c0 <N_BELE_RTP::le_policy>, p=0xa1fffd)
[#2] 0x51249b → Packer::get_te32(this=0xa00030, p=0xa1fffd)
[#3] 0x51249b → PackLinuxElf32::elf_lookup(this=0xa00030, name=0x6cdffe "JNI_OnLoad")
[#4] 0x529509 → PackLinuxElf32::PackLinuxElf32help1(this=0xa00030, f=0x7fffffffce10)
[#5] 0x52c65e → PackLinuxElf32Le::PackLinuxElf32Le(f=0x7fffffffce10, this=0xa00030)
[#6] 0x52c65e → PackLinuxElf32x86::PackLinuxElf32x86(f=0x7fffffffce10, this=0xa00030)
[#7] 0x52c65e → PackBSDElf32x86::PackBSDElf32x86(f=0x7fffffffce10, this=0xa00030)
[#8] 0x52c65e → PackFreeBSDElf32x86::PackFreeBSDElf32x86(this=0xa00030, f=0x7fffffffce10)
[#9] 0x60448c → PackMaster::visitAllPackers(func=0x602c30 <try_unpack(Packer*, void*)>, f=0x7fffffffce10, o=0x7fffffffcfc8, user=0x7fffffffce10)
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  bt
#0  0x000000000066f8f8 in get_le32 (p=0xa1fffd) at bele.h:164
#1  N_BELE_RTP::LEPolicy::get32 (this=0x9ed3c0 <N_BELE_RTP::le_policy>, p=0xa1fffd) at bele_policy.h:192
#2  0x000000000051249b in Packer::get_te32 (this=0xa00030, p=0xa1fffd) at packer.h:296
#3  PackLinuxElf32::elf_lookup (this=0xa00030, name=0x6cdffe "JNI_OnLoad") at p_lx_elf.cpp:5382
#4  0x0000000000529509 in PackLinuxElf32::PackLinuxElf32help1 (this=this@entry=0xa00030, f=f@entry=0x7fffffffce10) at p_lx_elf.cpp:315
#5  0x000000000052c65e in PackLinuxElf32Le::PackLinuxElf32Le (f=0x7fffffffce10, this=0xa00030) at p_lx_elf.h:395
#6  PackLinuxElf32x86::PackLinuxElf32x86 (f=0x7fffffffce10, this=0xa00030) at p_lx_elf.cpp:4800
#7  PackBSDElf32x86::PackBSDElf32x86 (f=0x7fffffffce10, this=0xa00030) at p_lx_elf.cpp:4817
#8  PackFreeBSDElf32x86::PackFreeBSDElf32x86 (this=0xa00030, f=0x7fffffffce10) at p_lx_elf.cpp:4828
#9  0x000000000060448c in PackMaster::visitAllPackers (func=0x602c30 <try_unpack(Packer*, void*)>, f=0x7fffffffce10, o=0x7fffffffcfc8, user=0x7fffffffce10) at packmast.cpp:190
#10 0x00000000006072ca in PackMaster::getUnpacker (f=<optimized out>) at packmast.cpp:248
#11 PackMaster::unpack (this=0x7fffffffcfb0, fo=0x7fffffffcee0) at packmast.cpp:266
#12 0x0000000000670dc5 in do_one_file (iname=iname@entry=0x7fffffffdf15 "id:000089,sig:11,src:001286,op:MOpt-core-havoc,rep:2", oname=oname@entry=0x7fffffffd550 "/dev/null") at work.cpp:160
#13 0x000000000067157c in do_files (i=i@entry=0x4, argc=0x5, argv=0x7fffffffdac8) at work.cpp:271
#14 0x00000000004056a1 in main (argc=0x5, argv=0x7fffffffdac8) at main.cpp:1538
```
Deferencing a generic poniter 'p' trigger the overflow.
```cpp
gef➤  p *p
Attempt to dereference a generic pointer.
gef➤  p p
$1 = (const void *) 0xa1fffd
```
Essentially, the problem is caused in PackLinuxElf32::elf_lookup() at p_lx_elf.cpp:5382
```cpp
do if (0==((h ^ get_te32(hp))>>1)) {
    unsigned st_name = get_te32(&dsp->st_name);
    char const *const p = get_str_name(st_name, (unsigned)-1);
    if (0==strcmp(name, p)) {
        return dsp;
    }
} while (++dsp, 0==(1u& get_te32(hp++)));
```
Several locations will also trigger vulnerabilities:
PackLinuxElf32::elf_lookup() at p_lx_elf.cpp:5368
```cpp
unsigned const w = get_te32(&bitmask[(n_bitmask -1) & (h>>5)]);
```
PackLinuxElf64::elf_lookup() at p_lx_elf.cpp:5404
```cpp
for (si= get_te32(&buckets[m]); 0!=si; si= get_te32(&chains[si]))
```
PackLinuxElf32::elf_lookup() at p_lx_elf.cpp:5349
```cpp
for (si= get_te32(&buckets[m]); 0!=si; si= get_te32(&chains[si])) {
            char const *const p= get_dynsym_name(si, (unsigned)-1);
            if (0==strcmp(name, p)) {
                return &dynsym[si];
            }
        }
```



## What should have happened?
Decompress a crafted/suspicious file.

## Do you have an idea for a solution?
We are very grateful to @jreiser for patching the bucket in p_lx_elf.cpp in the [issue 365](https://github.com/upx/upx/issues/365). However, in fact, all places involving get_te32 () should be strengthened in upx, especially in p_lx_elf.cpp. The four positions we reported should be patched at least:
1) p_lx_elf.cpp:5382
2) p_lx_elf.cpp:5368
3) p_lx_elf.cpp:5404
4) p_lx_elf.cpp:5349


## How can we reproduce the issue?
1. compile upx with address-sanitize
2. execute cmd
```bash
upx.out -df $PoC -o /dev/null
```

p_lx_elf.cpp:5382
[Poc](https://github.com/14isnot40/vul_discovery/blob/master/upx/df/hbo_PackLinuxElf32__elf_lookup5382) can be found here.

p_lx_elf.cpp:5368
[Poc](https://github.com/14isnot40/vul_discovery/blob/master/upx/df/hbo_PackLinuxElf32__elf_lookup5368) can be found here.

p_lx_elf.cpp:5404
[Poc](https://github.com/14isnot40/vul_discovery/blob/master/upx/df/hbo_PackLinuxElf64__elf_lookup5404) can be found here.

p_lx_elf.cpp:5349
[Poc](https://github.com/14isnot40/vul_discovery/blob/master/upx/df/hbo_PackLinuxElf32__elf_lookup5349) can be found here.

## Please tell us details about your environment.
* UPX version used (`upx --version`):
```bash
upx 4.0.0-git-c6b9e3c62d15 (latest-devel-branch)
UCL data compression library 1.03
zlib data compression library 1.2.8
LZMA SDK version 4.43
```
* Host Operating System and version:
Ubuntu 16.04 64-bit
* Host CPU architecture:
Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz with 8GB 
* Target Operating System and version:
 same as Host
* Target CPU architecture:
 same as Host


Comments:
Comment by jreiser on 2020-05-28 00:14:16+00:00:
Fixed on `devel` branch by above commit.

---

Comment by jreiser on 2023-04-17 18:03:04+00:00:
Verified as "reported problem is not present" in official release [upx-4.0.2](https://github.com/upx/upx/releases/tag/v4.0.2) of Jan.30, 2023.
```
$ for i in *; do $UPX402 -df -o /dev/null $i; done
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2023
UPX 4.0.2       Markus Oberhumer, Laszlo Molnar & John Reiser   Jan 30th 2023

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
upx: hbo_PackLinuxElf32__elf_lookup5349: NotPackedException: not packed by UPX

Unpacked 0 files.
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2023
UPX 4.0.2       Markus Oberhumer, Laszlo Molnar & John Reiser   Jan 30th 2023

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
upx: hbo_PackLinuxElf32__elf_lookup5368: NotPackedException: not packed by UPX

Unpacked 0 files.
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2023
UPX 4.0.2       Markus Oberhumer, Laszlo Molnar & John Reiser   Jan 30th 2023

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
upx: hbo_PackLinuxElf32__elf_lookup5382: NotPackedException: not packed by UPX

Unpacked 0 files.
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2023
UPX 4.0.2       Markus Oberhumer, Laszlo Molnar & John Reiser   Jan 30th 2023

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
upx: hbo_PackLinuxElf64__elf_lookup5404: CantUnpackException: bad DT_STRSZ 0

Unpacked 0 files.
```
Also, by independent verification none of the PoC files are packed by UPX, as evidenced by not containing the string "UPX", so attempted de-compression via `upx -d` is correctly rejected:
```
$ grep UPX hbo_PackLinuxElf32__elf_lookup5349 hbo_PackLinuxElf32__elf_lookup5368 hbo_PackLinuxElf32__elf_lookup5382 hbo_PackLinuxElf64__elf_lookup5404
   ##  empty output
$ echo $?
1   ## no matches found
$ 

```


Build script:
#!/bin/bash -eu
# Minimized build script with only core build commands
set -eu
sed -i 's/ \&\& __clang_major__ < 15//m' /src/upx/src/util/util.cpp
mkdir -p build/debug
cd build/debug
cmake ../..
    cmake --build . --target $fuzz_basename -v

Dockerfile:
FROM hwiwonlee/secb.base:latest
RUN apt-get update && apt-get install -y make autoconf automake libtool cmake
RUN git clone https://github.com/upx/upx upx
RUN git -C upx checkout e0bf4609938b0b4d2e83469fac00206a771429e5
WORKDIR $SRC/upx
COPY build.sh $SRC/

Additional files: