PoC: two heap-buffer-overflows in alibaba/MNN MNNConvert ONNX frontend
Affected: alibaba/MNN model converter (MNNConvert), ONNX frontend.
Verified on master HEAD 0bff03c. Format: ONNX (.onnx). Trigger: default flags β a victim runs
MNNConvert -f ONNX --modelFile attacker.onnx. Both are AddressSanitizer-confirmed on the stock binary.
Common root: MNNConvert trusts attacker-controlled ONNX protobuf fields (node arity, tensor dims, raw_data)
as spec-conformant and uses them as vector indices / read lengths with no validation, and MNN's CHECK/DCHECK
(tools/converter/include/logkit.h:69-81) are non-fatal loggers, so nothing rejects the malformed node/tensor.
Bug 1 (evil.onnx) β CWE-787/125 heap OOB read+write in SequenceInsertOnnx::run
SequenceOnnx.cpp:120 std::swap(indexs[1], indexs[2]). A SequenceInsert node with 1 input β
dstOp->inputIndexes size 1; the guard if(indexs.size()==2) (:109) only upgrades the 2-input form, so a
1-input node falls through to the swap β OOB read+write +4/+8 bytes past the size-1 std::vector<int>.
==ERROR: AddressSanitizer: heap-buffer-overflow ... READ of size 4
#0 std::swap<int> bits/move.h:197
#1 SequenceInsertOnnx::run SequenceOnnx.cpp:120
#2 onnx2MNNNet onnxConverter.cpp:160
#3 MNN::Cli::convertModel #4 main
0 bytes after 4-byte region (size-1 vector<int>, allocated onnxConverter.cpp:151)
Impact: uncontrolled 4-8 B heap corruption / crash. Negative control (control.onnx, valid 2-input) converts clean.
Bug 2 (evil_m1.onnx) β CWE-125 heap OOB read β info-disclosure in convertTensorToBlob
onnxOpConverter.cpp:327-436. dataSize = product(constantTp->dims()) drives the per-dtype materialize read
loop, but the source buffer (alignContent, :292) is sized to raw_data().size(), with no check of the
ONNX invariant raw_data.size() == product(dims)Β·elemsize. A tensor initializer with dims=[1000000],
FLOAT, and 4 bytes of raw_data β the FLOAT loop (:411-412) reads 1,000,000 floats from an 8-byte buffer.
==ERROR: AddressSanitizer: heap-buffer-overflow ... READ of size 4
#0 convertTensorToBlob onnxOpConverter.cpp:412
#1 makeConst (lambda) onnxConverter.cpp:105
#2 onnx2MNNNet onnxConverter.cpp:142
#3 MNN::Cli::convertModel #4 main
0 bytes after 8-byte region (alignContent, allocated onnxOpConverter.cpp:292)
Impact: the over-read bytes are stored into constantParam->float32s and serialized into the output .mnn β
victim process heap memory is exfiltrated into the converted model (info-disclosure), then SIGSEGV on far reads.
The over-read LENGTH is attacker-controlled (via dims, up to GBs). Negative control (control_m1.onnx,
dims=[1] matched) converts clean.
Reproduction (both)
git clone --depth 1 https://github.com/alibaba/MNN.git && cd MNN && mkdir build && cd build
cmake .. -DMNN_BUILD_CONVERTER=ON -DCMAKE_BUILD_TYPE=Debug -DMNN_BUILD_TORCH=OFF \
-DCMAKE_CXX_FLAGS="-fsanitize=address -g" -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address"
make MNNConvert -j
LD_LIBRARY_PATH=$PWD ASAN_OPTIONS=detect_leaks=0 ./MNNConvert -f ONNX --modelFile evil.onnx --MNNModel /tmp/a.mnn --bizCode poc # Bug 1
LD_LIBRARY_PATH=$PWD ASAN_OPTIONS=detect_leaks=0 ./MNNConvert -f ONNX --modelFile evil_m1.onnx --MNNModel /tmp/b.mnn --bizCode poc # Bug 2
Suggested fixes
- Bug 1: validate
SequenceInsert/sibling op input arity before use (or makelogkitCHECK fatal). - Bug 2: in
convertTensorToBlob, requireraw_data.size() == product(dims) * elemsize(and the external-datalength) before the materialize loop.