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

================= Bug Report (1/1) ==================
## Source: GitHub Issue
## URL: https://github.com/upx/upx/issues/379
## Description:
Issue: upx/upx#379
Title: [bug] heap buffer overflow in PackLinuxElf64::invert_pt_dynamic at p_lx_elf.cpp:5239
State: closed
Created by: 14isnot40
Created at: 2020-05-23 14:21:20+00:00

Issue Body:
## What's the problem (or question)?
A heap-based buffer overflow was discovered in upx, during the variable 'bucket' points to an inaccessible address. The issue is being triggered in the function PackLinuxElf64::invert_pt_dynamic at p_lx_elf.cpp:5239.

ASAN reports:
```bash
==110294==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x63000000f760 at pc 0x000000466f38 bp 0x7ffcebb0b6a0 sp 0x7ffcebb0b690
READ of size 4 at 0x63000000f760 thread T0
    #0 0x466f37 in PackLinuxElf64::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16, LE32, LE64, LE64, LE64> > const*) /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.cpp:5239
    #1 0x46f660 in PackLinuxElf64::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16, LE32, LE64, LE64, LE64> > const*) /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.cpp:5127
    #2 0x46f660 in PackLinuxElf64::PackLinuxElf64help1(InputFile*) /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.cpp:795
    #3 0x470479 in PackLinuxElf64Le::PackLinuxElf64Le(InputFile*) /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.h:407
    #4 0x470479 in PackLinuxElf64amd::PackLinuxElf64amd(InputFile*) /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.cpp:1008
    #5 0x4f34b2 in PackMaster::visitAllPackers(Packer* (*)(Packer*, void*), InputFile*, options_t const*, void*) /home/test/Desktop/EVAULATION/upx/src/packmast.cpp:194
    #6 0x4f50f9 in PackMaster::getUnpacker(InputFile*) /home/test/Desktop/EVAULATION/upx/src/packmast.cpp:248
    #7 0x4f521f in PackMaster::unpack(OutputFile*) /home/test/Desktop/EVAULATION/upx/src/packmast.cpp:266
    #8 0x52a1e6 in do_one_file(char const*, char*) /home/test/Desktop/EVAULATION/upx/src/work.cpp:160
    #9 0x52a69e in do_files(int, int, char**) /home/test/Desktop/EVAULATION/upx/src/work.cpp:271
    #10 0x403ace in main /home/test/Desktop/EVAULATION/upx/src/main.cpp:1538
    #11 0x7f8a7e1c882f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #12 0x404828 in _start (/home/test/Desktop/EVAULATION/upx/src/upx.out+0x404828)

0x63000000f760 is located 0 bytes to the right of 62304-byte region [0x630000000400,0x63000000f760)
allocated by thread T0 here:
    #0 0x7f8a7edbc602 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/p_lx_elf.cpp:5239 PackLinuxElf64::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16, LE32, LE64, LE64, LE64> > const*)
Shadow bytes around the buggy address:
  0x0c607fff9e90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c607fff9ea0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c607fff9eb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c607fff9ec0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c607fff9ed0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c607fff9ee0: 00 00 00 00 00 00 00 00 00 00 00 00[fa]fa fa fa
  0x0c607fff9ef0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c607fff9f00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c607fff9f10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c607fff9f20: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c607fff9f30: 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
==110294==ABORTING
```
Then analysis the reasons for segv by debugging:
```bash
Program received signal SIGSEGV, Segmentation fault.
0x000000000053b1a8 in PackLinuxElf64::invert_pt_dynamic (this=this@entry=0xa00030, dynp=<optimized out>) at p_lx_elf.cpp:5239
5239                if (buckets[j]) {
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x0               
$rbx   : 0x0000000000a00030  →  0x0000000000726a30  →  <vtable+0> add BYTE PTR [rax], al
$rcx   : 0x180             
$rdx   : 0xc00             
$rsp   : 0x00007fffffffcbf0  →  0x000000000065fa86  →  <mem_size(unsigned+0> mov rax, QWORD PTR [rsp+0x10]
$rbp   : 0xffffffff        
$rsi   : 0x7aa9            
$rdi   : 0x0000000000a01548  →  0x480000104fe81024
$rip   : 0x000000000053b1a8  →  <PackLinuxElf64::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> mov eax, DWORD PTR [rdi+rsi*4+0x14]
$r8    : 0x12              
$r9    : 0xffe0            
$r10   : 0xc10             
$r11   : 0x7               
$r12   : 0x8               
$r13   : 0x180             
$r14   : 0x0000000000a0f4d8  →  0x000000000000000d
$r15   : 0x0               
$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 ────
0x00007fffffffcbf0│+0x0000: 0x000000000065fa86  →  <mem_size(unsigned+0> mov rax, QWORD PTR [rsp+0x10]     ← $rsp
0x00007fffffffcbf8│+0x0008: 0x0000000000400298  →   add eax, DWORD PTR [rax]
0x00007fffffffcc00│+0x0010: 0x000000000000ffe0
0x00007fffffffcc08│+0x0018: 0x000000000000f360
0x00007fffffffcc10│+0x0020: 0x00007ffff7352260  →  <read+16> cmp rax, 0xfffffffffffff001
0x00007fffffffcc18│+0x0028: 0x000000000000f360
0x00007fffffffcc20│+0x0030: 0x00007ffff7362447  →  <lseek64+7> cmp rax, 0xfffffffffffff001
0x00007fffffffcc28│+0x0038: 0x0000000000539692  →  <PackLinuxElf64::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> mov rax, QWORD PTR [rsp+0x10]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x53b197 <PackLinuxElf64::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> mov    rcx, QWORD PTR [rsp+0x8]
     0x53b19c <PackLinuxElf64::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> mov    rdx, QWORD PTR [rsp]
     0x53b1a0 <PackLinuxElf64::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> lea    rsp, [rsp+0x98]
 →   0x53b1a8 <PackLinuxElf64::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> mov    eax, DWORD PTR [rdi+rsi*4+0x14]
     0x53b1ac <PackLinuxElf64::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> test   eax, eax
     0x53b1ae <PackLinuxElf64::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> je     0x53b235 <PackLinuxElf64::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,  LE32,  LE64,  LE64,  LE64> > const*)+7109>
     0x53b1b4 <PackLinuxElf64::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> lea    rsp, [rsp-0x98]
     0x53b1bc <PackLinuxElf64::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> mov    QWORD PTR [rsp], rdx
     0x53b1c0 <PackLinuxElf64::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> mov    QWORD PTR [rsp+0x8], rcx
───────────────────────────────────────────────────────────────────────────────────────────────────────────────── source:p_lx_elf.cpp+5239 ────
   5234             unsigned     const *const hasharr = &buckets[n_bucket]; (void)hasharr;
   5235           //unsigned     const *const gashend = &hasharr[n_bucket];  // minimum, except:
   5236             // Rust and Android trim unused zeroes from high end of hasharr[]
   5237             unsigned bmax = 0;
   5238             for (unsigned j= 0; j < n_bucket; ++j) {
 → 5239                 if (buckets[j]) {
   5240                     if (buckets[j] < symbias) {
   5241                         char msg[50]; snprintf(msg, sizeof(msg),
   5242                                 "bad DT_GNU_HASH bucket[%d] < symbias{%#x}\n",
   5243                                 buckets[j], symbias);
   5244                         throwCantPack(msg);
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "upx.out", stopped, reason: SIGSEGV
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x53b1a8 → PackLinuxElf64::invert_pt_dynamic(this=0xa00030, dynp=<optimized out>)
[#1] 0x53d654 → PackLinuxElf64::invert_pt_dynamic(dynp=<optimized out>, this=0xa00030)
[#2] 0x53d654 → PackLinuxElf64::PackLinuxElf64help1(this=0xa00030, f=0x7fffffffce10)
[#3] 0x53f186 → PackLinuxElf64Le::PackLinuxElf64Le(f=0x7fffffffce10, this=0xa00030)
[#4] 0x53f186 → PackLinuxElf64amd::PackLinuxElf64amd(this=0xa00030, f=0x7fffffffce10)
[#5] 0x6048ac → PackMaster::visitAllPackers(func=0x602c30 <try_unpack(Packer*, void*)>, f=0x7fffffffce10, o=0x7fffffffcfc8, user=0x7fffffffce10)
[#6] 0x6072ca → PackMaster::getUnpacker(f=<optimized out>)
[#7] 0x6072ca → PackMaster::unpack(this=0x7fffffffcfb0, fo=0x7fffffffcee0)
[#8] 0x670dc5 → do_one_file(iname=0x7fffffffdf12 "id:000036,sig:11,src:000541+000827,op:MOpt-splice,rep:2", oname=0x7fffffffd550 "/dev/null")
[#9] 0x67157c → do_files(i=0x4, argc=0x5, argv=0x7fffffffdac8)
```
The instruction to crash is, corresponds to the bucket [j] in the source code
```bash
mov    eax, DWORD PTR [rdi+rsi*4+0x14]
```
The register rdi and rsi are:
```bash
$rsi   : 0x7aa9            
$rdi   : 0x0000000000a01548
```
The DWORD PTR pointer to 0xa20000, where is a invalid address.
```bash
gef➤  x /10xg 0xa20000
0xa20000:    Cannot access memory at address 0xa20000
```
## What should have happened?
Decompress a crafted/suspicious file.

