DRFLAC-01 ·

Use-after-free in dr_flac's CUESHEET metadata callback

For CUESHEET blocks, dr_flac freed the raw block buffer and then called the application's metadata callback, which was still being handed a pointer into it. Every other block type frees after the callback.

Target
mackron/dr_libs (dr_flac.h)
Class
CWE-416
Severity
Low to moderate (my estimate, roughly CVSS 3.1 5.5)
Status
Fixed in dr_libs, miniaudio pending
CVE
pending
Report
mackron/dr_libs/issues/345
Fix
mackron/dr_libs 9fa8c3c

dr_flac is a single-header FLAC decoder for C and C++, part of mackron’s dr_libs. It is also bundled in miniaudio, where it appears as ma_dr_flac. Like most single-header libraries it gets copied into projects, so whatever copy a project vendored is the copy it runs.

The bug needs one thing from the application: it has to open files with one of the drflac_open_*_with_metadata() functions and read pRawData for CUESHEET blocks in its callback. Plain decoding, and metadata callbacks that don’t touch a cue sheet’s raw bytes, never see it.

One free in the wrong place

drflac__read_and_decode_metadata() reads each metadata block into a buffer, fills in a drflac_metadata struct that points at it, and hands that struct to the application’s onMeta callback. Every block type frees the buffer once the callback has returned. The CUESHEET case frees it first:

/* The original data is no longer needed. */
drflac__free_from_callbacks(pRawData, pAllocationCallbacks);
pRawData = NULL;

onMeta(pUserDataMD, &metadata);

Setting the local pRawData to NULL only clears the local. metadata.pRawData still holds the old address, so the callback gets a struct whose raw-data pointer points into freed memory.

Seeing it

A metadata callback that sums the bytes of the block is enough:

static void on_meta(void *user, drflac_metadata *m)
{
    if (m->type != DRFLAC_METADATA_BLOCK_TYPE_CUESHEET) return;

    const drflac_uint8 *p = (const drflac_uint8 *)m->pRawData;
    unsigned sum = 0;
    for (drflac_uint32 i = 0; i < m->rawDataSize; i++) sum += p[i];
    printf("checksum=%u\n", sum);
}

/* drflac_open_file_with_metadata("cue.flac", on_meta, NULL, NULL); */

With a small cue sheet the callback reads stale freed memory: the checksum comes out as 1695 instead of 276. AddressSanitizer confirmed it as a use after free.

Crashing it

Make the block bigger and the picture changes. Pad the CUESHEET block to about 300 KB and glibc returns a buffer that large to the OS when it is freed, so the callback’s read lands on unmapped memory. A normal, non-ASan build then dies with SIGSEGV.

The same file still plays fine in ffmpeg, so the problem is what the application’s callback is handed, not the file.

I didn’t attempt anything beyond the crash, and I’m not claiming anything beyond it.

How I found it

I wrote a fuzz harness for dr_flac’s metadata API. The project’s existing fuzzer opens files without a metadata callback, so onMeta is never called and the CUESHEET path after the free is never reached. A harness that supplies a callback and actually reads the block is what turned it up.

How far back

Form First affected Notes
dr_flac.h f4cccfc (2022-08-25) first released in v0.12.39 (2022-09-17); affected through v0.13.4
ma_dr_flac in miniaudio not bisected present on master (9634bed) and dev (78c8a43) as of Oct 2026; not yet patched

Before reporting I checked it was actually new. I searched dr_libs issues and PRs for “cuesheet”, “pRawData”, “use after free” / UAF and onMeta, and miniaudio issues for the same terms. Nothing matched. The closest was dr_libs #318 (July 2026), a SEEKTABLE pRawData size mismatch, which is a different bug, and that report listed the CUESHEET handler as fine. No CVE or advisory existed for it.

The fix

Move the free below the onMeta call, so the CUESHEET case frees the buffer after the callback like the other block types do:

onMeta(pUserDataMD, &metadata);

/* The original data is no longer needed. */
drflac__free_from_callbacks(pRawData, pAllocationCallbacks);
pRawData = NULL;

The real commit orders it the same way: onMeta first, then the free of pRawData, then the free of pTrackData. The maintainer’s commit also fixed a misaligned read I mentioned in the report. Thanks to mackron for the quick fix.

Timeline

Date  
2026-10-14 Found and confirmed with AddressSanitizer
2026-10-18 Reported as issue #345
2026-10-20 Fixed upstream in 9fa8c3c; issue closed

The project’s SECURITY.md asks for security reports to go in as public issues, so this did. A CVE has been requested but isn’t assigned yet. The honest status is: found, reported, fixed, CVE pending.

Verification: found and confirmed with clang 18.1.3 and AddressSanitizer on Ubuntu 24.04; the non-ASan crash was with gcc 13.3 -O2. Confirmed on dr_libs master dfe8377 (v0.13.4) and on miniaudio master 9634bed and dev 78c8a43. After the fix (9fa8c3c) both sample files return the correct checksum and run clean under ASan and UBSan.