From c05d421c29cb6ac2eec16e29ddb0d182a1647cb4 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 6 May 2026 05:38:53 -0700 Subject: [PATCH] fix(content-pack): reject malicious WCP headers (oversize counts + path traversal) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three security/robustness guards on unpackZone: 1. fileCount > 1M or infoSize > 16MB rejected upfront — would OOM on the next allocation. 2. Per-file dataSize > 256MB rejected — single malicious entry could exhaust memory mid-extraction. 3. Path traversal ('..' or absolute paths) rejected — would write outside destDir// and clobber system files. WCPs are user-shareable archives, so a hostile pack downloaded from a forum should not be able to OOM the editor or write to /etc. --- tools/editor/content_pack.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tools/editor/content_pack.cpp b/tools/editor/content_pack.cpp index 9ff7a09f..17bc9c50 100644 --- a/tools/editor/content_pack.cpp +++ b/tools/editor/content_pack.cpp @@ -107,6 +107,14 @@ bool ContentPacker::unpackZone(const std::string& wcpPath, const std::string& de uint32_t fileCount, infoSize; in.read(reinterpret_cast(&fileCount), 4); in.read(reinterpret_cast(&infoSize), 4); + // Sanity bounds: a zone with more than 1M files or a 16MB info block is + // almost certainly corrupted. Reject early so we don't OOM on a malicious + // header before reading the body. + if (fileCount > 1'000'000 || infoSize > 16 * 1024 * 1024) { + LOG_ERROR("WCP header rejected (fileCount=", fileCount, + " infoSize=", infoSize, "): ", wcpPath); + return false; + } // Read the info JSON to extract the zone name. packZone stored files // relative to the zone subdirectory (e.g. "MyZone_32_32.adt"), so we @@ -132,6 +140,19 @@ bool ContentPacker::unpackZone(const std::string& wcpPath, const std::string& de uint32_t dataSize; in.read(reinterpret_cast(&dataSize), 4); + // Cap individual file size to prevent OOM from a malicious entry. + // 256MB per packed file is well above any legitimate content. + if (dataSize > 256 * 1024 * 1024) { + LOG_ERROR("WCP rejected file ", path, " size ", dataSize, " too large"); + return false; + } + // Reject path-traversal attempts. Files like "../../etc/passwd" would + // write outside destDir// and clobber system files. + if (path.find("..") != std::string::npos || + (!path.empty() && path[0] == '/')) { + LOG_ERROR("WCP rejected suspicious path: ", path); + return false; + } std::vector data(dataSize); in.read(data.data(), dataSize);