From 90289ba48b87250a0fcc4eb7d94daa5c38065ef1 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 6 May 2026 05:42:50 -0700 Subject: [PATCH] fix(wob+wom): reject corrupted header counts before allocating Adds upfront sanity bounds to both WoB and WOM load: WOM: vert<=1M, index<=4M, tex<=1K WOB: groups<=4K, portals<=8K, doodads<=64K Real WoW models stay well under these limits (M2 vert is uint16 anyway). Without these checks a corrupted header could trigger a multi-GB allocation and OOM the process before we finish reading the body. Also caps name length to 1KB on WoB load (already done on WOM). --- src/pipeline/wowee_building.cpp | 9 +++++++++ src/pipeline/wowee_model.cpp | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/src/pipeline/wowee_building.cpp b/src/pipeline/wowee_building.cpp index 14e405ca..18d76595 100644 --- a/src/pipeline/wowee_building.cpp +++ b/src/pipeline/wowee_building.cpp @@ -31,9 +31,18 @@ WoweeBuilding WoweeBuildingLoader::load(const std::string& basePath) { f.read(reinterpret_cast(&portalCount), 4); f.read(reinterpret_cast(&doodadCount), 4); f.read(reinterpret_cast(&bld.boundRadius), 4); + // Sanity bounds. Real WMOs are usually 1-50 groups; >4096 indicates a + // corrupted header that would OOM us when we resize() vectors. + if (groupCount > 4096 || portalCount > 8192 || doodadCount > 65536) { + LOG_ERROR("WOB header rejected (groups=", groupCount, + " portals=", portalCount, " doodads=", doodadCount, "): ", basePath); + return WoweeBuilding{}; + } + if (!std::isfinite(bld.boundRadius) || bld.boundRadius < 0.0f) bld.boundRadius = 1.0f; uint16_t nameLen; f.read(reinterpret_cast(&nameLen), 2); + if (nameLen > 1024) nameLen = 0; bld.name.resize(nameLen); f.read(bld.name.data(), nameLen); diff --git a/src/pipeline/wowee_model.cpp b/src/pipeline/wowee_model.cpp index 79d4649d..ba501178 100644 --- a/src/pipeline/wowee_model.cpp +++ b/src/pipeline/wowee_model.cpp @@ -39,6 +39,14 @@ WoweeModel WoweeModelLoader::load(const std::string& basePath) { f.read(reinterpret_cast(&model.boundRadius), 4); f.read(reinterpret_cast(&model.boundMin), 12); f.read(reinterpret_cast(&model.boundMax), 12); + // Sanity bounds. Real M2 models cap at 65k vertices (uint16 indices) and + // typically 64 textures. Reject obviously corrupted counts before we + // try to allocate huge vertex/index buffers. + if (vertCount > 1'000'000 || indexCount > 4'000'000 || texCount > 1024) { + LOG_ERROR("WOM header rejected (verts=", vertCount, + " indices=", indexCount, " textures=", texCount, "): ", basePath); + return WoweeModel{}; + } // Bound sanity — radius drives M2 culling, min/max drive collision AABBs. // NaN/inf would either cull-out the model or crash the cull math.