YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
MNN external-weight size mismatch β heap OOB write (CWE-787)
Target: MNN (Alibaba, deployed at scale β Taobao/Alipay/numerous mobile & edge apps)
Class: CWE-787 Out-of-bounds Write (heap), controlled size + controlled content
Route: huntr / Protect AI β MFV (.mnn model-file parsing β memory corruption)
Summary
MNN's external-weight loading for the Scale and LayerNorm ops allocates the
second buffer (bias/beta) using externalInfo[1] (the first size field) but reads
it using externalInfo[2] (an independent, file-controlled size field), with no
externalInfo[2] <= externalInfo[1] check. A malicious .mnn model whose op carries
external = [offset, 16, LARGE] makes FileLoader::read(biasData, LARGE) write
LARGE attacker-controlled bytes into a 16-byte heap buffer β controlled heap OOB write.
Root cause (master source/core/OpCommonUtils.cpp)
Scale case (:557-566):
int outputCount = externalInfo[1] / sizeof(float); // from FIRST size
std::vector<float> scaleData(outputCount);
external->read((char*)scaleData.data(), externalInfo[1]); // consistent
if (externalSize > 2) {
std::vector<float> biasData(outputCount); // sized by externalInfo[1]
external->read((char*)biasData.data(), externalInfo[2]);// read by externalInfo[2] -> OOB
}
LayerNorm case (:580-585): identical pattern (gamma/beta). The code assumes
bias size == scale size (same channel count) but reads the independent field unchecked.
PoC β real files, official MNN 3.6.0 pip wheel
| file | external field |
result |
|---|---|---|
benign.mnn / benign.bin |
[0, 16, 16] |
loads cleanly via Interpreter.createSession (control) |
malicious.mnn / malicious.bin |
[0, 16, 4194304] |
SIGSEGV in OpCommonUtils::createExecutionWithExternal during createSession |
mid.mnn / mid.bin |
[0, 16, 4096] |
intermediate (also faults) |
The benign and malicious models are byte-identical except the single external[2]
size field (and the matching external weight blob length). Regenerate with
gen_poc.py (uses flatc-generated MNN schema); load with load_poc.py (official
pip install MNN 3.6.0 wheel β no custom/instrumented build).
Crash trace (official wheel, gdb)
Thread 1 received signal SIGSEGV, Segmentation fault.
#0 MNN::OpCommonUtils::createExecutionWithExternal(...) <- fault
#1 MNN::Pipeline::allocMemory(bool, bool)
#2 MNN::Session::resize()
#3 MNN::Interpreter::createMultiPathSession(...)
#4 MNN::Interpreter::createMultiPathSession(...)
#5 MNN::Interpreter::createSession(...)
#6 PyMNNInterpreter_createSession (public Python API)
(full trace: trace_gdb.txt)
Reachability
Loading an untrusted external-weight .mnn via the public
Interpreter::createFromFile / createSession path reaches the OOB write β no
special flags required. External-weight is MNN's standard mechanism for shipping
large models; such models arrive via app bundles / downloads / OTA and are
untrusted by design.
dedup
MNN has 0 published security advisories; the OOB issues in its tracker are
app-crash bugs, not this external-weight alloc-by-[1]/read-by-[2] vector.
Complementary extracted-ASan evidence (verbatim heap-buffer-overflow WRITE of size 1000 into a 16-byte region) is recorded in the accompanying research notes.
Suggested fix
Validate externalInfo[2] == externalInfo[1] (bias matches scale) β or more
generally externalInfo[i+1] <= the destination buffer's allocated bytes β before
each read, and reject the model with MNN_ERROR on mismatch.