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

================= Bug Report (1/1) ==================
## Source: GitHub Issue
## URL: https://github.com/upx/upx/issues/378
## Description:
Issue: upx/upx#378
Title: [bug]heap buffer overflow in PackLinuxElf32::invert_pt_dynamic at p_lx_elf.cpp:1688
State: closed
Created by: 14isnot40
Created at: 2020-05-23 14:19:31+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 PackLinuxElf32::invert_pt_dynamic at p_lx_elf.cpp:1688.

ASAN reports:
```bash
==110358==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x61600000fed9 at pc 0x00000045acc8 bp 0x7ffd88c9b020 sp 0x7ffd88c9b010
READ of size 4 at 0x61600000fed9 thread T0
    #0 0x45acc7 in PackLinuxElf32::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16, LE32, LE32, LE32, LE32> > const*) /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.cpp:1688
    #1 0x463ba5 in PackLinuxElf32::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16, LE32, LE32, LE32, LE32> > const*) /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.cpp:1583
    #2 0x463ba5 in PackLinuxElf32::PackLinuxElf32help1(InputFile*) /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.cpp:305
    #3 0x464e96 in PackLinuxElf32Le::PackLinuxElf32Le(InputFile*) /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.h:395
    #4 0x464e96 in PackLinuxElf32x86::PackLinuxElf32x86(InputFile*) /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.cpp:4800
    #5 0x464e96 in PackBSDElf32x86::PackBSDElf32x86(InputFile*) /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.cpp:4817
    #6 0x464e96 in PackFreeBSDElf32x86::PackFreeBSDElf32x86(InputFile*) /home/test/Desktop/EVAULATION/upx/src/p_lx_elf.cpp:4828
    #7 0x4f337a in PackMaster::visitAllPackers(Packer* (*)(Packer*, void*), InputFile*, options_t const*, void*) /home/test/Desktop/EVAULATION/upx/src/packmast.cpp:190
    #8 0x4f50f9 in PackMaster::getUnpacker(InputFile*) /home/test/Desktop/EVAULATION/upx/src/packmast.cpp:248
    #9 0x4f521f in PackMaster::unpack(OutputFile*) /home/test/Desktop/EVAULATION/upx/src/packmast.cpp:266
    #10 0x52a1e6 in do_one_file(char const*, char*) /home/test/Desktop/EVAULATION/upx/src/work.cpp:160
    #11 0x52a69e in do_files(int, int, char**) /home/test/Desktop/EVAULATION/upx/src/work.cpp:271
    #12 0x403ace in main /home/test/Desktop/EVAULATION/upx/src/main.cpp:1538
    #13 0x7ff33c8dc82f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #14 0x404828 in _start (/home/test/Desktop/EVAULATION/upx/src/upx.out+0x404828)

0x61600000fed9 is located 1 bytes to the right of 600-byte region [0x61600000fc80,0x61600000fed8)
allocated by thread T0 here:
    #0 0x7ff33d4d0602 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:1688 PackLinuxElf32::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16, LE32, LE32, LE32, LE32> > const*)
Shadow bytes around the buggy address:
  0x0c2c7fff9f80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c2c7fff9f90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c2c7fff9fa0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c2c7fff9fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c2c7fff9fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c2c7fff9fd0: 00 00 00 00 00 00 00 00 00 00 00[fa]fa fa fa fa
  0x0c2c7fff9fe0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c2c7fff9ff0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c2c7fffa000: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c2c7fffa010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c2c7fffa020: 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
==110358==ABORTING
```

