Heap Buffer Overflow Write in DWARF Unit Index Parsing
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
**Bug summary**
LLVM's DWARF unit index parser (`DWARFUnitIndex::parseImpl`) contains a heap buffer overflow write triggered by crafted `.debug_cu_index` sections in DWARF package files. The parser allocates a `Contribs` array sized by the `NumUnits` header field but indexes into it using bucket `Index` values read from the file without bounds checking. A crafted input with `NumUnits=1` and a bucket `Index=2` writes an 8-byte pointer one element past the end of the heap allocation. This is reachable via any tool that parses DWARF data (e.g., `llvm-dwarfdump`, `lldb`), but requires a developer to process a malicious object file.
**PoC / reproduction**
- Entrypoint: `llvm-dwarfdump-fuzzer`
Reproduce via OSS-Fuzz (uses `reproduce.py`, which reads the PoC file above):
```bash
export OSS_FUZZ_DIR=~/oss-fuzz
python3 reproduce.py poc.bin
```
PoC contents:
This PoC is binary. Reconstruct it from base64:
```bash
python3 - <<'PY'
import base64
b64 = '''f0VMRgIBAQAAAAAAAAAAAAEAPgABAAAAAAAAAAAAAAAAAAAAAAAAAJgAAAAAAAAAAAAAAEAAOAAA
AEAAAwACAAIAAAABAAAAAQAAAAIAAAAAAAAAAAAAAL66/srvvq3eAAAAAAIAAAABAAAAAAEAABAA
AAAAAAAAAC5kZWJ1Z19jdV9pbmRleAAuc2hzdHJ0YWIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAAAAAA
AAAAAAAAAAAAAAAAQAAAAAAAAAA0AAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAABEAAAAD
AAAAAAAAAAAAAAAAAAAAAAAAAHgAAAAAAAAAHAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAA
AAA='''
open('poc.bin', 'wb').write(base64.b64decode(b64))
print('wrote poc.bin')
PY
```
**ASAN backtrace**
```text
+ FUZZER=llvm-dwarfdump-fuzzer
+ shift
+ '[' '!' -v TESTCASE ']'
+ TESTCASE=/testcase
+ '[' '!' -f /testcase ']'
+ export RUN_FUZZER_MODE=interactive
+ RUN_FUZZER_MODE=interactive
+ export FUZZING_ENGINE=libfuzzer
+ FUZZING_ENGINE=libfuzzer
+ export SKIP_SEED_CORPUS=1
+ SKIP_SEED_CORPUS=1
+ run_fuzzer llvm-dwarfdump-fuzzer -runs=100 /testcase
vm.mmap_rnd_bits = 28
/out/llvm-dwarfdump-fuzzer -- -rss_limit_mb=2560 -timeout=25 -runs=100 /testcase < /dev/null
INFO: libFuzzer ignores flags that start with '--'
INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 1423643022
INFO: Loaded 1 modules (326556 inline 8-bit counters): 326556 [0x5555584433a0, 0x555558492f3c),
INFO: Loaded 1 PC tables (326556 PCs): 326556 [0x555558492f40,0x55555898e900),
/out/llvm-dwarfdump-fuzzer: Running 1 inputs 100 time(s) each.
Running: /testcase
=================================================================
[1m[31m==14==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7c1ff6de0098 at pc 0x555556adb6fa bp 0x7fffffffd890 sp 0x7fffffffd888
[1m[0m[1m[34mWRITE of size 8 at 0x7c1ff6de0098 thread T0[1m[0m
SCARINESS: 42 (8-byte-write-heap-buffer-overflow)
#0 0x555556adb6f9 in llvm::DWARFUnitIndex::parseImpl(llvm::DataExtractor) /src/llvm-project/llvm/lib/DebugInfo/DWARF/DWARFUnitIndex.cpp:163:25
#1 0x555556ada0ae in llvm::DWARFUnitIndex::parse(llvm::DataExtractor) /src/llvm-project/llvm/lib/DebugInfo/DWARF/DWARFUnitIndex.cpp:120:12
#2 0x5555569b2b3a in (anonymous namespace)::ThreadUnsafeDWARFContextState::getCUIndex() /src/llvm-project/llvm/lib/DebugInfo/DWARF/DWARFContext.cpp:333:18
#3 0x55555697a5dc in getCUIndex /src/llvm-project/llvm/lib/DebugInfo/DWARF/DWARFContext.cpp:1423:17
#4 0x55555697a5dc in llvm::DWARFContext::dump(llvm::raw_ostream&, llvm::DIDumpOptions, std::__1::array, 28ul>) /src/llvm-project/llvm/lib/DebugInfo/DWARF/DWARFContext.cpp:1213:5
#5 0x55555699d5e6 in llvm::DWARFContext::dump(llvm::raw_ostream&, llvm::DIDumpOptions) /src/llvm-project/llvm/include/llvm/DebugInfo/DWARF/DWARFContext.h:160:5
#6 0x55555696a42f in LLVMFuzzerTestOneInput /src/llvm-project/llvm/tools/llvm-dwarfdump/fuzzer/llvm-dwarfdump-fuzzer.cpp:38:10
#7 0x55555680724d in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:619:13
#8 0x5555567f0f62 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:329:6
#9 0x5555567f6e30 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:865:9
#10 0x5555568239c2 in main /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerMain.cpp:20:10
#11 0x7ffff7c1d082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 5792732f783158c66fb4f3756458ca24e46e827d)
#12 0x5555567ea04d in _start (/out/llvm-dwarfdump-fuzzer+0x129604d)
DEDUP_TOKEN: llvm::DWARFUnitIndex::parseImpl(llvm::DataExtractor)--llvm::DWARFUnitIndex::parse(llvm::DataExtractor)--(anonymous namespace)::ThreadUnsafeDWARFContextState::getCUIndex()
[1m[32m0x7c1ff6de0098 is located 0 bytes after 8-byte region [0x7c1ff6de0090,0x7c1ff6de0098)
[1m[0m[1m[35mallocated by thread T0 here:[1m[0m
#0 0x555556968a0d in operator new[](unsigned long) /src/llvm-project/compiler-rt/lib/asan/asan_new_delete.cpp:111:37
#1 0x555556ada782 in make_unique /usr/local/bin/../include/c++/v1/__memory/unique_ptr.h:765:55
#2 0x555556ada782 in llvm::DWARFUnitIndex::parseImpl(llvm::DataExtractor) /src/llvm-project/llvm/lib/DebugInfo/DWARF/DWARFUnitIndex.cpp:147:7
#3 0x555556ada0ae in llvm::DWARFUnitIndex::parse(llvm::DataExtractor) /src/llvm-project/llvm/lib/DebugInfo/DWARF/DWARFUnitIndex.cpp:120:12
#4 0x5555569b2b3a in (anonymous namespace)::ThreadUnsafeDWARFContextState::getCUIndex() /src/llvm-project/llvm/lib/DebugInfo/DWARF/DWARFContext.cpp:333:18
#5 0x55555697a5dc in getCUIndex /src/llvm-project/llvm/lib/DebugInfo/DWARF/DWARFContext.cpp:1423:17
#6 0x55555697a5dc in llvm::DWARFContext::dump(llvm::raw_ostream&, llvm::DIDumpOptions, std::__1::array, 28ul>) /src/llvm-project/llvm/lib/DebugInfo/DWARF/DWARFContext.cpp:1213:5
#7 0x55555699d5e6 in llvm::DWARFContext::dump(llvm::raw_ostream&, llvm::DIDumpOptions) /src/llvm-project/llvm/include/llvm/DebugInfo/DWARF/DWARFContext.h:160:5
#8 0x55555696a42f in LLVMFuzzerTestOneInput /src/llvm-project/llvm/tools/llvm-dwarfdump/fuzzer/llvm-dwarfdump-fuzzer.cpp:38:10
#9 0x55555680724d in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:619:13
#10 0x5555567f0f62 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:329:6
#11 0x5555567f6e30 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:865:9
#12 0x5555568239c2 in main /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerMain.cpp:20:10
#13 0x7ffff7c1d082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 5792732f783158c66fb4f3756458ca24e46e827d)
DEDUP_TOKEN: operator new[](unsigned long)--make_unique--llvm::DWARFUnitIndex::parseImpl(llvm::DataExtractor)
SUMMARY: AddressSanitizer: heap-buffer-overflow /src/llvm-project/llvm/lib/DebugInfo/DWARF/DWARFUnitIndex.cpp:163:25 in llvm::DWARFUnitIndex::parseImpl(llvm::DataExtractor)
Shadow bytes around the buggy address:
0x7c1ff6ddfe00: [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m
0x7c1ff6ddfe80: [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m
0x7c1ff6ddff00: [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m
0x7c1ff6ddff80: [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m
0x7c1ff6de0000: [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[0m00[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[0m00[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[0m00[1m[0m [1m[31mfa[1m[0m
=>0x7c1ff6de0080: [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[0m00[1m[0m[[1m[31mfa[1m[0m][1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[0m04[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[0m04[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[0m00[1m[0m [1m[0m00[1m[0m
0x7c1ff6de0100: [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m
0x7c1ff6de0180: [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m
0x7c1ff6de0200: [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m
0x7c1ff6de0280: [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m
0x7c1ff6de0300: [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m [1m[31mfa[1m[0m
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: [1m[0m00[1m[0m
Partially addressable: [1m[0m01[1m[0m [1m[0m02[1m[0m [1m[0m03[1m[0m [1m[0m04[1m[0m [1m[0m05[1m[0m [1m[0m06[1m[0m [1m[0m07[1m[0m
Heap left redzone: [1m[31mfa[1m[0m
Freed heap region: [1m[35mfd[1m[0m
Stack left redzone: [1m[31mf1[1m[0m
Stack mid redzone: [1m[31mf2[1m[0m
Stack right redzone: [1m[31mf3[1m[0m
Stack after return: [1m[35mf5[1m[0m
Stack use after scope: [1m[35mf8[1m[0m
Global redzone: [1m[31mf9[1m[0m
Global init order: [1m[36mf6[1m[0m
Poisoned by user: [1m[34mf7[1m[0m
Container overflow: [1m[34mfc[1m[0m
Array cookie: [1m[31mac[1m[0m
Intra object redzone: [1m[33mbb[1m[0m
ASan internal: [1m[33mfe[1m[0m
Left alloca redzone: [1m[34mca[1m[0m
Right alloca redzone: [1m[34mcb[1m[0m
==14==ABORTING
```
**Fix (diff)**
```diff
diff --git a/llvm/lib/DebugInfo/DWARF/DWARFUnitIndex.cpp b/llvm/lib/DebugInfo/DWARF/DWARFUnitIndex.cpp
index a4487e2dc..8911a7ae8 100644
--- a/llvm/lib/DebugInfo/DWARF/DWARFUnitIndex.cpp
+++ b/llvm/lib/DebugInfo/DWARF/DWARFUnitIndex.cpp
@@ -157,6 +157,8 @@ bool DWARFUnitIndex::parseImpl(DataExtractor IndexData) {
auto Index = IndexData.getU32(&Offset);
if (!Index)
continue;
+ if (Index > Header.NumUnits)
+ return false;
Rows[i].Index = this;
Rows[i].Contributions =
std::make_unique(Header.NumColumns);
```
**Background of that issue**
This bug was found as a part of an Anthropic research into the use of large language models for automated vulnerability discovery in open source software. Anthropic then engaged Trail of Bits to independently triage and validate those issues.
Contributor guide
Research direction
Start in llvm/lib/DebugInfo/DWARF/DWARFUnitIndex.cpp, especially DWARFUnitIndex::parseImpl and the llvm-dwarfdump-fuzzer entrypoint. Reproduce the issue with reproduce.py and the supplied PoC under ASan, then trace the NumUnits and bucket Index handling. Done means malformed DWARF unit indexes no longer produce an out-of-bounds write and the PoC no longer triggers ASan.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 48/100