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.
This commit is contained in:
Acts1631
2026-07-19 21:28:01 -04:00
parent cb9df12645
commit 5b9961b0c6
+37 -12
View File
@@ -174,7 +174,7 @@ bool CompressBufferIntoPacket(std::span<const u8> 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<u8> in_buffer(LZO_OUT_LEN);
std::vector<u8> 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<std::vector<u8>> DecompressPacketIntoBuffer(sf::Packet& packet)
{
u64 size = Common::PacketReadU64(packet);
const u64 size = Common::PacketReadU64(packet);
std::vector<u8> out_buffer(size);
std::vector<u8> out_buffer;
if (size == 0)
return out_buffer;
std::vector<u8> in_buffer(LZO_OUT_LEN);
std::vector<u8> 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<std::vector<u8>> 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;