From cb7f11f2ea748606b66bed05103d6bee201fe49a Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 6 May 2026 05:46:55 -0700 Subject: [PATCH] fix(wom): cap total keyframes at 10M to prevent OOM on hostile model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per-bone-anim cap of 10K keyframes still let a malicious file allocate up to 1024 anims × 512 bones × 10K keys = 5.24B keyframes — multi-GB pre-OOM allocation. Now tracks total across the whole model and stops allocating after 10M (real models stay well under 100K). When the cap is hit we still seek past the remaining payload to keep file alignment intact for whatever follows. --- src/pipeline/wowee_model.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/pipeline/wowee_model.cpp b/src/pipeline/wowee_model.cpp index ba501178..0ffee0b1 100644 --- a/src/pipeline/wowee_model.cpp +++ b/src/pipeline/wowee_model.cpp @@ -143,6 +143,11 @@ WoweeModel WoweeModelLoader::load(const std::string& basePath) { } uint32_t animCount = 0; + // Track total keyframes loaded; cap at 10M total to prevent a + // pathological model (e.g. 1024 anims × 512 bones × 10K keys = 5B + // keyframes attempted otherwise). + size_t totalKeyframes = 0; + constexpr size_t kMaxTotalKeyframes = 10'000'000; if (f.read(reinterpret_cast(&animCount), 4) && animCount > 0 && animCount <= 1024) { model.animations.resize(animCount); for (uint32_t ai = 0; ai < animCount; ai++) { @@ -157,7 +162,14 @@ WoweeModel WoweeModelLoader::load(const std::string& basePath) { for (size_t bi = 0; bi < model.bones.size(); bi++) { uint32_t kfCount = 0; f.read(reinterpret_cast(&kfCount), 4); + if (totalKeyframes >= kMaxTotalKeyframes) { + // Skip the keyframe payload by seeking past it so we + // don't desync the remaining anims/bones. + f.seekg(static_cast(kfCount) * 44, std::ios::cur); + continue; + } for (uint32_t ki = 0; ki < kfCount && ki < 10000; ki++) { + if (totalKeyframes++ >= kMaxTotalKeyframes) break; WoweeModel::AnimKeyframe kf; f.read(reinterpret_cast(&kf.timeMs), 4); f.read(reinterpret_cast(&kf.translation), 12);