From bfbe44ad1cfd5e05a51110c4dcb4dda4bddb9506 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 11 Aug 2026 14:57:00 +0200 Subject: [PATCH] SimpleAudioDec: fix unsigned underflow and unvalidated stream-data size FindNextMp3Sync() computed `sourcebuff.size() - 2` as the loop bound; when size() is 0 or 1 this underflows to a huge size_t, turning the scan into an out-of-bounds read. Reachable via sceMp3NotifyAddStreamData followed by sceMp3Decode with as little as 1 pending byte. AuNotifyAddStreamData() trusted the game-supplied `size` outright: a negative value would make sourcebuff.resize() attempt a huge allocation (via size_t underflow), an unbounded positive value grows sourcebuff without limit, and the validated range didn't match the actual read range (checked [AuBuf, AuBuf+size) while reading from [AuBuf+offset, AuBuf+offset+size)). Validate size is positive and capped to the buffer's declared capacity, and validate the range actually read. --- Core/HW/SimpleAudioDec.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Core/HW/SimpleAudioDec.cpp b/Core/HW/SimpleAudioDec.cpp index 56d017e6c2..1fee3ceafd 100644 --- a/Core/HW/SimpleAudioDec.cpp +++ b/Core/HW/SimpleAudioDec.cpp @@ -481,6 +481,11 @@ AuCtx::~AuCtx() { } size_t AuCtx::FindNextMp3Sync() { + // sourcebuff.size() - 2 underflows to a huge size_t when size() is 0 or 1, + // turning this into an out-of-bounds scan - guard against that explicitly. + if (sourcebuff.size() < 3) { + return 0; + } for (size_t i = 0; i < sourcebuff.size() - 2; ++i) { if ((sourcebuff[i] & 0xFF) == 0xFF && (sourcebuff[i + 1] & 0xC0) == 0xC0) { return i; @@ -603,7 +608,12 @@ u32 AuCtx::AuNotifyAddStreamData(int size) { AuBufAvailable += size; } - if (Memory::IsValidRange(AuBuf, size)) { + // `size` is game-supplied and was previously trusted outright: a negative value + // would make sourcebuff.resize() attempt a huge allocation (size_t underflow), + // and an unbounded positive value would grow sourcebuff without limit (DoS). + // The validated range also has to match what's actually read below - it was + // checking [AuBuf, AuBuf+size) while the copy reads from [AuBuf+offset, ...). + if (size > 0 && size <= (int)AuBufSize && Memory::IsValidRange(AuBuf + offset, size)) { sourcebuff.resize(sourcebuff.size() + size); Memory::MemcpyUnchecked(&sourcebuff[sourcebuff.size() - size], AuBuf + offset, size); }