Then analysis the reasons for segv by debugging:
```bash
Program received signal SIGSEGV, Segmentation fault.
0x0000000000527258 in PackLinuxElf32::invert_pt_dynamic (this=this@entry=0xa00030, dynp=<optimized out>) at p_lx_elf.cpp:1688
1688                if (buckets[j]) {
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x0000000000a009c1  →  0x00000000ffffffff
$rbx   : 0x0000000000a00030  →  0x00000000007267a0  →  <vtable+0> add BYTE PTR [rax], al
$rcx   : 0x0               
$rdx   : 0x0               
$rsp   : 0x00007fffffffcc10  →  0x0000000000a007c0  →  0xff0000010900457f
$rbp   : 0xffffff00        
$rsi   : 0x7d88            
$rdi   : 0x6               
$rip   : 0x0000000000527258  →  <PackLinuxElf32::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> mov r9d, DWORD PTR [rax+rsi*4+0x1c]
$r8    : 0x0               
$r9    : 0x0               
$r10   : 0x10              
$r11   : 0x0               
$r12   : 0xd               
$r13   : 0xffffffff        
$r14   : 0x200             
$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 ────
0x00007fffffffcc10│+0x0000: 0x0000000000a007c0  →  0xff0000010900457f     ← $rsp
0x00007fffffffcc18│+0x0008: 0x0000000000000000
0x00007fffffffcc20│+0x0010: 0x00007ffff7352260  →  <read+16> cmp rax, 0xfffffffffffff001
0x00007fffffffcc28│+0x0018: 0x0000000000000258
0x00007fffffffcc30│+0x0020: 0x00007ffff7362447  →  <lseek64+7> cmp rax, 0xfffffffffffff001
0x00007fffffffcc38│+0x0028: 0x00000000005255b2  →  <PackLinuxElf32::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> mov rax, QWORD PTR [rsp+0x10]
0x00007fffffffcc40│+0x0030: 0x0000000000000000
0x00007fffffffcc48│+0x0038: 0x0000000000000000
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x527247 <PackLinuxElf32::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> mov    rcx, QWORD PTR [rsp+0x8]
     0x52724c <PackLinuxElf32::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> mov    rdx, QWORD PTR [rsp]
     0x527250 <PackLinuxElf32::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> lea    rsp, [rsp+0x98]
 →   0x527258 <PackLinuxElf32::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> mov    r9d, DWORD PTR [rax+rsi*4+0x1c]
     0x52725d <PackLinuxElf32::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> test   r9d, r9d
     0x527260 <PackLinuxElf32::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> je     0x5272eb <PackLinuxElf32::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,  LE32,  LE32,  LE32,  LE32> > const*)+7515>
     0x527266 <PackLinuxElf32::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> xchg   ax, ax
     0x527268 <PackLinuxElf32::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> lea    rsp, [rsp-0x98]
     0x527270 <PackLinuxElf32::invert_pt_dynamic(N_Elf::Dyn<N_Elf::ElfITypes<LE16,+0> mov    QWORD PTR [rsp], rdx
───────────────────────────────────────────────────────────────────────────────────────────────────────────────── source:p_lx_elf.cpp+1688 ────
   1683             unsigned     const *const hasharr = &buckets[n_bucket]; (void)hasharr;
   1684           //unsigned     const *const gashend = &hasharr[n_bucket];  // minimum, except:
   1685             // Rust and Android trim unused zeroes from high end of hasharr[]
   1686             unsigned bmax = 0;
   1687             for (unsigned j= 0; j < n_bucket; ++j) {
 → 1688                 if (buckets[j]) {
   1689                     if (buckets[j] < symbias) {
   1690                         char msg[50]; snprintf(msg, sizeof(msg),
   1691                                 "bad DT_GNU_HASH bucket[%d] < symbias{%#x}\n",
   1692                                 buckets[j], symbias);
   1693                         throwCantPack(msg);
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "upx.out", stopped, reason: SIGSEGV
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x527258 → PackLinuxElf32::invert_pt_dynamic(this=0xa00030, dynp=<optimized out>)
[#1] 0x529294 → PackLinuxElf32::invert_pt_dynamic(dynp=<optimized out>, this=0xa00030)
[#2] 0x529294 → PackLinuxElf32::PackLinuxElf32help1(this=0xa00030, f=0x7fffffffce20)
[#3] 0x52c65e → PackLinuxElf32Le::PackLinuxElf32Le(f=0x7fffffffce20, this=0xa00030)
[#4] 0x52c65e → PackLinuxElf32x86::PackLinuxElf32x86(f=0x7fffffffce20, this=0xa00030)
[#5] 0x52c65e → PackBSDElf32x86::PackBSDElf32x86(f=0x7fffffffce20, this=0xa00030)
[#6] 0x52c65e → PackFreeBSDElf32x86::PackFreeBSDElf32x86(this=0xa00030, f=0x7fffffffce20)
[#7] 0x60448c → PackMaster::visitAllPackers(func=0x602c30 <try_unpack(Packer*, void*)>, f=0x7fffffffce20, o=0x7fffffffcfd8, user=0x7fffffffce20)
[#8] 0x6072ca → PackMaster::getUnpacker(f=<optimized out>)
[#9] 0x6072ca → PackMaster::unpack(this=0x7fffffffcfc0, fo=0x7fffffffcef0)

```
The instruction to crash is, corresponds to the bucket [j] in the source code
```bash
mov    r9d, DWORD PTR [rax+rsi*4+0x1c]
```
The value of register rax and rsi are:
```bash
$rax   : 0x0000000000a009c1
$rsi   : 0x7d88
```
The DWORD PTR pointer to 0xa1fffd, where the 0xa20000 is a invalid address.
```bash
gef➤  x /10xg 0xa1fffd
0xa1fffd:    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
unsigned const *const bitmask = (unsigned 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]) {
```


## 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_PackLinuxElf32%5E%25%5E%25invert_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:12:15+00:00:
Fixed on `devel` branch by above commit.

---

Comment by jreiser on 2023-04-17 18:19:23+00:00:
Verified "problem not present" in official release [upx-4.0.2](https://github.com/upx/upx/releases/tag/v4.0.2) of Jan.30, 2023:
```
$ valgrind $UPX402 -df $PoC -o /dev/null
==26280== Memcheck, a memory error detector
==26280== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==26280== Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
==26280== Command: /home2/upx/upx-4.0.2-amd64_linux/upx -df hbo_PackLinuxElf32invert_pt_dynamic -o /dev/null
==26280== 
                       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_PackLinuxElf32invert_pt_dynamic: NotPackedException: not packed by UPX
$ 

$ grep UPX *
   ## empty output: independent verification of "not packed by UPX"
$  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: