Fix ADPCM sample buffer overread in audio synthesis (#6364)

The sampleDataStartPad and aligned variables existed solely to satisfy
the N64 RSP DMA requirement that source addresses be 16-byte aligned.
On PC, aLoadBuffer is a plain memcpy with no such constraint.

The alignment dance caused aLoadBuffer to read up to 15 bytes before
sampleData and up to 8+ bytes past the end of the sample buffer. On
platforms with strict allocator guard pages (e.g. OpenBSD), this
triggers a SIGSEGV.

A second issue remains after removing the alignment dance: nFramesToDecode
is derived from sample counts (loopEnd), but size is not always a multiple
of frameSize. loopEnd and size are derived independently during encoding
and can disagree on the final partial frame, leaving nFramesToDecode *
frameSize exceeding the remaining bytes in the buffer.

Remove sampleDataStartPad and aligned entirely. Clamp the load to
min(nFramesToDecode * frameSize, audioFontSample->size - sampleDataOffset).
The ADPCM decoder operates on DMEM, so a partial last frame in DMEM
produces at most a negligible artifact at sound termination.
This commit is contained in:
Paul Schwabauer
2026-03-21 19:34:18 +01:00
committed by GitHub
parent 43f77c13fb
commit b6bf97e2f1
2 changed files with 17 additions and 22 deletions

View File

@@ -96,11 +96,11 @@ void aClearBufferImpl(uint16_t addr, int nbytes) {
void aLoadBufferImpl(const void* source_addr, uint16_t dest_addr, uint16_t nbytes) { void aLoadBufferImpl(const void* source_addr, uint16_t dest_addr, uint16_t nbytes) {
#if __SANITIZE_ADDRESS__ #if __SANITIZE_ADDRESS__
for (size_t i = 0; i < ROUND_DOWN_16(nbytes); i++) { for (size_t i = 0; i < nbytes; i++) {
BUF_U8(dest_addr)[i] = ((const unsigned char*)source_addr)[i]; BUF_U8(dest_addr)[i] = ((const unsigned char*)source_addr)[i];
} }
#else #else
memcpy(BUF_U8(dest_addr), source_addr, ROUND_DOWN_16(nbytes)); memcpy(BUF_U8(dest_addr), source_addr, nbytes);
#endif #endif
} }

View File

@@ -699,7 +699,6 @@ Acmd* AudioSynth_ProcessNote(s32 noteIndex, NoteSubEu* noteSubEu, NoteSynthesisS
u8* sampleData; u8* sampleData;
s32 nParts; s32 nParts;
s32 curPart; s32 curPart;
intptr_t sampleDataStartPad;
s32 side; s32 side;
s32 resampledTempLen; s32 resampledTempLen;
u16 noteSamplesDmemAddrBeforeResampling; u16 noteSamplesDmemAddrBeforeResampling;
@@ -713,7 +712,6 @@ Acmd* AudioSynth_ProcessNote(s32 noteIndex, NoteSubEu* noteSubEu, NoteSynthesisS
s16* filter; s16* filter;
s32 bookOffset; s32 bookOffset;
s32 finished; s32 finished;
s32 aligned;
s16 addr; s16 addr;
u16 unused; u16 unused;
@@ -896,14 +894,17 @@ Acmd* AudioSynth_ProcessNote(s32 noteIndex, NoteSubEu* noteSubEu, NoteSynthesisS
return cmd; return cmd;
} }
sampleDataStartPad = (uintptr_t)sampleData & 0xF; addr = DMEM_COMPRESSED_ADPCM_DATA - ALIGN16(nFramesToDecode * frameSize);
aligned = ALIGN16((nFramesToDecode * frameSize) + 16); u32 bytesToLoad = nFramesToDecode * frameSize;
addr = DMEM_COMPRESSED_ADPCM_DATA - aligned; u32 bytesAvail = (u32)sampleDataOffset < audioFontSample->size
? audioFontSample->size - (u32)sampleDataOffset
aLoadBuffer(cmd++, sampleData - sampleDataStartPad, addr, aligned); : 0;
if (bytesToLoad > bytesAvail) {
bytesToLoad = bytesAvail;
}
aLoadBuffer(cmd++, sampleData, addr, bytesToLoad);
} else { } else {
nSamplesToDecode = 0; nSamplesToDecode = 0;
sampleDataStartPad = 0;
} }
if (synthState->restart) { if (synthState->restart) {
@@ -920,24 +921,18 @@ Acmd* AudioSynth_ProcessNote(s32 noteIndex, NoteSubEu* noteSubEu, NoteSynthesisS
} }
switch (audioFontSample->codec) { switch (audioFontSample->codec) {
case CODEC_ADPCM: case CODEC_ADPCM:
aligned = ALIGN16((nFramesToDecode * frameSize) + 0x10); addr = DMEM_COMPRESSED_ADPCM_DATA - ALIGN16(nFramesToDecode * frameSize);
addr = DMEM_COMPRESSED_ADPCM_DATA - aligned; aSetBuffer(cmd++, 0, addr, DMEM_UNCOMPRESSED_NOTE + phi_s4, nSamplesToDecode * 2);
aSetBuffer(cmd++, 0, addr + sampleDataStartPad, DMEM_UNCOMPRESSED_NOTE + phi_s4,
nSamplesToDecode * 2);
aADPCMdec(cmd++, flags, synthState->synthesisBuffers->adpcmdecState); aADPCMdec(cmd++, flags, synthState->synthesisBuffers->adpcmdecState);
break; break;
case CODEC_SMALL_ADPCM: case CODEC_SMALL_ADPCM:
aligned = ALIGN16((nFramesToDecode * frameSize) + 0x10); addr = DMEM_COMPRESSED_ADPCM_DATA - ALIGN16(nFramesToDecode * frameSize);
addr = DMEM_COMPRESSED_ADPCM_DATA - aligned; aSetBuffer(cmd++, 0, addr, DMEM_UNCOMPRESSED_NOTE + phi_s4, nSamplesToDecode * 2);
aSetBuffer(cmd++, 0, addr + sampleDataStartPad, DMEM_UNCOMPRESSED_NOTE + phi_s4,
nSamplesToDecode * 2);
aADPCMdec(cmd++, flags | 4, synthState->synthesisBuffers->adpcmdecState); aADPCMdec(cmd++, flags | 4, synthState->synthesisBuffers->adpcmdecState);
break; break;
case CODEC_S8: case CODEC_S8:
aligned = ALIGN16((nFramesToDecode * frameSize) + 0x10); addr = DMEM_COMPRESSED_ADPCM_DATA - ALIGN16(nFramesToDecode * frameSize);
addr = DMEM_COMPRESSED_ADPCM_DATA - aligned; AudioSynth_SetBuffer(cmd++, 0, addr, DMEM_UNCOMPRESSED_NOTE + phi_s4, nSamplesToDecode * 2);
AudioSynth_SetBuffer(cmd++, 0, addr + sampleDataStartPad, DMEM_UNCOMPRESSED_NOTE + phi_s4,
nSamplesToDecode * 2);
AudioSynth_S8Dec(cmd++, flags, synthState->synthesisBuffers->adpcmdecState); AudioSynth_S8Dec(cmd++, flags, synthState->synthesisBuffers->adpcmdecState);
break; break;
} }