xiph / xiph/flac

[bug] Heap-overflow (read) via `FLAC__metadata_object_vorbiscomment_set_vendor_string`

Open
#855 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug libFLAC
Dominant language
C
Stars
2.4k
Forks
363
PR merge metrics
No merged PRs in 30d

Description

Hi, there is a potential bug in utf8len_ reachable by FLAC__metadata_object_vorbiscomment_set_vendor_string.

This bug was reproduced on https://github.com/xiph/flac/commit/9547dbc2ddfca06a70ea937dbb605bbe78ea5f90.

Description

The crash occurs in utf8len_ (format.c:343) which checks for 4-byte UTF-8 sequences by reading utf8[0..3] without ensuring that at least 4 bytes remain in the buffer. When FLAC__metadata_object_vorbiscomment_set_vendor_string() validates the provided vendor string via FLAC__format_vorbiscomment_entry_value_is_legal(), a one-byte buffer beginning with 0xF0 (a 4-byte UTF-8 lead byte) triggers the path that dereferences utf8[1], utf8[2], and utf8[3], causing a heap OOB read.

Note that the size of the vendor string is provided to FLAC__format_vorbiscomment_entry_value_is_legal, but it invokes utf8len_ without providing this information.

POC

The following testcase demonstrates the bug:

testcase.cpp

#include <cstdlib>
extern "C" {
#include "/fuzz/install/include/FLAC/metadata.h"
}
int main() {
    // Create a VORBIS_COMMENT object
    FLAC__StreamMetadata *obj = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT);
    if (!obj) return 0;
    // 1-byte buffer starting with 0xF0 => 4-byte UTF-8 lead without continuation bytes
    FLAC__byte *buf = (FLAC__byte*)malloc(1);
    buf[0] = 0xF0;
    FLAC__StreamMetadata_VorbisComment_Entry entry;
    entry.entry = buf;
    entry.length = 1; // valid per API (non-zero length with non-NULL pointer)
    // Triggers OOB read in utf8len_/validation
    FLAC__metadata_object_vorbiscomment_set_vendor_string(obj, entry, /*copy=*/0);
    return 0;
}

stdout

=================================================================
==1==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x502000000011 at pc 0x557d3196af45 bp 0x7fffb5d3caa0 sp 0x7fffb5d3ca98
READ of size 1 at 0x502000000011 thread T0
    #0 0x557d3196af44 in utf8len_ format.c
    #1 0x557d3196aaec in FLAC__format_vorbiscomment_entry_value_is_legal (/fuzz/test+0x156aec) (BuildId: 73032547d555f3196deb8ae4a3007a887ac3a7cf)
    #2 0x557d31925d26 in FLAC__metadata_object_vorbiscomment_set_vendor_string (/fuzz/test+0x111d26) (BuildId: 73032547d555f3196deb8ae4a3007a887ac3a7cf)
    #3 0x557d319206ed in main /fuzz/testcase.cpp:16:5
    #4 0x7fb433393d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #5 0x7fb433393e3f in __libc_start_main csu/../csu/libc-start.c:392:3
    #6 0x557d31845374 in _start (/fuzz/test+0x31374) (BuildId: 73032547d555f3196deb8ae4a3007a887ac3a7cf)

0x502000000011 is located 0 bytes after 1-byte region [0x502000000010,0x502000000011)
allocated by thread T0 here:
    #0 0x557d318e171e in malloc (/fuzz/test+0xcd71e) (BuildId: 73032547d555f3196deb8ae4a3007a887ac3a7cf)
    #1 0x557d31920588 in main /fuzz/testcase.cpp:10:36
    #2 0x7fb433393d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16

SUMMARY: AddressSanitizer: heap-buffer-overflow format.c in utf8len_
Shadow bytes around the buggy address:
  0x501ffffffd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x501ffffffe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x501ffffffe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x501fffffff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x501fffffff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x502000000000: fa fa[01]fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x502000000080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x502000000100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x502000000180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x502000000200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x502000000280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
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

stderr


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/xiph/flac.git /fuzz/src && \
    cd /fuzz/src && \
    git checkout 9547dbc2ddfca06a70ea937dbb605bbe78ea5f90 && \
    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++

# Install build dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
    ca-certificates \
    cmake \
    ninja-build \
    make \
    pkg-config \
    build-essential \
 && rm -rf /var/lib/apt/lists/*

# Configure, build, install static libs to /fuzz/install
ENV CC=clang_wrapper CXX=clang_wrapper++
WORKDIR /fuzz/build
RUN cmake -S /fuzz/src -B . -G Ninja \
    -DCMAKE_INSTALL_PREFIX=/fuzz/install \
    -DCMAKE_BUILD_TYPE=Release \
    -DBUILD_SHARED_LIBS=OFF \
    -DBUILD_PROGRAMS=OFF \
    -DBUILD_EXAMPLES=OFF \
    -DBUILD_TESTING=OFF \
    -DBUILD_DOCS=OFF \
    -DWITH_OGG=OFF \
    -DINSTALL_MANPAGES=OFF \
    -DBUILD_CXXLIBS=ON
RUN cmake --build . -j$(nproc)
RUN cmake --install .

# Provide shim for FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION allocation-check globals
RUN printf "int alloc_check_threshold=2147483647;\nint alloc_check_counter=0;\nint alloc_check_keep_failing=0;\n" > /fuzz/build/flac_fuzz_shim.c \
 && clang_wrapper -c /fuzz/build/flac_fuzz_shim.c -o /fuzz/build/flac_fuzz_shim.o \
 && ar rcs /fuzz/install/lib/libflac_fuzz_shim.a /fuzz/build/flac_fuzz_shim.o

Build Command

clang++-17 -fsanitize=address -g -O0 -o /fuzz/test /fuzz/testcase.cpp -I/fuzz/install/include -L/fuzz/install/lib -lFLAC++ -lFLAC -lflac_fuzz_shim -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="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 -lFLAC++ -lFLAC -lflac_fuzz_shim -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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at utf8len_ in format.c:343 and trace how FLAC__format_vorbiscomment_entry_value_is_legal is called by FLAC__metadata_object_vorbiscomment_set_vendor_string. Build the provided Dockerfile and run testcase.cpp with AddressSanitizer to reproduce the one-byte 0xF0 input. Done means the testcase no longer reports a heap-buffer-overflow during validation.

Written by the indexing model from the issue text.

Assessment

Tech stack
c
Domain
security
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.