From 240b8f7aef7e255f8fff3a23584f80b2963d363f Mon Sep 17 00:00:00 2001 From: Zephyron Date: Wed, 3 Dec 2025 12:03:06 +1000 Subject: [PATCH] fix: Add safety checks to ZSTD decompression and improve HTTP client - Add maximum packet size limit (16 MB) to prevent memory exhaustion - Add empty input validation for ZSTD decompression - Improve error handling with detailed logging - Increase HTTP timeout from 30 to 60 seconds - Enable HTTP redirect following and keep-alive connections Signed-off-by: Zephyron --- src/common/zstd_compression.cpp | 30 +++++++++++++++++++++++++++++- src/web_service/web_backend.cpp | 4 +++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/common/zstd_compression.cpp b/src/common/zstd_compression.cpp index cb6ec171b..19bb37c82 100644 --- a/src/common/zstd_compression.cpp +++ b/src/common/zstd_compression.cpp @@ -1,9 +1,11 @@ // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include #include +#include "common/logging/log.h" #include "common/zstd_compression.h" namespace Common::Compression { @@ -32,17 +34,43 @@ std::vector CompressDataZSTDDefault(const u8* source, std::size_t source_siz } std::vector DecompressDataZSTD(std::span compressed) { + if (compressed.empty()) { + return {}; + } + const std::size_t decompressed_size = ZSTD_getFrameContentSize(compressed.data(), compressed.size()); + + // Define a reasonable maximum size for a decompressed network packet. + // 16 MB is a very generous limit for a single game packet. + constexpr u64 MAX_REASONABLE_PACKET_SIZE = 16 * 1024 * 1024; + + // ZSTD_getFrameContentSize can return special values if the size isn't in the header + // or if there's an error. We must check for these AND our own sanity limit. + if (decompressed_size == ZSTD_CONTENTSIZE_ERROR || + decompressed_size == ZSTD_CONTENTSIZE_UNKNOWN || + decompressed_size > MAX_REASONABLE_PACKET_SIZE) { + + LOG_ERROR(Common, "Received network packet with invalid or oversized decompressed_size: {}", decompressed_size); + return {}; // Return an empty vector to signal a graceful failure. + } + std::vector decompressed(decompressed_size); const std::size_t uncompressed_result_size = ZSTD_decompress( decompressed.data(), decompressed.size(), compressed.data(), compressed.size()); - if (decompressed_size != uncompressed_result_size || ZSTD_isError(uncompressed_result_size)) { + if (ZSTD_isError(uncompressed_result_size)) { // check the result of decompress // Decompression failed + LOG_ERROR(Common, "ZSTD_decompress failed with error: {}", ZSTD_getErrorName(uncompressed_result_size)); return {}; } + + if (decompressed_size != uncompressed_result_size) { + LOG_ERROR(Common, "ZSTD decompressed size mismatch. Expected {}, got {}", decompressed_size, uncompressed_result_size); + return {}; + } + return decompressed; } diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp index b41b42f34..74fe8ec52 100644 --- a/src/web_service/web_backend.cpp +++ b/src/web_service/web_backend.cpp @@ -27,7 +27,7 @@ namespace WebService { constexpr std::array API_VERSION{'1'}; -constexpr std::size_t TIMEOUT_SECONDS = 30; +constexpr std::size_t TIMEOUT_SECONDS = 60; struct Client::Impl { Impl(std::string host_, std::string username_, std::string token_) @@ -80,6 +80,8 @@ struct Client::Impl { // Create a new client for each request. This is the safest approach in a // multi-threaded environment as it avoids sharing a single client instance. httplib::Client cli(host.c_str()); + cli.set_follow_location(true); + cli.set_keep_alive(true); cli.set_connection_timeout(TIMEOUT_SECONDS); cli.set_read_timeout(TIMEOUT_SECONDS); cli.set_write_timeout(TIMEOUT_SECONDS);