From 62b668e89813167e9db9a87e990cde65d763de41 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 6 May 2026 05:54:31 -0700 Subject: [PATCH] fix(content-pack): cap WCP per-entry path length + catch backslash/drive traversal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Path length cap (1KB) — uint16 can hold 64KB but no real zone path should exceed 256 chars. Path traversal check extended to also catch: - Windows backslash absolute paths ('\' at start) - Windows drive-prefixed paths ('C:\...') A WCP downloaded from a forum and unpacked on Windows would otherwise have these vectors open. --- tools/editor/content_pack.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tools/editor/content_pack.cpp b/tools/editor/content_pack.cpp index 17bc9c50..688fd1c6 100644 --- a/tools/editor/content_pack.cpp +++ b/tools/editor/content_pack.cpp @@ -135,6 +135,12 @@ bool ContentPacker::unpackZone(const std::string& wcpPath, const std::string& de for (uint32_t i = 0; i < fileCount; i++) { uint16_t pathLen; in.read(reinterpret_cast(&pathLen), 2); + // Cap path length — uint16 can hold up to 64KB but real zone paths + // are well under 256 chars. Anything longer is corrupt or malicious. + if (pathLen > 1024) { + LOG_ERROR("WCP rejected file ", i, " path length ", pathLen, " too large"); + return false; + } std::string path(pathLen, '\0'); in.read(path.data(), pathLen); @@ -148,8 +154,10 @@ bool ContentPacker::unpackZone(const std::string& wcpPath, const std::string& de } // Reject path-traversal attempts. Files like "../../etc/passwd" would // write outside destDir// and clobber system files. + // Also catch Windows-style backslash traversal and absolute paths. if (path.find("..") != std::string::npos || - (!path.empty() && path[0] == '/')) { + (!path.empty() && (path[0] == '/' || path[0] == '\\')) || + (path.size() >= 2 && path[1] == ':')) { // C:\... drive prefix LOG_ERROR("WCP rejected suspicious path: ", path); return false; }