ANT-2026-5WVCAVEA · upx

heap-buffer-overflow medium

Severity Claude medium · Security research firm medium · Maintainer -

Discovered by Claude Mythos Preview

REPORT

Anthropic's analysis, sealed at approval. Disclosure to the maintainer was performed by Trail of Bits.

ANT-2026-5WVCAVEA: Heap buffer overflow in un_DT_INIT relocation restore due to attacker-controlled offset near buffer end

An attacker-controlled offset near the end of a heap buffer leads to an out-of-bounds access during DT_INIT relocation restore.

Target

Project: upx
Location: un_DT_INIT
Discovery: static analysis — not yet dynamically reproduced

Technical Details

ASAN: "heap-buffer-overflow ... WRITE of size 8 at 0x7df4f56f1a8f ... located 3 bytes after 22668-byte region". The root cause is a missing bounds check after elf_find_dynamic(): the returned pointer is validated only as t < file_size_u, guaranteeing the start is in-bounds but not that rp + sizeof(Elf64_Rela) is. The assignments rp->r_info = ... (offset +8) and rp->r_addend = ... (offset +16) therefore write past the end of the file_image MemBuffer. A parallel missing check on dynsym[0] allows an out-of-bounds read of the source values.

Reproduction

This finding was identified by static analysis and has not yet been dynamically reproduced. The Technical Details section above describes the code path; a trigger input is not included.

[No reproducer or sanitizer output attached — request from security-cvd@anthropic.com if needed.]

Acknowledgement

This vulnerability was discovered by Claude, Anthropic's AI assistant, and triaged by the Anthropic security team in collaboration with Anthropic Research. Please direct questions to security-cvd@anthropic.com and reference ANT-2026-5WVCAVEA.


Reference: ANT-2026-5WVCAVEA
Anthropic CVD Policy: https://www.anthropic.com/coordinated-vulnerability-disclosure

SECURITY RESEARCH FIRM ANALYSIS

Triage and disclosure were performed by Trail of Bits. The writeup below is the document the firm sent to the maintainer.

Verdict
true positive
Severity
medium

This issue tracker is ONLY used for reporting bugs. Please use stackoverflow for supporting issues.

What's the problem (or question)?

From Anthropic "Trail of Bits" report:

Vulnerability Header

Executive Summary

A heap buffer overflow (WRITE of 8 bytes, 3 bytes past end of buffer) exists in UPX's PackLinuxElf64::un_DT_INIT function when processing a crafted UPX-packed ELF binary. The vulnerability is triggered during the --test or --decompress operations when restoring DT_INIT_ARRAY relocations. A pointer derived from attacker-controlled dynamic section data can point near the end of the file_image buffer, causing writes to Elf64_Rela fields to overflow the heap allocation.

Root Cause Analysis

Technical Description

When UPX tests or decompresses a packed ELF64 binary, un_DT_INIT() processes the dynamic section to restore original DT_INIT_ARRAY entries. At p_lx_elf.cpp:7659, the code retrieves a relocation entry pointer:

Elf64_Rela *rp = (Elf64_Rela *)elf_find_dynamic(dyn_null->d_val);

The elf_find_dynamic() function (line 8373) converts an RVA to a file offset and returns a pointer into file_image:

upx_uint64_t const t = elf_get_offset_from_address(get_te64(&dynp->d_val));
if (t && t < file_size_u) {
    return t + file_image;
}

The check t < file_size_u ensures the start of the returned pointer is within bounds, but does not ensure that the full Elf64_Rela structure (24 bytes: r_offset + r_info + r_addend) fits within the buffer. When rp points close to the end of file_image, the writes at lines 7664-7665 overflow:

rp->r_info = rp_unc->r_info;     // write at rp+8, can overflow
rp->r_addend = rp_unc->r_addend; // write at rp+16, can overflow further

Additionally, rp_unc is cast from &dynsym[0] without verifying that dynsym points to at least one full entry (24 bytes) within file_image before reading r_info and r_addend.

The file_image buffer is allocated at line 1144 with size file_size (22668 bytes for this PoC). The ASAN report confirms the write occurs 3 bytes past the end of this buffer.

First Faulty Condition

Missing bounds check at p_lx_elf.cpp:7661 — after obtaining rp from elf_find_dynamic() and confirming it is non-null, the code proceeds to write sizeof(Elf64_Rela) bytes through rp without verifying the full structure fits within file_image.

Trace Analysis

==7==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7df4f56f1a8f
WRITE of size 8 at 0x7df4f56f1a8f thread T0
    #0 PackLinuxElf64::un_DT_INIT(...)        p_lx_elf.cpp:7664  ← faulting write (rp->r_info)
    #1 PackLinuxElf64::unpack(OutputFile*)     p_lx_elf.cpp:8063  ← calls un_DT_INIT
    #2 Packer::doTest()                        packer.cpp:107     ← test operation
    #3 do_one_file(...)                        work.cpp:340       ← file processing
    #4 do_files(...)                           work.cpp:427       ← main loop
    #5 upx_main(...)                           main.cpp:1323      ← entry point
    #6 LLVMFuzzerTestOneInput                  test_packed_file_fuzzer.cpp:42

0x7df4f56f1a8f is located 3 bytes after 22668-byte region [0x7df4f56ec200,0x7df4f56f1a8c)
allocated by: MemBuffer::alloc() → PackLinuxElf64::PackLinuxElf64help1(InputFile*)

The crash address is 3 bytes past the 22668-byte file_image buffer. The rp pointer was computed to an offset near the end of the file, and the 8-byte write to rp->r_info (at offset 8 within the Elf64_Rela struct) extends past the allocation boundary.

Sibling Verification

