SeekFile stored a signed s32 position into the unsigned size_t seekPos,
so a negative position (e.g. from a truncating s32 cast of a large
lseek offset) wrapped seekPos to near 2^64. ReadFile's clamp arithmetic
then also wrapped, driving a memcpy from a wild pointer.
- Clamp the computed seek position to 0 in SeekFile.
- Clamp the read size against the remaining data in ReadFile, returning
0 when seekPos is at or past the end.
The LZRC decompressor's only bounds check for output (and input) was a
debug-only _dbg_assert_msg_, which is a no-op in release builds. The
NPDRM demo block device also passed a hardcoded 1 MiB output length
while the real destination buffer (blockBuf_) could be as small as 2048
bytes, allowing a crafted NPDRM image to trigger an unbounded heap
overflow during game load.
Changes:
- rc_putbyte/rc_getbyte now enforce real bounds and set an error flag
instead of relying on debug asserts; decompression aborts with -1 on
overflow or truncated input.
- normalize() reads via rc_getbyte so it stays in bounds.
- Plain-text path clamps the copy size to both the output buffer and the
remaining input (and no longer interprets the size as signed).
- NPDRMDemoBlockDevice::ReadBlock passes blockSize_ (the real buffer
size) instead of 0x00100000 to lzrc_decompress.
- Add unittest/TestLzrc (synthetic input, no test data files): checks the
plain-text clamp, truncated input, and output overflow all fail safely.
- AGENTS.md: note to reuse existing format handlers/decompressors before
writing new ones.
A crafted .cso compressed ISO could trigger a heap buffer overflow on any
sector read, reachable via ordinary gameplay.
Two root causes:
1. Unvalidated frame index deltas. ReadBlock/ReadBlocks computed a
compressed read range from two adjacent frame-index-table entries.
With non-monotonic entries, compressedReadEnd - compressedReadPos
underflows as u64, producing a huge read size that fileLoader->ReadAt()
wrote into the fixed-size readBuffer.
2. hdr.align (indexShift, 0-255) used as '1 << indexShift' was undefined
behavior at >= 32, and frameSize + (1 << indexShift) could wrap,
undersizing the buffer while inflate() was configured with the full
frameSize.
Fixes:
- Validate the index table is monotonically non-decreasing in the
constructor.
- Reject files with indexShift > 20.
- Use unsigned shift for buffer size math and store readBufferSize.
- Clamp compressed read sizes to readBufferSize in both ReadBlock and
ReadBlocks.
Some preserved PSP prototype builds were distributed as DVD-R images that contain the actual UMD data inside USER_L0.IMG and, for dual-layer titles, USER_L1.IMG. These images previously required manual extraction, renaming, or concatenation before PPSSPP could load them.
Add support for recognizing these DVD-R wrapper layouts and exposing the embedded UMD image through the normal disc loading path. This includes both ISO-based wrappers and UDF-based wrappers, so preserved prototype dumps can be opened directly without conversion.
This makes PPSSPP compatible with a wider range of preserved developer disc images without relying on title-specific handling.
The primary importance of this patch is to encourage preserving 1:1, perfect disc image dumps in the manner of Redump.org.
This patch has been successfully tested on the following DVD-R ISOs:
https://hiddenpalace.org/Rock_Band_Unplugged_(Dec_10,_2008_prototype)https://hiddenpalace.org/WipEout_Pulse_(May_4,_2007_prototype)https://hiddenpalace.org/Lara_Croft_Tomb_Raider:_Anniversary_(May_19,_2007_prototype)https://hiddenpalace.org/Heatseeker_(Jan_15,_2007_prototype)Fixes#15547.
Turns out these were needed after all. For some reason, on Windows and
Mac, <algorithm> gets auto-included by something else so I don't notice
when it's missing, and MSVC's include dependency tracker doesn't see it
either.