Skip to content

Stack buffer overflow in meep::grid_volume::str when writing to caller-provided buffer #3136

@hgarrereyn

Description

@hgarrereyn

Hi, there is a potential bug in meep::grid_volume::str reachable via normal use.

This bug was reproduced on 0b21c67.

Description

The following testcase creates a meep::grid_volume and then tries to print it to a fixed size buffer.

Inside meep::grid_volume::str, it uses sprintf repeatedly with a pattern like:

written += snprintf(buffer + written, buflen - written, ...)

However, snprintf actually returns the number of bytes that would have been written, not the number of bytes that were actually written. Hence written can become larger than buflen, and then subsequent snprintf calls will overflow.

POC

The following testcase demonstrates the bug:

testcase.cpp

#include <cstdio>
#include <cstdlib>
#include <cstdint>
#include <cstring>

#include "/fuzz/install/include/meep/mympi.hpp"
#include "/fuzz/install/include/meep/vec.hpp"
extern "C" {
  #include "/fuzz/install/include/meep/meep-config.h"
}
#include "/fuzz/install/include/meep/material_data.hpp"
#include "/fuzz/install/include/meep/adjust_verbosity.hpp"
#include "/fuzz/install/include/meep.hpp"
#include "/fuzz/install/include/meep/meepgeom.hpp"

int main() {
  // Simple, benign 3D grid; no crafted parameters needed
  meep::grid_volume gv = meep::vol3d(10.0, 11.0, 9.0, 1.0);
  // Caller-provided stack buffer
  char buf[128];
  // This call overflows buf[] inside meep::grid_volume::str
  const char *s = gv.str(buf, sizeof(buf));
  // Use the result to prevent optimizing away
  if (s) puts(s);
  return 0;
}

stdout


stderr

=================================================================
==1==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fd4ddc00397 at pc 0x56460742db52 bp 0x7ffc08e0f920 sp 0x7ffc08e0f0c0
WRITE of size 3 at 0x7fd4ddc00397 thread T0
    #0 0x56460742db51 in vsnprintf (/fuzz/test+0x50b51) (BuildId: 33b5038da7b3b4d3720d97aa1dcfb185543a5654)
    #1 0x56460742f430 in snprintf (/fuzz/test+0x52430) (BuildId: 33b5038da7b3b4d3720d97aa1dcfb185543a5654)
    #2 0x7fd4e0167240 in meep::grid_volume::str(char*, unsigned long) (/fuzz/install/lib/libmeep.so.35+0x212240) (BuildId: 71fc55fe5df6d220df7947efe4917ac005e82c0e)
    #3 0x5646074e459c in main /fuzz/testcase.cpp:22:22
    #4 0x7fd4dfa05d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #5 0x7fd4dfa05e3f in __libc_start_main csu/../csu/libc-start.c:392:3
    #6 0x564607409314 in _start (/fuzz/test+0x2c314) (BuildId: 33b5038da7b3b4d3720d97aa1dcfb185543a5654)

Address 0x7fd4ddc00397 is located in stack of thread T0 at offset 407 in frame
    #0 0x5646074e441f in main /fuzz/testcase.cpp:16

  This frame has 2 object(s):
    [32, 192) 'gv' (line 18)
    [256, 384) 'buf' (line 20) <== Memory access at offset 407 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow (/fuzz/test+0x50b51) (BuildId: 33b5038da7b3b4d3720d97aa1dcfb185543a5654) in vsnprintf
Shadow bytes around the buggy address:
  0x7fd4ddc00100: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
  0x7fd4ddc00180: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5
  0x7fd4ddc00200: f1 f1 f1 f1 00 00 00 00 00 00 00 00 00 00 00 00
  0x7fd4ddc00280: 00 00 00 00 00 00 00 00 f2 f2 f2 f2 f2 f2 f2 f2
  0x7fd4ddc00300: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x7fd4ddc00380: f3 f3[f3]f3 00 00 00 00 00 00 00 00 00 00 00 00
  0x7fd4ddc00400: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7fd4ddc00480: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7fd4ddc00500: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7fd4ddc00580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7fd4ddc00600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==1==ABORTING

Steps to Reproduce

The crash was triaged with the following Dockerfile:

Dockerfile

# Ubuntu 22.04 with some packages pre-installed
FROM hgarrereyn/stitch_repro_base@sha256:3ae94cdb7bf2660f4941dc523fe48cd2555049f6fb7d17577f5efd32a40fdd2c

RUN git clone https://github.com/NanoComp/meep /fuzz/src && \
    cd /fuzz/src && \
    git checkout 0b21c6745180abf37f2f65d37b7f301531e64af7 && \
    git submodule update --init --remote --recursive

ENV LD_LIBRARY_PATH=/fuzz/install/lib
ENV ASAN_OPTIONS=hard_rss_limit_mb=1024:detect_leaks=0

RUN echo '#!/bin/bash\nexec clang-17 -fsanitize=address -O0 "$@"' > /usr/local/bin/clang_wrapper && \
    chmod +x /usr/local/bin/clang_wrapper && \
    echo '#!/bin/bash\nexec clang++-17 -fsanitize=address -O0 "$@"' > /usr/local/bin/clang_wrapper++ && \
    chmod +x /usr/local/bin/clang_wrapper++

RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
    autoconf automake libtool pkg-config gfortran make \
    libctl-dev \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /fuzz/src

RUN CC=clang_wrapper CXX=clang_wrapper++ \
    ./autogen.sh \
      --prefix=/fuzz/install \
      --without-python \
      --without-scheme \
      --without-hdf5 \
      --with-mpi=no \
      --with-openmp=no

RUN make -j"$(nproc)" && make install

Build Command

clang++-17 -fsanitize=address -g -O0 -o /fuzz/test /fuzz/testcase.cpp -I/fuzz/install/include -L/fuzz/install/lib -lmeep -lm && /fuzz/test

Reproduce

  1. Copy Dockerfile and testcase.cpp into a local folder.
  2. Build the repro image:
docker build . -t repro --platform=linux/amd64
  1. Compile and run the testcase in the image:
docker run \
    -it --rm \
    --platform linux/amd64 \
    --mount type=bind,source="$(pwd)/testcase.cpp",target=/fuzz/testcase.cpp \
    repro \
    bash -c "clang++-17 -fsanitize=address -g -O0 -o /fuzz/test /fuzz/testcase.cpp -I/fuzz/install/include -L/fuzz/install/lib -lmeep -lm && /fuzz/test"


Additional Info

This testcase was discovered by STITCH, an autonomous fuzzing system. All reports are reviewed manually (by a human) before submission.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions