From 5b9961b0c6572dd15040c00ef7e0e244b5526028 Mon Sep 17 00:00:00 2001 From: Acts1631 Date: Sun, 19 Jul 2026 21:28:01 -0400 Subject: NetPlay: bound LZO decompression output NetPlay save synchronization decoded remote LZO blocks with the unsafe decoder and no output capacity. A malicious host could overflow a client buffer with a block larger than its declared size. Use the bounds-checking decoder, validate the declared output length, and grow buffer results only after each checked block has been decoded. --- Source/Core/Core/NetPlayCommon.cpp | 49 ++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/Source/Core/Core/NetPlayCommon.cpp b/Source/Core/Core/NetPlayCommon.cpp index 68500d04f4..0d1d73ecbf 100644 --- a/Source/Core/Core/NetPlayCommon.cpp +++ b/Source/Core/Core/NetPlayCommon.cpp @@ -174,7 +174,7 @@ bool CompressBufferIntoPacket(std::span in_buffer, sf::Packet& packet) bool DecompressPacketIntoFile(sf::Packet& packet, const std::string& file_path) { - u64 file_size = Common::PacketReadU64(packet); + const u64 file_size = Common::PacketReadU64(packet); if (file_size == 0) return true; @@ -188,11 +188,12 @@ bool DecompressPacketIntoFile(sf::Packet& packet, const std::string& file_path) std::vector in_buffer(LZO_OUT_LEN); std::vector out_buffer(LZO_IN_LEN); + u64 bytes_written = 0; while (true) { - u32 cur_len = 0; // number of bytes to read - lzo_uint new_len = 0; // number of bytes to write + u32 cur_len = 0; // number of bytes to read + lzo_uint new_len = out_buffer.size(); // output buffer capacity packet >> cur_len; if (!cur_len) @@ -209,21 +210,29 @@ bool DecompressPacketIntoFile(sf::Packet& packet, const std::string& file_path) packet >> in_buffer[j]; } - if (lzo1x_decompress(in_buffer.data(), cur_len, out_buffer.data(), &new_len, nullptr) != + if (lzo1x_decompress_safe(in_buffer.data(), cur_len, out_buffer.data(), &new_len, nullptr) != LZO_E_OK) { PanicAlertFmtT("Internal LZO Error - decompression failed"); return false; } + if (new_len > file_size - bytes_written) + { + PanicAlertFmtT("LZO error - output is too large"); + return false; + } + if (!file.WriteBytes(out_buffer.data(), new_len)) { PanicAlertFmtT("Error writing file: {0}", file_path); return false; } + + bytes_written += new_len; } - return true; + return bytes_written == file_size; } static bool DecompressPacketIntoFolderInternal(sf::Packet& packet, const std::string& folder_path) @@ -269,20 +278,21 @@ bool DecompressPacketIntoFolder(sf::Packet& packet, const std::string& folder_pa std::optional> DecompressPacketIntoBuffer(sf::Packet& packet) { - u64 size = Common::PacketReadU64(packet); + const u64 size = Common::PacketReadU64(packet); - std::vector out_buffer(size); + std::vector out_buffer; if (size == 0) return out_buffer; std::vector in_buffer(LZO_OUT_LEN); + std::vector decompressed_buffer(LZO_IN_LEN); - lzo_uint i = 0; + u64 decompressed_size = 0; while (true) { - u32 cur_len = 0; // number of bytes to read - lzo_uint new_len = 0; // number of bytes to write + u32 cur_len = 0; // number of bytes to read + lzo_uint new_len = decompressed_buffer.size(); // output buffer capacity packet >> cur_len; if (!cur_len) @@ -299,13 +309,28 @@ std::optional> DecompressPacketIntoBuffer(sf::Packet& packet) packet >> in_buffer[j]; } - if (lzo1x_decompress(in_buffer.data(), cur_len, &out_buffer[i], &new_len, nullptr) != LZO_E_OK) + if (lzo1x_decompress_safe(in_buffer.data(), cur_len, decompressed_buffer.data(), &new_len, + nullptr) != LZO_E_OK) { PanicAlertFmtT("Internal LZO Error - decompression failed"); return {}; } - i += new_len; + if (new_len > size - decompressed_size || new_len > out_buffer.max_size() - out_buffer.size()) + { + PanicAlertFmtT("LZO error - output is too large"); + return {}; + } + + out_buffer.insert(out_buffer.end(), decompressed_buffer.begin(), + decompressed_buffer.begin() + new_len); + decompressed_size += new_len; + } + + if (decompressed_size != size) + { + PanicAlertFmtT("LZO error - output size mismatch"); + return {}; } return out_buffer; -- cgit v1.2.3