numFrames and numBlocks are derived from the same attacker-controlled
64-bit total_bytes field, but independently truncated to 32 bits using
different divisors (frameSize vs. the fixed 2048-byte block size).
With extreme total_bytes/block_size combinations the two truncations
can disagree so that numBlocks (which gates ReadBlock's bounds check)
describes more blocks than numFrames actually covers - ReadBlock then
indexes the numFrames+1-sized `index` array one or more elements past
its end. Reject any header where this could happen before allocating
anything.
The block table read from an NPDRM PBP's PSAR blob is only reversibly
XOR-scrambled, not otherwise validated, so a crafted file fully
controls table_[block].size/offset and the header's LBA/block-size
fields. Several of these were used without checks:
- table_[block].size could exceed blockSize_, causing ReadAt and the
KIRK cipher update to write past the end of blockBuf_/tempBuf_ (both
allocated as exactly blockSize_ bytes) - a heap buffer overflow.
- The block index derived from blockNumber (which can come from an
attacker-influenced /sce_lbn.../_size... raw sector open) was never
bounds-checked against numBlocks_ before indexing table_[].
- blockLBAs_ could be 0, dividing by zero both when computing
numBlocks_ and when computing the block index in ReadBlock.
- lbaSize_ could underflow if lbaEnd < lbaStart, and tableSize_
(numBlocks_ * sizeof(table_info)) was computed in 32-bit, so a large
numBlocks_ could wrap it to a small value - passing the "did we read
the whole table" check while only actually reading (and
descrambling) a small prefix, leaving the rest of the table_ array
as untouched, uninitialized heap memory that ReadBlock() would later
trust.
Reject all of these instead.
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.
* Rename LogType to Log
* Explicitly use the Log:: enum when logging. Allows for autocomplete when editing.
* Mac/ARM64 buildfix
* Do the same with the hle result log macros
* Rename the log names to mixed case while at it.
* iOS buildfix
* Qt buildfix attempt, ARM32 buildfix
* Move and rename file_util/fd_util to Common/File/FileUtil and DirListing
Let's also move net while we're at it.
Move the ZIM/PNG loaders over to Common.
Move the UI framework into Common
iOS buildfix
* Buildfix
* Buildfixes
* Apple buildfix
* This typo again..
* UWP buildfix
* Fix build of PPSSPPQt, such as it is (it's not in good condition...)
* Guess what? Another buildfix.
Also move colorutil.cpp/h
linking build fix experiment
Delete a bunch of unused CMakeLists.txt files
CMakeLists.txt linking fix
Don't include NativeApp.h from any headers.
Android.mk buildfix
Half of the UWP fix
Buildfix
Minor project file cleanup
Buildfixes
Guess what? More buildfixes!