Sibling Entrypoint Unpatched Crash Same Root Cause? Patched Result Notes
decompress_packed_file_fuzzer un_shlib_1 (p_lx_elf.cpp:7146) No Still crashes (parent patch only) Different bug: heap overflow in readx due to unchecked len. This is covered as Finding 1 (with its own patch).

Proposed Fix

Add bounds checks before writing through rp and reading through dynsym[0] (see patch.diff):

if ((char *)rp + sizeof(Elf64_Rela) > (char *)&file_image[0] + file_size_u)
    throwCantUnpack("bad DT_INIT_ARRAY relocation offset");
if ((char *)&dynsym[1] > (char *)&file_image[0] + file_size_u)
    throwCantUnpack("bad dynsym for DT_INIT_ARRAY");

This ensures both the relocation entry and the source dynsym entry are fully within the allocated buffer before any writes occur. Validated: the PoC is rejected with CantUnpackException (no ASAN crash) after applying the patch.

Exploitability Assessment

Attack Vector & Reachability

This bug is triggered when UPX processes a crafted UPX-packed ELF64 binary via upx -t <file> (test) or upx -d <file> -o <out> (decompress). The oss-fuzz harness writes the input to a temporary file and invokes upx_main with -t, so fuzz-harness reachability matches real-world reachability for this issue. The attacker needs the victim to run UPX on the attacker-supplied packed binary (e.g., during manual analysis or automated processing).

Reachability verdict (fuzz harness): CONFIRMED

Reachability verdict (real world): CONFIRMED — requires the user to run UPX on an attacker-supplied packed binary.

Prerequisite checks before the vulnerable code is reached: - File is accepted as an ELF64 input and routed to PackLinuxElf64. - PT_DYNAMIC must satisfy p_offset + p_filesz <= file_size (or UPX rejects the file). - The dynamic table must take the "no DT_INIT; DT_INIT_ARRAY/DT_PREINIT_ARRAY" restoration path in un_DT_INIT.

The Attack Sequence

  1. An attacker crafts a UPX-packed ELF64 binary with dynamic section values such that elf_find_dynamic(dyn_null->d_val) returns a relocation pointer near the end of file_image.
  2. A victim runs upx -t or upx -d on the file.
  3. UPX enters PackLinuxElf64::un_DT_INIT, obtains rp via elf_find_dynamic(), and attempts to restore rp->r_info/rp->r_addend, resulting in out-of-bounds heap writes.
  4. This leads to a crash; depending on heap layout and exploitation technique, it may be usable as a memory corruption primitive.

Technical Primitive

Grade 0.50 — WRITE crash with memory corruption. The attacker controls: - Write location: partially controlled via crafted ELF dynamic section values (the dyn_null->d_val field determines where rp points) - Write content: the written data comes from dynsym[0] which is also within the attacker-controlled file image - Write size: 8 bytes (fixed, from r_info field assignment)

The 3-byte overflow past the heap buffer could corrupt heap metadata or adjacent allocations. While the overflow is small, writing 8 bytes of attacker-controlled data at a partially-controlled heap offset provides a potential primitive for heap exploitation.

Mitigation Analysis

Given the small overflow (3 bytes), converting this to reliable code execution would be challenging but not impossible with heap grooming.

Reproduction Steps

```bash

Clone oss-fuzz and build UPX fuzzers with ASAN

cd /path/to/oss-fuzz python3 infra/helper.py build_fuzzers --sanitizer address upx

Run the primary PoC

build/out/upx/test_packed_file_fuzzer

Expected: ASAN reports heap-buffer-overflow WRITE in un_DT_INIT at p_lx_elf.cpp:7664

Please tell us details about your environment.

[poc.bin.gz]((https://github.com/user-attachments/files/26144179/poc.bin.gz)

TIMELINE

Dates from discovery through public reveal.

  1. 2026-03-20 Sent to maintainer
  2. 2026-03-29 Reported to tracker
  3. 2026-05-09 Maintainer acknowledged
  4. 2026-06-08 Patch released
  5. 2026-08-17 Publicly revealed
PROVENANCE

SHA-3-512 hash:

0734711e5afe8fa99b46eae24834a17a136a72dd2c6d5ca662c6872de6ae54b2a686471646eb4d5a746ecb184755a0bfca4172b1e9bd8af0551387a9fe9738e8

Committed 2026-05-07 00:01 PT

Revealed 2026-08-17 10:47 PT

Verify (download preimage.json)

Show preimage JSON
{
  "ant_id": "ANT-2026-5WVCAVEA",
  "bug_class": "Heap Buffer Overflow",
  "claude_severity": "medium",
  "commit_sha": null,
  "created_at": "2026-03-29T20:43:28+00:00",
  "description": "An attacker-controlled offset near the end of a heap buffer leads to an out-of-bounds access during DT_INIT relocation restore.",
  "discovered_at": null,
  "location": "un_DT_INIT",
  "poc_sha256": null,
  "preimage_version": 1,
  "project": "UPX",
  "reproduction": null,
  "technical_details": "ASAN: \"heap-buffer-overflow ... WRITE of size 8 at 0x7df4f56f1a8f ... located 3 bytes after 22668-byte region\". The root cause is a missing bounds check after elf_find_dynamic(): the returned pointer is validated only as `t < file_size_u`, guaranteeing the start is in-bounds but not that `rp + sizeof(Elf64_Rela)` is. The assignments `rp->r_info = ...` (offset +8) and `rp->r_addend = ...` (offset +16) therefore write past the end of the file_image MemBuffer. A parallel missing check on dynsym[0] allows an out-of-bounds read of the source values.",
  "title": "Heap buffer overflow in un_DT_INIT relocation restore due to attacker-controlled offset near buffer end",
  "vendor_severity": "medium"
}