Fix ROI UAF cluster and bump Emscripten INITIAL_MEMORY to 128 MB (#807)

ROI UAF fixes:
- LegoAnimActorStruct deep-copies m_roiMap and deletes copy-assignment, so
  LegoExtraActor's copy-construction at legoextraactor.cpp no longer shares
  ownership with the source presenter.
- LegoROI tracks slot back-references; its destructor nulls every registered
  LegoROI** so per-element UAF in arrays like LegoAnimPresenter::m_roiMap is
  impossible. LegoAnimActorStruct / LegoAnimPresenter / LegoLocomotionAnimPresenter
  register and unregister around their array lifetimes.
- LegoAnimationManager::Suspend now invokes ClearMaps on every persistent
  LegoExtraActor (not just those currently in m_extras) so dormant actors
  cannot retain stale m_AnimTreePtr after world teardown. Adds ClearMaps
  overrides on Act2Actor, Act3Cop, Act3Brickster, Act3Shark, LegoRaceCar,
  LegoExtraActor.

INITIAL_MEMORY=128mb is re-added to the Emscripten link options.
This commit is contained in:
foxtacles 2026-05-17 13:11:38 -07:00 committed by GitHub
parent 298e02b858
commit 9e82c461dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 146 additions and 6 deletions

View File

@ -14,7 +14,7 @@ endif()
if (EMSCRIPTEN) if (EMSCRIPTEN)
add_compile_options(-pthread -gsource-map) add_compile_options(-pthread -gsource-map)
add_link_options(-sUSE_WEBGL2=1 -sMIN_WEBGL_VERSION=2 -sALLOW_MEMORY_GROWTH=1 -sMAXIMUM_MEMORY=2gb -sUSE_PTHREADS=1 -sPROXY_TO_PTHREAD=1 -sOFFSCREENCANVAS_SUPPORT=1 -sPTHREAD_POOL_SIZE_STRICT=0 -sFORCE_FILESYSTEM=1 -sWASMFS=1 -sEXIT_RUNTIME=1 -sABORT_ON_WASM_EXCEPTIONS=1 -gsource-map) add_link_options(-sUSE_WEBGL2=1 -sMIN_WEBGL_VERSION=2 -sALLOW_MEMORY_GROWTH=1 -sINITIAL_MEMORY=128mb -sMAXIMUM_MEMORY=2gb -sUSE_PTHREADS=1 -sPROXY_TO_PTHREAD=1 -sOFFSCREENCANVAS_SUPPORT=1 -sPTHREAD_POOL_SIZE_STRICT=0 -sFORCE_FILESYSTEM=1 -sWASMFS=1 -sEXIT_RUNTIME=1 -sABORT_ON_WASM_EXCEPTIONS=1 -gsource-map)
set(SDL_PTHREADS ON CACHE BOOL "Enable SDL pthreads" FORCE) set(SDL_PTHREADS ON CACHE BOOL "Enable SDL pthreads" FORCE)
find_program(LLVM_OBJCOPY_BIN NAMES llvm-objcopy HINTS "${EMSCRIPTEN_ROOT_PATH}/../bin" REQUIRED) find_program(LLVM_OBJCOPY_BIN NAMES llvm-objcopy HINTS "${EMSCRIPTEN_ROOT_PATH}/../bin" REQUIRED)
set(ISLE_EMSCRIPTEN_VERSION_DIR "${CMAKE_BINARY_DIR}/generated") set(ISLE_EMSCRIPTEN_VERSION_DIR "${CMAKE_BINARY_DIR}/generated")

View File

@ -39,6 +39,7 @@ class Act2Actor : public LegoAnimActor {
} // vtable+0x68 } // vtable+0x68
void Animate(float p_time) override; // vtable+0x70 void Animate(float p_time) override; // vtable+0x70
void ClearMaps() override; // vtable+0x84
MxResult HitActor(LegoPathActor*, MxBool) override; // vtable+0x94 MxResult HitActor(LegoPathActor*, MxBool) override; // vtable+0x94
MxResult CalculateSpline() override; // vtable+0x9c MxResult CalculateSpline() override; // vtable+0x9c
MxS32 NextTargetLocation() override; // vtable+0xa0 MxS32 NextTargetLocation() override; // vtable+0xa0

View File

@ -19,6 +19,7 @@ class Act3Shark : public LegoAnimActor {
void ParseAction(char*) override; // vtable+0x20 void ParseAction(char*) override; // vtable+0x20
void Animate(float p_time) override; // vtable+0x70 void Animate(float p_time) override; // vtable+0x70
void ClearMaps() override; // vtable+0x84
// LegoAnimActor vtable // LegoAnimActor vtable
virtual MxResult EatPizza(Act3Ammo* p_ammo); // vtable+0x10 virtual MxResult EatPizza(Act3Ammo* p_ammo); // vtable+0x10
@ -104,6 +105,7 @@ class Act3Cop : public Act3Actor {
void ParseAction(char* p_extra) override; // vtable+0x20 void ParseAction(char* p_extra) override; // vtable+0x20
void Animate(float p_time) override; // vtable+0x70 void Animate(float p_time) override; // vtable+0x70
void ClearMaps() override; // vtable+0x84
MxResult HitActor(LegoPathActor*, MxBool) override; // vtable+0x94 MxResult HitActor(LegoPathActor*, MxBool) override; // vtable+0x94
MxResult CalculateSpline() override; // vtable+0x9c MxResult CalculateSpline() override; // vtable+0x9c
@ -137,6 +139,7 @@ class Act3Brickster : public Act3Actor {
void ParseAction(char* p_extra) override; // vtable+0x20 void ParseAction(char* p_extra) override; // vtable+0x20
void Animate(float p_time) override; // vtable+0x70 void Animate(float p_time) override; // vtable+0x70
void ClearMaps() override; // vtable+0x84
MxResult HitActor(LegoPathActor* p_actor, MxBool p_bool) override; // vtable+0x94 MxResult HitActor(LegoPathActor* p_actor, MxBool p_bool) override; // vtable+0x94
void SwitchBoundary( void SwitchBoundary(
LegoPathBoundary*& p_boundary, LegoPathBoundary*& p_boundary,

View File

@ -9,6 +9,8 @@ class LegoAnim;
// SIZE 0x20 // SIZE 0x20
struct LegoAnimActorStruct { struct LegoAnimActorStruct {
LegoAnimActorStruct(float p_worldSpeed, LegoAnim* p_AnimTreePtr, LegoROI** p_roiMap, MxU32 p_numROIs); LegoAnimActorStruct(float p_worldSpeed, LegoAnim* p_AnimTreePtr, LegoROI** p_roiMap, MxU32 p_numROIs);
LegoAnimActorStruct(const LegoAnimActorStruct& p_other);
LegoAnimActorStruct& operator=(const LegoAnimActorStruct&) = delete;
~LegoAnimActorStruct(); ~LegoAnimActorStruct();
float GetDuration(); float GetDuration();

View File

@ -49,6 +49,7 @@ class LegoExtraActor : public virtual LegoAnimActor {
) override; // vtable+0x6c ) override; // vtable+0x6c
void Animate(float p_time) override; // vtable+0x70 void Animate(float p_time) override; // vtable+0x70
void ApplyTransform(Matrix4& p_transform) override; // vtable+0x74 void ApplyTransform(Matrix4& p_transform) override; // vtable+0x74
void ClearMaps() override; // vtable+0x84
MxU32 StepState(float p_time, Matrix4& p_matrix) override; // vtable+0x90 MxU32 StepState(float p_time, Matrix4& p_matrix) override; // vtable+0x90
MxResult HitActor(LegoPathActor* p_actor, MxBool p_bool) override; // vtable+0x94 MxResult HitActor(LegoPathActor* p_actor, MxBool p_bool) override; // vtable+0x94
MxResult CalculateSpline() override; // vtable+0x9c MxResult CalculateSpline() override; // vtable+0x9c

View File

@ -160,6 +160,7 @@ class LegoRaceCar : public LegoCarRaceActor, public LegoRaceMap {
} // vtable+0x6c } // vtable+0x6c
void Animate(float p_time) override; // vtable+0x70 void Animate(float p_time) override; // vtable+0x70
void ClearMaps() override; // vtable+0x84
MxResult HitActor(LegoPathActor* p_actor, MxBool p_bool) override; // vtable+0x94 MxResult HitActor(LegoPathActor* p_actor, MxBool p_bool) override; // vtable+0x94
// FUNCTION: LEGO1 0x10014560 // FUNCTION: LEGO1 0x10014560

View File

@ -877,3 +877,9 @@ LegoEntity* Act2Actor::GetNextEntity(MxBool* p_isBuilding)
return result; return result;
} }
void Act2Actor::ClearMaps()
{
m_shootAnim = NULL;
LegoAnimActor::ClearMaps();
}

View File

@ -1231,3 +1231,22 @@ void Act3Shark::ParseAction(char* p_extra)
m_unk0x38->SetVisibility(FALSE); m_unk0x38->SetVisibility(FALSE);
m_world->PlaceActor(this); m_world->PlaceActor(this);
} }
void Act3Cop::ClearMaps()
{
m_eatAnim = NULL;
Act3Actor::ClearMaps();
}
void Act3Brickster::ClearMaps()
{
m_shootAnim = NULL;
Act3Actor::ClearMaps();
}
void Act3Shark::ClearMaps()
{
m_unk0x34 = NULL;
m_unk0x38 = NULL;
LegoAnimActor::ClearMaps();
}

View File

@ -437,11 +437,14 @@ void LegoAnimationManager::Suspend()
LegoROI* roi = m_extras[i].m_roi; LegoROI* roi = m_extras[i].m_roi;
if (roi != NULL) { if (roi != NULL) {
LegoPathActor* actor = CharacterManager()->GetExtraActor(roi->GetName()); LegoExtraActor* actor = CharacterManager()->GetExtraActor(roi->GetName());
if (actor != NULL && actor->GetController() != NULL) { if (actor != NULL) {
actor->GetController()->RemoveActor(actor); if (actor->GetController() != NULL) {
actor->SetController(NULL); actor->GetController()->RemoveActor(actor);
actor->SetController(NULL);
}
actor->ClearMaps();
} }
CharacterManager()->ReleaseActor(roi); CharacterManager()->ReleaseActor(roi);
@ -466,6 +469,18 @@ void LegoAnimationManager::Suspend()
m_extras[i].m_speed = -1.0f; m_extras[i].m_speed = -1.0f;
} }
// Catch dormant actors too: despawn paths null m_extras[i].m_roi
// without ClearMaps, leaving stale m_AnimTreePtr behind.
for (i = 0; i < (MxS32) CharacterManager()->GetNumActors(); i++) {
const char* name = CharacterManager()->GetActorName(i);
if (name != NULL) {
LegoExtraActor* extraActor = CharacterManager()->GetExtraActor(name);
if (extraActor != NULL) {
extraActor->ClearMaps();
}
}
}
m_unk0x18 = 0; m_unk0x18 = 0;
m_unk0x1a = FALSE; m_unk0x1a = FALSE;
m_enableCamAnims = FALSE; m_enableCamAnims = FALSE;

View File

@ -22,13 +22,39 @@ LegoAnimActorStruct::LegoAnimActorStruct(
{ {
m_worldSpeed = p_worldSpeed; m_worldSpeed = p_worldSpeed;
m_AnimTreePtr = p_AnimTreePtr; m_AnimTreePtr = p_AnimTreePtr;
m_roiMap = p_roiMap;
m_numROIs = p_numROIs; m_numROIs = p_numROIs;
// Deep copy: source array's owner may outlive or rebuild it independently.
m_roiMap = new LegoROI*[p_numROIs + 1];
memcpy(m_roiMap, p_roiMap, (p_numROIs + 1) * sizeof(LegoROI*));
for (MxU32 i = 0; i <= p_numROIs; i++) {
if (m_roiMap[i]) {
m_roiMap[i]->RegisterSlotRef(&m_roiMap[i]);
}
}
}
LegoAnimActorStruct::LegoAnimActorStruct(const LegoAnimActorStruct& p_other)
: m_worldSpeed(p_other.m_worldSpeed), m_AnimTreePtr(p_other.m_AnimTreePtr), m_numROIs(p_other.m_numROIs),
m_unk0x10(p_other.m_unk0x10)
{
m_roiMap = new LegoROI*[m_numROIs + 1];
memcpy(m_roiMap, p_other.m_roiMap, (m_numROIs + 1) * sizeof(LegoROI*));
for (MxU32 i = 0; i <= m_numROIs; i++) {
if (m_roiMap[i]) {
m_roiMap[i]->RegisterSlotRef(&m_roiMap[i]);
}
}
} }
// FUNCTION: LEGO1 0x1001c0a0 // FUNCTION: LEGO1 0x1001c0a0
LegoAnimActorStruct::~LegoAnimActorStruct() LegoAnimActorStruct::~LegoAnimActorStruct()
{ {
for (MxU32 i = 0; i <= m_numROIs; i++) {
if (m_roiMap[i]) {
m_roiMap[i]->UnregisterSlotRef(&m_roiMap[i]);
}
}
delete[] m_roiMap;
for (MxU16 i = 0; i < m_unk0x10.size(); i++) { for (MxU16 i = 0; i < m_unk0x10.size(); i++) {
delete m_unk0x10[i]; delete m_unk0x10[i];
} }

View File

@ -536,3 +536,12 @@ inline MxU32 LegoExtraActor::CheckPresenterAndActorIntersections(
return 0; return 0;
} }
void LegoExtraActor::ClearMaps()
{
delete m_assAnim;
m_assAnim = NULL;
delete m_disAnim;
m_disAnim = NULL;
LegoAnimActor::ClearMaps();
}

View File

@ -736,3 +736,10 @@ MxResult LegoJetski::HitActor(LegoPathActor* p_actor, MxBool p_bool)
return SUCCESS; return SUCCESS;
} }
void LegoRaceCar::ClearMaps()
{
m_skelKick1Anim = NULL;
m_skelKick2Anim = NULL;
LegoAnimActor::ClearMaps();
}

View File

@ -86,6 +86,11 @@ void LegoAnimPresenter::Destroy(MxBool p_fromDestructor)
} }
if (m_roiMap != NULL) { if (m_roiMap != NULL) {
for (MxU32 i = 0; i <= m_roiMapSize; i++) {
if (m_roiMap[i]) {
m_roiMap[i]->UnregisterSlotRef(&m_roiMap[i]);
}
}
delete[] m_roiMap; delete[] m_roiMap;
} }
@ -126,6 +131,11 @@ void LegoAnimPresenter::Destroy(MxBool p_fromDestructor)
} }
if (m_ptAtCamROI != NULL) { if (m_ptAtCamROI != NULL) {
for (MxS32 i = 0; i < m_ptAtCamCount; i++) {
if (m_ptAtCamROI[i]) {
m_ptAtCamROI[i]->UnregisterSlotRef(&m_ptAtCamROI[i]);
}
}
delete[] m_ptAtCamROI; delete[] m_ptAtCamROI;
} }
@ -415,12 +425,22 @@ void LegoAnimPresenter::BuildROIMap()
LegoAnimStructMap anims; LegoAnimStructMap anims;
if (m_ptAtCamROI != NULL) { if (m_ptAtCamROI != NULL) {
for (MxS32 i = 0; i < m_ptAtCamCount; i++) {
if (m_ptAtCamROI[i]) {
m_ptAtCamROI[i]->UnregisterSlotRef(&m_ptAtCamROI[i]);
}
}
memset(m_ptAtCamROI, 0, m_ptAtCamCount * sizeof(*m_ptAtCamROI)); memset(m_ptAtCamROI, 0, m_ptAtCamCount * sizeof(*m_ptAtCamROI));
} }
UpdateStructMapAndROIIndex(anims, m_anim->GetRoot(), NULL); UpdateStructMapAndROIIndex(anims, m_anim->GetRoot(), NULL);
if (m_roiMap != NULL) { if (m_roiMap != NULL) {
for (MxU32 i = 0; i <= m_roiMapSize; i++) {
if (m_roiMap[i]) {
m_roiMap[i]->UnregisterSlotRef(&m_roiMap[i]);
}
}
delete[] m_roiMap; delete[] m_roiMap;
m_roiMapSize = 0; m_roiMapSize = 0;
} }
@ -432,12 +452,14 @@ void LegoAnimPresenter::BuildROIMap()
for (LegoAnimStructMap::iterator it = anims.begin(); it != anims.end();) { for (LegoAnimStructMap::iterator it = anims.begin(); it != anims.end();) {
MxU32 index = (*it).second.m_index; MxU32 index = (*it).second.m_index;
m_roiMap[index] = (*it).second.m_roi; m_roiMap[index] = (*it).second.m_roi;
m_roiMap[index]->RegisterSlotRef(&m_roiMap[index]);
if (m_roiMap[index]->GetName() != NULL) { if (m_roiMap[index]->GetName() != NULL) {
for (MxS32 i = 0; i < m_ptAtCamCount; i++) { for (MxS32 i = 0; i < m_ptAtCamCount; i++) {
if (m_ptAtCamROI[i] == NULL && m_ptAtCamNames[i] != NULL) { if (m_ptAtCamROI[i] == NULL && m_ptAtCamNames[i] != NULL) {
if (!SDL_strcasecmp(m_ptAtCamNames[i], m_roiMap[index]->GetName())) { if (!SDL_strcasecmp(m_ptAtCamNames[i], m_roiMap[index]->GetName())) {
m_ptAtCamROI[i] = m_roiMap[index]; m_ptAtCamROI[i] = m_roiMap[index];
m_ptAtCamROI[i]->RegisterSlotRef(&m_ptAtCamROI[i]);
break; break;
} }
} }
@ -1025,6 +1047,11 @@ void LegoAnimPresenter::ParseExtra()
} }
if (m_ptAtCamROI != NULL) { if (m_ptAtCamROI != NULL) {
for (MxS32 i = 0; i < m_ptAtCamCount; i++) {
if (m_ptAtCamROI[i]) {
m_ptAtCamROI[i]->UnregisterSlotRef(&m_ptAtCamROI[i]);
}
}
delete[] m_ptAtCamROI; delete[] m_ptAtCamROI;
m_ptAtCamROI = NULL; m_ptAtCamROI = NULL;
} }
@ -1447,6 +1474,13 @@ void LegoLocomotionAnimPresenter::CreateROIAndBuildMap(LegoAnimActor* p_actor, M
if (m_roiMap != NULL) { if (m_roiMap != NULL) {
m_roiMapList->Append(m_roiMap); m_roiMapList->Append(m_roiMap);
p_actor->CreateAnimActorStruct(m_anim, p_worldSpeed, m_roiMap, m_roiMapSize); p_actor->CreateAnimActorStruct(m_anim, p_worldSpeed, m_roiMap, m_roiMapSize);
for (MxU32 i = 0; i <= m_roiMapSize; i++) {
if (m_roiMap[i]) {
m_roiMap[i]->UnregisterSlotRef(&m_roiMap[i]);
}
}
m_roiMap = NULL; m_roiMap = NULL;
} }

View File

@ -96,6 +96,9 @@ LegoROI::LegoROI(Tgl::Renderer* p_renderer, ViewLODList* p_lodList) : ViewROI(p_
// FUNCTION: BETA10 0x10189a42 // FUNCTION: BETA10 0x10189a42
LegoROI::~LegoROI() LegoROI::~LegoROI()
{ {
for (LegoROI** slot : m_slotRefs) {
*slot = NULL;
}
if (comp) { if (comp) {
CompoundObject::iterator iterator; CompoundObject::iterator iterator;

View File

@ -5,6 +5,9 @@
#include "misc/legotypes.h" #include "misc/legotypes.h"
#include "viewmanager/viewroi.h" #include "viewmanager/viewroi.h"
#include <algorithm>
#include <vector>
typedef unsigned char (*ColorOverride)(const char*, char*, unsigned int); typedef unsigned char (*ColorOverride)(const char*, char*, unsigned int);
typedef unsigned char (*TextureHandler)(const char*, unsigned char*, unsigned int); typedef unsigned char (*TextureHandler)(const char*, unsigned char*, unsigned int);
@ -99,6 +102,15 @@ class LegoROI : public ViewROI {
void SetBoundingSphere(const BoundingSphere& p_sphere) { m_sphere = m_world_bounding_sphere = p_sphere; } void SetBoundingSphere(const BoundingSphere& p_sphere) { m_sphere = m_world_bounding_sphere = p_sphere; }
void SetBoundingBox(const BoundingBox& p_box) { m_bounding_box = p_box; } void SetBoundingBox(const BoundingBox& p_box) { m_bounding_box = p_box; }
void RegisterSlotRef(LegoROI** p_slot) { m_slotRefs.push_back(p_slot); }
void UnregisterSlotRef(LegoROI** p_slot)
{
std::vector<LegoROI**>::iterator it = std::find(m_slotRefs.begin(), m_slotRefs.end(), p_slot);
if (it != m_slotRefs.end()) {
m_slotRefs.erase(it);
}
}
// SYNTHETIC: LEGO1 0x100a82b0 // SYNTHETIC: LEGO1 0x100a82b0
// SYNTHETIC: BETA10 0x1018c490 // SYNTHETIC: BETA10 0x1018c490
// LegoROI::`scalar deleting destructor' // LegoROI::`scalar deleting destructor'
@ -108,6 +120,7 @@ class LegoROI : public ViewROI {
BoundingSphere m_sphere; // 0xe8 BoundingSphere m_sphere; // 0xe8
LegoBool m_sharedLodList; // 0x100 LegoBool m_sharedLodList; // 0x100
LegoEntity* m_entity; // 0x104 LegoEntity* m_entity; // 0x104
std::vector<LegoROI**> m_slotRefs;
}; };
// VTABLE: LEGO1 0x100dbea8 // VTABLE: LEGO1 0x100dbea8