## Do you have an idea for a solution?
A boundary check is needed for loop variable 'j' because the size allocated for variable 'buckets' is limited.
```cpp
upx_uint64_t const *const bitmask = (upx_uint64_t const *)(void const *)&gashtab[4];
unsigned     const *const buckets = (unsigned const *)&bitmask[n_bitmask];
unsigned     const *const hasharr = &buckets[n_bucket]; (void)hasharr;
//unsigned     const *const gashend = &hasharr[n_bucket];  // minimum, except:
// Rust and Android trim unused zeroes from high end of hasharr[]
unsigned bmax = 0;
for (unsigned j= 0; j < n_bucket; ++j) {
    if (buckets[j]) {
        if (buckets[j] < symbias) {
            char msg[50]; snprintf(msg, sizeof(msg),
                                   "bad DT_GNU_HASH bucket[%d] < symbias{%#x}\n",
                                   buckets[j], symbias);
            throwCantPack(msg);
        }
        if (bmax < buckets[j]) {
            bmax = buckets[j];
        }
    }
}
```


## How can we reproduce the issue?
1. compile upx with address-sanitize
2. execute cmd
```bash
upx.out -df $PoC -o /dev/null
```
[Poc](https://github.com/14isnot40/vul_discovery/blob/master/upx/df/hbo_PackLinuxElf64__invert_pt_dynamic) 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:13:08+00:00:
Fixed on `devel` branch by above commit.

---

Comment by jreiser on 2023-04-17 18:13:35+00:00:
Verified as "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:
```
$ $UPX402 -df hbo_PackLinuxElf64__invert_pt_dynamic -o /dev/null
                       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__invert_pt_dynamic: CantUnpackException: bad DT_{0x6} = 0x400010 (beyond EOF)

Unpacked 0 files.
```
Also, the PoC file does not contain the string "UPX", so is independently verified as "not packed by UPX":
```
$ grep UPX hbo_PackLinuxElf64__invert_pt_dynamic
   ## empty output
$ echo $?
1   ## no matches
$ 
```



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: