bugfix(contain): Add workaround for use-after-free bug that may crash the game#2747
bugfix(contain): Add workaround for use-after-free bug that may crash the game#2747Caball009 wants to merge 5 commits into
Conversation
|
| Filename | Overview |
|---|---|
| GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h | Simple rename of m_xferContainedByID to m_containedByID with an updated comment clarifying the field's expanded role. |
| GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp | Core of the workaround: onContainedBy and onRemovedFrom now maintain m_containedByID continuously under RETAIL_COMPATIBLE_CRC; onDestroy uses it as a sentinel to skip the unsafe removeFromContain call; the xfer save path is guarded to avoid re-deriving the ID from a potentially dangling pointer. |
Sequence Diagram
sequenceDiagram
participant Container
participant Contained as ContainedObject
participant Logic as GameLogic
Note over Container,Contained: Normal containment flow
Container->>Contained: onContainedBy(container)
Contained->>Contained: "m_containedBy = container"
Contained->>Contained: "m_containedByID = container->getID()"
Note over Container,Contained: Use-after-free scenario
Container->>Logic: onDestroy() frees m_containList
Logic->>Contained: onContainedBy(destroyedContainer)
Contained->>Contained: "m_containedBy = destroyedContainer dangling"
Contained->>Contained: "m_containedByID = INVALID_ID sentinel set"
Note over Contained: ContainedObject also destroyed
Logic->>Contained: onDestroy()
alt INVALID_ID - container was freed
Contained->>Contained: DEBUG_CRASH skip removeFromContain
else Valid ID
Contained->>Logic: findObjectByID verify in sync
Contained->>Container: "contain->removeFromContain(this)"
end
Reviews (6): Last reviewed commit: "Addressed feedback." | Re-trigger Greptile
3c40642 to
69cfc9b
Compare
| if (m_containedBy) | ||
| { | ||
| m_containedBy->getContain()->removeFromContain( this ); | ||
| if (ContainModuleInterface* contained = m_containedBy->getContain()) |
There was a problem hiding this comment.
I think this use-after-free be avoided now as well. After all, if m_containedBy is not null then we can expect m_containedBy->getContain() not null too.
There was a problem hiding this comment.
I think this use-after-free be avoided now as well.
Good point, changed.
After all, if
m_containedByis not null then we can expectm_containedBy->getContain()not null too.
That makes sense, but I'd like to stick to the original code that also has this check on m_containedBy->getContain().
da36906 to
1af8f31
Compare
|
I just did a test in multiplayer with local instances and noticed 6 places where the game would access the container object after destruction. I noticed because the game would crash if the memory is overwritten on deallocation, as is the case with The following 5 could be rewritten like so: An early return if |
Is this for a follow up or what do we do with this? |
This PR provides a retail specific workaround for use-after-free bug that could crash the game under very specific conditions (see issue and related PR).
The actual crash would happen when e.g.
OpenContain::m_containListis accessed after it's been destructed. STLPort and MSVC implementations forstd::listrely on dynamically allocated memory, so accessing the begin / end iterator after the destruction of the list is not just undefined behavior, it crashes the game. See minimal crash reproduction: https://godbolt.org/z/T9sEb4MsKTODO: