summaryrefslogtreecommitdiff
path: root/Source
diff options
context:
space:
mode:
authorActs1631 <acts1631kjv@proton.me>2026-11-01 19:39:01 -0500
committerActs1631 <acts1631kjv@proton.me>2026-11-05 17:09:40 -0500
commitf662e8585f327e03da026f58bc1e7cebb159b8cb (patch)
tree8e2b4a3356911818c5fc2a027869ecbd3ecb4337 /Source
parent96f8181230e6b4445675e7b6c5d913c189f4d7bb (diff)
Core: bound legacy savestate LZO decompression
Legacy savestate headers controlled both the compressed input length and the decompression buffer size. The input was read into a fixed-size buffer without validation, and the unsafe LZO API could write past its output buffer. Use the legacy block size as the output capacity, reject oversized compressed blocks, and use the bounds-checking LZO decompressor.
Diffstat (limited to 'Source')
-rw-r--r--Source/Core/Core/State.cpp16
1 files changed, 8 insertions, 8 deletions
diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp
index 952a23ffaa..162a8b6f11 100644
--- a/Source/Core/Core/State.cpp
+++ b/Source/Core/Core/State.cpp
@@ -511,13 +511,13 @@ static bool GetVersionFromLZO(StateHeader& header, File::IOFile& f)
{
// Just read the first block, since it will contain the full revision string
lzo_uint32 cur_len = 0; // size of compressed bytes
- lzo_uint new_len = 0; // size of uncompressed bytes
- Common::UniqueBuffer<u8> buffer(header.legacy_header.lzo_size);
+ Common::UniqueBuffer<u8> buffer(IN_LEN);
+ lzo_uint new_len = buffer.size(); // output buffer capacity
- if (!f.ReadArray(&cur_len, 1) || !f.ReadBytes(out, cur_len))
+ if (!f.ReadArray(&cur_len, 1) || cur_len > OUT_LEN || !f.ReadBytes(out, cur_len))
return false;
- const int res = lzo1x_decompress(out, cur_len, buffer.data(), &new_len, nullptr);
+ const int res = lzo1x_decompress_safe(out, cur_len, buffer.data(), &new_len, nullptr);
if (res != LZO_E_OK)
{
// This doesn't seem to happen anymore.
@@ -528,7 +528,7 @@ static bool GetVersionFromLZO(StateHeader& header, File::IOFile& f)
}
// Read in cookie and string length
- if (buffer.size() >= sizeof(StateHeaderVersion))
+ if (new_len >= sizeof(StateHeaderVersion))
{
memcpy(&header.version_header, buffer.data(), sizeof(StateHeaderVersion));
}
@@ -536,12 +536,12 @@ static bool GetVersionFromLZO(StateHeader& header, File::IOFile& f)
{
PanicAlertFmtT("Internal LZO Error - failed to parse decompressed version cookie and version "
"string length ({0})",
- buffer.size());
+ new_len);
return false;
}
// Read in the string
- if (buffer.size() >= sizeof(StateHeaderVersion) + header.version_header.version_string_length)
+ if (new_len >= sizeof(StateHeaderVersion) + header.version_header.version_string_length)
{
header.version_string.assign(
reinterpret_cast<char*>(buffer.data() + sizeof(StateHeaderVersion)),
@@ -550,7 +550,7 @@ static bool GetVersionFromLZO(StateHeader& header, File::IOFile& f)
else
{
PanicAlertFmtT("Internal LZO Error - failed to parse decompressed version string ({0} / {1})",
- header.version_header.version_string_length, buffer.size());
+ header.version_header.version_string_length, new_len);
return false;
}