Skip to content

Fix out-of-bounds reads in drflac_read_pcm_frames_* when reusing an inconsistent frame (crafted FLAC) - #330

Closed
Alb3e3 wants to merge 1 commit into
mackron:masterfrom
Alb3e3:fix-read-oob-inconsistent-frame
Closed

Fix out-of-bounds reads in drflac_read_pcm_frames_* when reusing an inconsistent frame (crafted FLAC)#330
Alb3e3 wants to merge 1 commit into
mackron:masterfrom
Alb3e3:fix-read-oob-inconsistent-frame

Conversation

@Alb3e3

@Alb3e3 Alb3e3 commented Aug 29, 2026

Copy link
Copy Markdown

Title

Fix out-of-bounds reads in drflac_read_pcm_frames_* when reusing an inconsistent frame (crafted FLAC)

Body

Summary

Two crafted-FLAC inputs cause drflac_read_pcm_frames_s32/s16/f32 to read out of bounds of the
decoded-sample buffer, crashing in release/NDEBUG builds via the ordinary public API. Any app
that opens an untrusted .flac is affected (audio players, game engines, media tooling).

Both stem from the same place: the readers reuse the current frame whenever
currentFLACFrame.pcmFramesRemaining > 0, indexing
currentFLACFrame.subframes[j].pSamplesS32[iFirstPCMFrame + i] — but a crafted stream can leave the
current frame in a state that is inconsistent with the decoded-sample buffer.

Bug 1 — NULL subframe dereference after a seek

Sequence (public API): open → read → drflac_seek_to_pcm_frame(N) (returns success) → read → SEGV.
After the seek, pcmFramesRemaining > 0 but only subframes[0] is populated and subframes[1..]
are NULL (left over from a prior partially-decoded frame). The read dereferences NULL + iFirstPCMFrame.

  • Repro: poc/min.flac (1106 bytes) + poc/repro.c. SEGV in drflac_read_pcm_frames_s32.

Bug 2 — heap-buffer-overflow on open (block size exceeds the maximum)

Reachable during drflac_open_memory_with_metadata alone (no seek). The current frame's
header.blockSizeInPCMFrames (e.g. 4096) is far larger than the STREAMINFO
maxBlockSizeInPCMFrames (e.g. 16) the decode buffer was sized for, while subframes[j] still point
at the small buffer. iFirstPCMFrame = blockSize - pcmFramesRemaining becomes huge (~2142), and the
read runs thousands of int32s past the allocation.

  • Repro: poc/min_open_overflow.flac. ASan: heap-buffer-overflow ... READ of size 16, located
    thousands of bytes after the decoded-sample region allocated in drflac_open_with_metadata_private.

Root cause

drflac_read_pcm_frames_* trusts pcmFramesRemaining/header.blockSizeInPCMFrames/subframes[j]
without checking they are mutually consistent and consistent with the decoded-sample buffer (which
only holds maxBlockSizeInPCMFrames samples per channel). A crafted stream can leave that state
inconsistent after a seek or during open.

Fix

Before reusing the current frame in each of the three readers, verify it is self-consistent:

  • header.blockSizeInPCMFrames <= maxBlockSizeInPCMFrames (buffer can hold it),
  • pcmFramesRemaining <= header.blockSizeInPCMFrames (no iFirstPCMFrame underflow),
  • every subframes[j].pSamplesS32 (j < channelCount) is non-NULL.
    If not, drop the stale frame (pcmFramesRemaining = 0) and decode a fresh one instead of
    dereferencing invalid state. (Could be factored into a small helper — happy to do that if preferred.)

Verification

  • Fixes both reproducers and every fuzzer-found crashing input (1913 inputs).
  • No regression: identical decoded frame counts before/after across all IETF FLAC decoder test
    files, and seek+read still works.
  • Found with coverage-guided libFuzzer + ASan while fuzzing the seek / metadata paths (the in-repo
    harness only exercises open_memory + read_s32, never seeks).

drflac_read_pcm_frames_s32/s16/f32 reuse the current frame whenever
pcmFramesRemaining > 0, indexing subframes[j].pSamplesS32[iFirstPCMFrame + i].
A crafted FLAC can leave that frame inconsistent with the decoded-sample
buffer (which only holds maxBlockSizeInPCMFrames samples per channel):

  * after a seek, subframes[1..] can be NULL while pcmFramesRemaining > 0
    (NULL dereference); and
  * on open, header.blockSizeInPCMFrames can exceed maxBlockSizeInPCMFrames
    (or pcmFramesRemaining can exceed the block size), so iFirstPCMFrame runs
    the read thousands of samples past the buffer (heap-buffer-overflow).

Before reusing the current frame, verify it is self-consistent (block size
within the maximum, pcmFramesRemaining within the block size, and all
channelCount subframe pointers non-NULL); otherwise drop it and decode a
fresh frame. No change for valid streams.
@Alb3e3

Alb3e3 commented Aug 29, 2026

Copy link
Copy Markdown
Author

Runnable reproducers (standalone repro.c + the two crafted FLAC files, base64) here: https://gist.github.com/Alb3e3/46ae48b8c611c7cd9c1e43f8cb79307f

clang -O2 -g -DNDEBUG -fsanitize=address -I/path/to/dr_libs repro.c -o repro
./repro min.flac                # Bug 1: SEGV after a successful drflac_seek_to_pcm_frame
./repro min_open_overflow.flac  # Bug 2: heap-buffer-overflow during open (blockSize > maxBlockSize)

Both abort under ASan on current master (v0.13.4) and return cleanly with this PR. Verified across 1913 fuzzer-found crashing inputs (all fixed) with no regression on the IETF FLAC decoder test-file set (identical decoded frame counts, seek+read still works).

mackron added a commit that referenced this pull request Aug 31, 2026
@mackron

mackron commented Aug 31, 2026

Copy link
Copy Markdown
Owner

This has been fixed manually. Please do not submit AI-generated bug reports in the future.

@mackron mackron closed this Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants