fix(content-pack): reject malicious WCP headers (oversize counts + path traversal)

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/<zoneName>/ 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.
This commit is contained in:
Kelsi 2026-05-06 05:38:53 -07:00
parent 53780de6da
commit c05d421c29

View file

@ -107,6 +107,14 @@ bool ContentPacker::unpackZone(const std::string& wcpPath, const std::string& de
uint32_t fileCount, infoSize;
in.read(reinterpret_cast<char*>(&fileCount), 4);
in.read(reinterpret_cast<char*>(&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<char*>(&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/<zoneName>/ 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<char> data(dataSize);
in.read(data.data(), dataSize);