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_size before the loop.
  • non-mmap path: validate parameter_size <= tensor.bytes() (and parameter_offset + parameter_size <= file_size) before file.read; fix the assert direction and move it ahead of the read.

Files

  • gen_malicious_mllm.py β€” generator for both PoC files
  • malicious_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 library
  • asan_write_trace.txt β€” ASan report for Bug 2
  • segv_mmap_trace.txt β€” crash evidence for Bug 1
Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support