Headline
CVE-2021-43313: [bug]heap buffer overflow in PackLinuxElf32::invert_pt_dynamic at p_lx_elf.cpp:1688 · Issue #378 · upx/upx
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.
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:
==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:
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
mov r9d, DWORD PTR [rax+rsi*4+0x1c]
The value of register rax and rsi are:
$rax : 0x0000000000a009c1 $rsi : 0x7d88
The DWORD PTR pointer to 0xa1fffd, where the 0xa20000 is a invalid address.
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.
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?
- compile upx with address-sanitize
- execute cmd
upx.out -df $PoC -o /dev/null
Poc can be found here.
Please tell us details about your environment.
- UPX version used (upx --version):
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® Core™ i5-6200U CPU @ 2.30GHz with 8GB - Target Operating System and version:
same as Host - Target CPU architecture:
same as Host