YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
PoC: mllm ParameterFile V2 β two load-time memory-safety bugs in the default model loader (ASan-verified, current master)
Target
mllm β C++ on-device multimodal LLM inference framework (~1.6k stars, actively maintained). .mllm is its native model parameter format; downloading and loading .mllm files from model zoos / third parties is the core usage.
Bug 1 β mmap default path: unchecked params_desc_offset / num_params β OOB read (CWE-125)
ParameterFileIOImpl<kCPU, kV2>::read(path, /*mmap=*/true) (the default entry, mllm/core/ParameterFile.cpp) does:
auto* header = static_cast<ModelFileV2Descriptor*>(mmap_file->data());
char* params_desc_begin = static_cast<char*>(mmap_file->data()) + header->params_desc_offset; // file-controlled, never checked vs file size
for (uint32_t i = 0; i < header->num_params; i++) { // file-controlled, no upper bound
auto* param_desc = reinterpret_cast<ModelFileV2ParamsDescriptor*>(params_desc_begin + i * sizeof(...));
std::string name(param_desc->_param_name_view()); // dereferences the wild pointer
malicious_mmap_descoffset.mllm (532 bytes) sets params_desc_offset = 0x10000000 (256 MB past EOF). Iteration 0 dereferences mmap_base + 256MB β SIGSEGV on load. The same loop allows num_params-driven walking off the mapping. No bounds check exists anywhere on either field.
Bug 2 β non-mmap path: parameter_size OOB heap write (CWE-787)
The non-mmap V2 read allocates each tensor from its file-supplied shape (β tensor.bytes()), then:
file.seekg(param_desc.parameter_offset);
file.read(static_cast<char*>(s->ptr_), param_desc.parameter_size); // parameter_size is file-controlled, INDEPENDENT of shape
MLLM_RT_ASSERT(param_desc.parameter_size >= tensor.bytes()); // (a) inverted direction, (b) AFTER the read
malicious_write_paramsz.mllm declares shape=[4] (fp32 β 16-byte heap buffer) with parameter_size = 1 MB β heap-buffer-overflow WRITE of ~1 MB of attacker-controlled file bytes. Verified under AddressSanitizer (WRITE of size 1048576, 0 bytes after 16-byte region). The only guard (parameter_size >= tensor.bytes()) is (a) the wrong direction β it passes exactly when the overflow occurs β and (b) evaluated after the read.
Heap OOB write with attacker-controlled size and content is an exploitation-grade primitive: the per-tensor loop allocates TensorStorage objects (which carry vtables) adjacent to overflow targets, enabling vtable corruption β control-flow hijack (analyzed separately; the ASan write proof is the reproducible artifact here).
Reproduction
Verified on mllm master (cloned 2026-07-18) built with AddressSanitizer:
# 1. Build mllm with ASan (submodules required; PIC fixes the xxhash/.so link)
git clone --depth 1 --recurse-submodules https://github.com/UbiquitousLearning/mllm.git
cd mllm
cmake -B build-asan -DMLLM_ENABLE_TEST=OFF -DMLLM_ENABLE_BENCHMARK=OFF -DMLLM_ENABLE_EXAMPLE=OFF \
-DCMAKE_BUILD_TYPE=Debug -DCMAKE_POSITION_INDEPENDENT_CODE=ON \
-DCMAKE_CXX_FLAGS="-fsanitize=address -g -O0" -DCMAKE_C_FLAGS="-fsanitize=address -g -O0"
cmake --build build-asan -j
# 2. Build poc_driver.cpp (this repo) against the library and run:
./poc_driver malicious_mmap_descoffset.mllm 1
# β SIGSEGV in strnlen β ModelFileV2ParamsDescriptor::_param_name_view
# β ParameterFileIOImpl<kCPU,kV2>::read (ParameterFile.cpp:302) [Bug 1, default path]
./poc_driver malicious_write_paramsz.mllm 0
# β AddressSanitizer: heap-buffer-overflow WRITE of size 261840, 0 bytes after 16-byte region
# in std::istream::read β ParameterFileIOImpl<kCPU,kV2>::read (ParameterFile.cpp:366) [Bug 2]
# allocation trace: Tensor::alloc (Tensor.cpp:72) β 16-byte chunk
gen_malicious_mllm.py regenerates both model files. Raw outputs: segv_mmap_trace.txt, asan_write_trace.txt.
Note on the Bug 2 driver: it registers a malloc-based CPU allocator (semantically identical to the stock CPUAllocator's malloc path) so ASan tracks the 16-byte tensor chunk individually; the stock buddy-pool configuration hands the tensor a chunk inside a large pool arena, where the same overflow silently corrupts neighboring pool memory instead (still OOB, just not ASan-visible).
Impact
Any application loading an untrusted .mllm model β the standard on-device LLM workflow β is exposed: denial of service via the default mmap path (Bug 1) and attacker-controlled heap corruption via the non-mmap path (Bug 2). Same vulnerability class as CVE-2026-27940 (llama.cpp GGUF) / CVE-2026-7482 ("Bleeding Llama", CVSS 9.1): file-supplied size/offset/count fields trusted without bounds validation during model parsing β here in an under-scrutinized on-device LLM runtime, unpatched on master.
Suggested fix
- mmap path: validate
params_desc_offset + num_params * sizeof(ModelFileV2ParamsDescriptor) <= file_sizebefore the loop. - non-mmap path: validate
parameter_size <= tensor.bytes()(andparameter_offset + parameter_size <= file_size) beforefile.read; fix the assert direction and move it ahead of the read.
Files
gen_malicious_mllm.pyβ generator for both PoC filesmalicious_mmap_descoffset.mllmβ Bug 1 trigger (532 B)malicious_write_paramsz.mllmβ Bug 2 trigger (~1 MB)poc_driver.cppβ loader driver against the real libraryasan_write_trace.txtβ ASan report for Bug 2segv_mmap_trace.txtβ crash evidence for Bug 1