From c5673294abf8a2849963a67c24afa5f1b8eeec9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 5 Nov 2026 12:17:59 +0100 Subject: [PATCH] Firmware, PS1, PKG: Fix wrapping offsets - PSAR: an entry's chunk size could wrap the u32 position back to an earlier entry, looping forever (this runs on a disc's DATA.BIN at boot). Entries must now end inside the archive. - MDS: the session offset check wrapped in u32, and the track name block wasn't checked at all, both reading gigabytes past the file. - PKG: check the data range without the u64 sum that could wrap. Co-Authored-By: Claude Opus 5.5 --- Core/FileLoaders/PS1EbootFileLoader.cpp | 7 +++++-- Core/Util/PSARUnpack.cpp | 5 +++++ Core/Util/PkgUnpack.cpp | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Core/FileLoaders/PS1EbootFileLoader.cpp b/Core/FileLoaders/PS1EbootFileLoader.cpp index 98c768bc4b..dfa94532f1 100644 --- a/Core/FileLoaders/PS1EbootFileLoader.cpp +++ b/Core/FileLoaders/PS1EbootFileLoader.cpp @@ -428,13 +428,13 @@ bool PS1EbootFileLoader::ParseMds(const Path &mdsPath, const std::string &data) const u8 *d = (const u8 *)data.data(); const size_t size = data.size(); auto read32 = [&](size_t offset) -> u32 { - return offset + 4 <= size ? (d[offset] | (d[offset + 1] << 8) | (d[offset + 2] << 16) | ((u32)d[offset + 3] << 24)) : 0; + return offset <= size && size - offset >= 4 ? (d[offset] | (d[offset + 1] << 8) | (d[offset + 2] << 16) | ((u32)d[offset + 3] << 24)) : 0; }; if (size < 0x58 || memcmp(d, "MEDIA DESCRIPTOR", 16) != 0) { return false; } const u32 session = read32(0x50); - if (session + 0x18 > size) { + if ((size_t)session + 0x18 > size) { return false; } const u32 numBlocks = d[session + 0x0A]; @@ -466,6 +466,9 @@ bool PS1EbootFileLoader::ParseMds(const Path &mdsPath, const std::string &data) if (file < 0) { // "*.mdf" is the .mds's own name. const u32 nameBlock = read32(b + 0x34); + if ((size_t)nameBlock + 5 > size) { + return false; + } const u32 nameOffset = read32(nameBlock); std::string name; for (u32 p = nameOffset; p < size && d[p] != 0; p += d[nameBlock + 4] ? 2 : 1) { diff --git a/Core/Util/PSARUnpack.cpp b/Core/Util/PSARUnpack.cpp index 44da1af5c7..734732f3db 100644 --- a/Core/Util/PSARUnpack.cpp +++ b/Core/Util/PSARUnpack.cpp @@ -697,6 +697,11 @@ int PSARReader::NextEntry(std::string *error) { const u32 chunkSize = ReadU32(block_.data() + 0x104); const u32 expandedSize = ReadU32(block_.data() + 0x108); + // pos_ is a u32, so a huge chunk would wrap it back to an earlier entry and loop forever. + if (chunkSize > limit_ - std::min((size_t)pos_, limit_)) { + *error = StringFromFormat("Entry '%s' runs past the end of the archive (%u bytes)", entryName_.c_str(), chunkSize); + return -1; + } if (expandedSize == 0) { entryIsDirectory_ = true; diff --git a/Core/Util/PkgUnpack.cpp b/Core/Util/PkgUnpack.cpp index 9cb7a443ae..d878312906 100644 --- a/Core/Util/PkgUnpack.cpp +++ b/Core/Util/PkgUnpack.cpp @@ -187,7 +187,7 @@ bool PkgReader::Open(FileLoader *loader, std::string *error) { info_.contentId = contentId; const s64 fileSize = loader->FileSize(); - if (fileSize < 0 || (u64)fileSize < totalSize || dataOffset_ + dataSize_ > (u64)fileSize) { + if (fileSize < 0 || (u64)fileSize < totalSize || dataOffset_ > (u64)fileSize || dataSize_ > (u64)fileSize - dataOffset_) { *error = "PKG file is truncated"; return false; }