In accordance with your security policy, I'm disclosing this issue publicly.
Summary
The return value of drflac__decode_samples__* is ignored in drflac__decode_subframe. These functions can fail before writing enough values to pSubframe->pSamplesS32. In that case, the previous values will be used as decoded sample data. If the current frame's block size is larger than those of all previous frames, uninitialized data from the heap will be used as decoded sample data.
switch (pSubframe->subframeType)
{
case DRFLAC_SUBFRAME_CONSTANT:
{
drflac__decode_samples__constant(bs, frame->header.blockSizeInPCMFrames, subframeBitsPerSample, pSubframe->pSamplesS32);
} break;
case DRFLAC_SUBFRAME_VERBATIM:
{
drflac__decode_samples__verbatim(bs, frame->header.blockSizeInPCMFrames, subframeBitsPerSample, pSubframe->pSamplesS32);
} break;
case DRFLAC_SUBFRAME_FIXED:
{
drflac__decode_samples__fixed(bs, frame->header.blockSizeInPCMFrames, subframeBitsPerSample, pSubframe->lpcOrder, pSubframe->pSamplesS32);
} break;
case DRFLAC_SUBFRAME_LPC:
{
drflac__decode_samples__lpc(bs, frame->header.blockSizeInPCMFrames, subframeBitsPerSample, pSubframe->lpcOrder, pSubframe->pSamplesS32);
} break;
default: return DRFLAC_FALSE;
}
return DRFLAC_TRUE;
Most failures are triggered by reaching the end of bs, in which case the CRC check will fail before using the poisoned data. But some failures can be triggered with invalid data in bs instead. For example, an invalid partition order (> 8) which leads to an early return in drflac__decode_samples_with_residual.
if (!drflac__read_uint8(bs, 4, &partitionOrder)) {
return DRFLAC_FALSE;
}
/*
From the FLAC spec:
The Rice partition order in a Rice-coded residual section must be less than or equal to 8.
*/
if (partitionOrder > 8) {
return DRFLAC_FALSE;
}
In that case, if there's a valid CRC immediately following the corrupt data, these poisoned values will be returned to the user as decoded sample data.
Reproduction
Attached are:
> python3 make_poc_flac.py poc.flac
wrote /poc.flac (53 bytes)
> clang -fsanitize=memory -fsanitize-memory-track-origins=2 -g -O1 msan_harness.c -o msan_harness
> ./msan_harness poc.flac
frames=16
Uninitialized bytes in __interceptor_fwrite at offset 0 inside [0x7ffffffcaa40, 64)
==11==WARNING: MemorySanitizer: use-of-uninitialized-value
#0 0x55555562c086 in main /msan_harness.c:29:9
#1 0x7fffff50a249 (/lib/x86_64-linux-gnu/libc.so.6+0x27249) (BuildId: 6196744a316dbd57c0fd8968df1680aac482cec4)
#2 0x7fffff50a304 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x27304) (BuildId: 6196744a316dbd57c0fd8968df1680aac482cec4)
#3 0x5555555752d0 in _start (/msan_harness+0x212d0) (BuildId: 2050e690069b925548a3ebc86683e24af73d3847)
Uninitialized value was stored to memory at
#0 0x555555605cab in drflac_read_pcm_frames_s32 /dr_flac.h:9915:56
Uninitialized value was created by a heap allocation
#0 0x5555555a9cc0 in __interceptor_malloc (/msan_harness+0x55cc0) (BuildId: 2050e690069b925548a3ebc86683e24af73d3847)
#1 0x55555562c4a4 in drflac__malloc_default /dr_flac.h:6353:12
SUMMARY: MemorySanitizer: use-of-uninitialized-value /msan_harness.c:29:9 in main
Exiting
Suggested Fix
Check the return value of drflac__decode_samples__* and return DRFLAC_FALSE if they fail.
switch (pSubframe->subframeType)
{
case DRFLAC_SUBFRAME_CONSTANT:
{
if (!drflac__decode_samples__constant(bs, frame->header.blockSizeInPCMFrames, subframeBitsPerSample, pSubframe->pSamplesS32)) {
return DRFLAC_FALSE;
}
} break;
case DRFLAC_SUBFRAME_VERBATIM:
{
if (!drflac__decode_samples__verbatim(bs, frame->header.blockSizeInPCMFrames, subframeBitsPerSample, pSubframe->pSamplesS32)) {
return DRFLAC_FALSE;
}
} break;
case DRFLAC_SUBFRAME_FIXED:
{
if (!drflac__decode_samples__fixed(bs, frame->header.blockSizeInPCMFrames, subframeBitsPerSample, pSubframe->lpcOrder, pSubframe->pSamplesS32)) {
return DRFLAC_FALSE;
}
} break;
case DRFLAC_SUBFRAME_LPC:
{
if (!drflac__decode_samples__lpc(bs, frame->header.blockSizeInPCMFrames, subframeBitsPerSample, pSubframe->lpcOrder, pSubframe->pSamplesS32)) {
return DRFLAC_FALSE;
}
} break;
default: return DRFLAC_FALSE;
}
return DRFLAC_TRUE;
In accordance with your security policy, I'm disclosing this issue publicly.
Summary
The return value of
drflac__decode_samples__*is ignored indrflac__decode_subframe. These functions can fail before writing enough values topSubframe->pSamplesS32. In that case, the previous values will be used as decoded sample data. If the current frame's block size is larger than those of all previous frames, uninitialized data from the heap will be used as decoded sample data.Most failures are triggered by reaching the end of
bs, in which case the CRC check will fail before using the poisoned data. But some failures can be triggered with invalid data inbsinstead. For example, an invalid partition order (> 8) which leads to an early return indrflac__decode_samples_with_residual.In that case, if there's a valid CRC immediately following the corrupt data, these poisoned values will be returned to the user as decoded sample data.
Reproduction
Attached are:
Suggested Fix
Check the return value of
drflac__decode_samples__*and returnDRFLAC_FALSEif they fail.