Bug: Global-buffer-overflow in HL1MDLLoader::read_sequence_infos()
- Dominant language
- C++
- Stars
- 13.2k
- Forks
- 3.2k
- Avg merge
- 2d 8h
- Merged PRs (30d)
- 21
Description
**Describe the bug**
A **global-buffer-overflow** occurs in `HL1MDLLoader::read_sequence_infos()` (File: `code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp`) when parsing a malformed Half-Life 1 MDL file. The file contains an extremely long bone name that fully fills the `aiString` buffer, causing an out-of-bounds memory read of 1023 bytes during `aiString` copy operation, which accesses memory in the global data section.
**To Reproduce**
Steps to reproduce the behavior:
1. Clone the assimp repository and build it refer to oss-fuzz.
```shell
export CC=clang \
CXX=clang++ \
CFLAGS='-fsanitize=address -O0 -g' \
CXXFLAGS='-fsanitize=address -O0 -g' \
LIB_FUZZING_ENGINE='-fsanitize=fuzzer'
```
2. Run the PoC using assimp_fuzzer:
[poc.zip](https://github.com/user-attachments/files/27228962/poc.zip)
The PoC is provided as a zip archive. After extracting, run:
```shell
./assimp_fuzzer ./poc.mdl
```
**ASAN Report**
```
=================================================================
==1912005==ERROR: AddressSanitizer: global-buffer-overflow on address 0x62d8f289cd80 at pc 0x62d8f0e83b06 bp 0x7ffc90881eb0 sp 0x7ffc90881670
READ of size 1023 at 0x62d8f289cd80 thread T0
#0 0x62d8f0e83b05 in __asan_memcpy (/home/hexijie/fuzz/fuzzers/assimp_fuzzer+0x3c5b05) (BuildId: 3707c90bb983c84eac8c279d24589825b44e573a)
#1 0x62d8f0ef4ae8 in aiString::aiString(aiString const&) /home/hexijie/fuzz/project/assimp/include/assimp/types.h:283:9
#2 0x62d8f0ef47e1 in bool aiMetadata::Set(unsigned int, std::__cxx11::basic_string, std::allocator> const&, aiString const&) /home/hexijie/fuzz/project/assimp/include/assimp/metadata.h:383:40
#3 0x62d8f17cc234 in Assimp::MDL::HalfLife::HL1MDLLoader::read_sequence_infos() /home/hexijie/fuzz/project/assimp/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp:1120:13
#4 0x62d8f17b869e in Assimp::MDL::HalfLife::HL1MDLLoader::load_file() /home/hexijie/fuzz/project/assimp/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp:150:13
#5 0x62d8f17b80a4 in Assimp::MDL::HalfLife::HL1MDLLoader::HL1MDLLoader(aiScene*, Assimp::IOSystem*, unsigned char const*, unsigned long, std::__cxx11::basic_string, std::allocator> const&, Assimp::MDL::HalfLife::HL1ImportSettings const&) /home/hexijie/fuzz/project/assimp/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp:102:5
#6 0x62d8f1794d9c in Assimp::MDLImporter::InternReadFile_HL1(std::__cxx11::basic_string, std::allocator> const&, unsigned int) /home/hexijie/fuzz/project/assimp/code/AssetLib/MDL/MDLLoader.cpp:2004:28
#7 0x62d8f17886c7 in Assimp::MDLImporter::InternReadFile(std::__cxx11::basic_string, std::allocator> const&, aiScene*, Assimp::IOSystem*) /home/hexijie/fuzz/project/assimp/code/AssetLib/MDL/MDLLoader.cpp:254:17
#8 0x62d8f139798b in Assimp::BaseImporter::ReadFile(Assimp::Importer*, std::__cxx11::basic_string, std::allocator> const&, Assimp::IOSystem*) /home/hexijie/fuzz/project/assimp/code/Common/BaseImporter.cpp:131:9
#9 0x62d8f0ecd0cf in Assimp::Importer::ReadFile(char const*, unsigned int) /home/hexijie/fuzz/project/assimp/code/Common/Importer.cpp:709:30
#10 0x62d8f0ecaa19 in Assimp::Importer::ReadFileFromMemory(void const*, unsigned long, unsigned int, char const*) /home/hexijie/fuzz/project/assimp/code/Common/Importer.cpp:507:9
#11 0x62d8f0ec6705 in LLVMFuzzerTestOneInput /home/hexijie/fuzz/project/assimp/build/../fuzz/assimp_fuzzer.cc:62:34
#12 0x62d8f0dd1b24 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) (/home/hexijie/fuzz/fuzzers/assimp_fuzzer+0x313b24) (BuildId: 3707c90bb983c84eac8c279d24589825b44e573a)
#13 0x62d8f0dbac56 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) (/home/hexijie/fuzz/fuzzers/assimp_fuzzer+0x2fcc56) (BuildId: 3707c90bb983c84eac8c279d24589825b44e573a)
#14 0x62d8f0dc070a in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/home/hexijie/fuzz/fuzzers/assimp_fuzzer+0x30270a) (BuildId: 3707c90bb983c84eac8c279d24589825b44e573a)
#15 0x62d8f0deaec6 in main (/home/hexijie/fuzz/fuzzers/assimp_fuzzer+0x32cec6) (BuildId: 3707c90bb983c84eac8c279d24589825b44e573a)
#16 0x7010a8a2a1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#17 0x7010a8a2a28a in __libc_start_main csu/../csu/libc-start.c:360:3
#18 0x62d8f0db5824 in _start (/home/hexijie/fuzz/fuzzers/assimp_fuzzer+0x2f7824) (BuildId: 3707c90bb983c84eac8c279d24589825b44e573a)
0x62d8f289cd80 is located 32 bytes before global variable 'desc' defined in '/home/hexijie/fuzz/project/assimp/code/AssetLib/MDL/MDLLoader.cpp:69' (0x62d8f289cda0) of size 64
0x62d8f289cd80 is located 0 bytes after global variable 'vtable for Assimp::MDLImporter' defined in '/home/hexijie/fuzz/project/assimp/code/AssetLib/MDL/MDLLoader.cpp' (0x62d8f289cd40) of size 64
SUMMARY: AddressSanitizer: global-buffer-overflow (/home/hexijie/fuzz/fuzzers/assimp_fuzzer+0x3c5b05) (BuildId: 3707c90bb983c84eac8c279d24589825b44e573a) in __asan_memcpy
Shadow bytes around the buggy address:
0x62d8f289cb00: 00 00 00 f9 f9 f9 f9 f9 00 00 00 00 00 00 00 00
0x62d8f289cb80: f9 f9 f9 f9 00 00 00 00 00 00 00 00 f9 f9 f9 f9
0x62d8f289cc00: 00 00 00 f9 f9 f9 f9 f9 00 00 00 00 00 00 00 00
0x62d8f289cc80: f9 f9 f9 f9 00 00 00 00 00 00 00 00 f9 f9 f9 f9
0x62d8f289cd00: 00 00 00 f9 f9 f9 f9 f9 00 00 00 00 00 00 00 00
=>0x62d8f289cd80:[f9]f9 f9 f9 00 00 00 00 00 00 00 00 f9 f9 f9 f9
0x62d8f289ce00: 00 00 00 f9 f9 f9 f9 f9 00 00 00 00 00 00 00 00
0x62d8f289ce80: f9 f9 f9 f9 00 00 00 00 00 00 00 00 f9 f9 f9 f9
0x62d8f289cf00: 00 00 00 f9 f9 f9 f9 f9 00 00 00 00 00 00 00 00
0x62d8f289cf80: f9 f9 f9 f9 00 00 00 00 00 00 00 00 f9 f9 f9 f9
0x62d8f289d000: 00 00 00 f9 f9 f9 f9 f9 00 00 00 00 00 00 00 00
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
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
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
Left alloca redzone: ca
Right alloca redzone: cb
==1912005==ABORTING
```
**Cause**
The MDL parser reads bone names from untrusted input without proper length validation, then stores them directly into `aiString` instances. A specially crafted malformed MDL file can fully populate the fixed-size `aiString::data[1024]` buffer. When this `aiString` is copied at line 1120, the ensuing 1023-byte memcpy operation causes an out-of-bounds read within the global data section, eventually leading to a global-buffer-overflow condition.
**Screenshots**
- `HL1MDLLoader::read_sequence_infos()` calls `md->Set()` to store the bone name metadata at line 1120.
- The bone name is read from the file into an `aiString` without length checks, allowing the internal `data` buffer to be fully filled.
- When the corrupted `aiString` is copied, the 1023-byte memcpy causes an out-of-bounds read into the global data section.
**Platform (please complete the following information):**
- OS: Ubuntu 22.04 (x86_64)
- Version: commit 17c12da558d23d70e4c728b30958e95cd616cbcb
Contributor guide
Research direction
Start in code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp at read_sequence_infos(), then inspect the md->Set() call around line 1120 and the aiString handling described in the report. Build the oss-fuzz-style assimp_fuzzer with AddressSanitizer and run the extracted poc.mdl. Done means the malformed file no longer triggers the reported global-buffer-overflow while normal Half-Life 1 MDL loading still works.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- computer-graphics
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100