A framework for building native applications using React
Fix SIGSEGV in ShadowNode::getTag() caused by use-after-free in findShadowNodeByTag_DEPRECATED (#55751)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/55751
A SIGSEGV crash is occurring in production when `ShadowNode::getTag()` is called on a destroyed ShadowNode during focus navigation (`FabricUIManagerBinding::findNextFocusableElement`).
**Root cause**: `UIManager::findShadowNodeByTag_DEPRECATED` captures a **raw pointer** to the root shadow node inside a `tryCommit` callback, then dereferences it **after the lock is released**. Another thread can commit a new tree in between, destroying the old root and leaving a dangling pointer:
```
tryCommit([&](const RootShadowNode& old) {
rootShadowNode = &old; // capture raw address
return nullptr; // cancel commit, release lock
}, {});
// !!! LOCK RELEASED — another thread can replace + destroy the old root
rootShadowNode->getChildren(); // use-after-free → SIGSEGV
```
**Fix**: Replace the `tryCommit` + raw pointer pattern with `ShadowTree::getCurrentRevision()`, which returns a `ShadowTreeRevision` by value containing a `shared_ptr<const RootShadowNode>`. The `shared_ptr` copy keeps the root node alive for the entire traversal.
**Why the old root gets destroyed**: After a commit, the old root's `shared_ptr` in `currentRevision_` is replaced. The `MountingCoordinator::push()` also overwrites `lastRevision_`. If no other holder remains (e.g. `baseRevision_` holds an earlier root), the old root's refcount drops to 0 and it is freed — while the finder thread may still hold a raw pointer to it.
Changelog: [Internal]
Reviewed By: mdvacca, javache
Differential Revision: D94376636
fbshipit-source-id: 07882beed246d61c1476f7f7790586c60a1debdb P
Peter Abbondanzo committed
7d2d3a2106be10f9a09ec78616b9981a01b5b2f9
Parent: ec29c29
Committed by meta-codesync[bot] <215208954+meta-codesync[bot]@users.noreply.github.com>
on 2/26/2026, 4:14:25 PM