Firefox · SpiderMonkey
CVE-2026-2795
UAF in SpiderMonkey
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Files Changed
js/src/builtin/FinalizationRegistryObject.cppjs/src/builtin/FinalizationRegistryObject.hjs/src/gc/FinalizationObservers.cpp
Patch
diff --git a/js/src/builtin/FinalizationRegistryObject.cpp b/js/src/builtin/FinalizationRegistryObject.cpp
index cfe546ec5fb..480cd28284f 100644
--- a/js/src/builtin/FinalizationRegistryObject.cpp
+++ b/js/src/builtin/FinalizationRegistryObject.cpp
@@ -720,6 +720,13 @@ void FinalizationQueueObject::setHasRegistry(bool newValue) {
setReservedSlot(HasRegistrySlot, BooleanValue(newValue));
}
+void FinalizationQueueObject::clear() {
+ MOZ_ASSERT(!hasRegistry());
+ if (FinalizationRecordVector* records = recordsToBeCleanedUp()) {
+ records->clear();
+ }
+}
+
bool FinalizationQueueObject::hasRegistry() const {
return getReservedSlot(HasRegistrySlot).toBoolean();
}
@@ -825,9 +832,11 @@ bool FinalizationQueueObject::cleanupQueuedRecords(
// b. Remove cell from finalizationRegistry.[[Cells]].
// c. Perform ? Call(callback, undefined, « cell.[[HeldValue]] »).
+ FinalizationRecordVector* records = queue->recordsToBeCleanedUp();
+ MOZ_ASSERT_IF(!queue->hasRegistry(), records->empty());
+
RootedValue heldValue(cx);
RootedValue rval(cx);
- FinalizationRecordVector* records = queue->recordsToBeCleanedUp();
while (!records->empty()) {
FinalizationRecordObject* record = records->popCopy();
MOZ_ASSERT(!record->isInRecordMap());
diff --git a/js/src/builtin/FinalizationRegistryObject.h b/js/src/builtin/FinalizationRegistryObject.h
index 42c2781ca57..b19d2cb04fd 100644
--- a/js/src/builtin/FinalizationRegistryObject.h
+++ b/js/src/builtin/FinalizationRegistryObject.h
@@ -233,6 +233,7 @@ class FinalizationQueueObject : public NativeObject {
void setQueuedForCleanup(bool value);
void setHasRegistry(bool newValue);
+ void clear();
static FinalizationQueueObject* create(JSContext* cx,
HandleObject cleanupCallback);
diff --git a/js/src/gc/FinalizationObservers.cpp b/js/src/gc/FinalizationObservers.cpp
index b67f07890a9..47112af8abd 100644
--- a/js/src/gc/FinalizationObservers.cpp
+++ b/js/src/gc/FinalizationObservers.cpp
@@ -375,6 +375,11 @@ void FinalizationObservers::traceWeakFinalizationRegistryEdges(JSTracer* trc) {
if (result.isDead()) {
auto* registry = result.initialTarget();
registry->queue()->setHasRegistry(false);
+
+ // Remove any queued records. These might be dead since the registry was
+ // not marked.
+ registry->queue()->clear();
+
e.removeFront();
} else {
FinalizationRegistryObject* registry = result.finalTarget();
@@ -414,8 +419,16 @@ void FinalizationObservers::traceWeakFinalizationRegistryEdges(JSTracer* trc) {
auto* record = &iter->as<FinalizationRecordObject>();
record->setInRecordMap(false);
record->unlink();
+
+ // Move the record to the queue object. In theory this requires a read
+ // barrier since the record pointer is weak. However we have may have
+ // finished marking the record's zone at this point so this is not
+ // possible. Instead note that the record will be marked if the registry
+ // is alive. If not then we clear any queued records when we discover
+ // the the registry is dead above.
FinalizationQueueObject* queue = record->queue();
queue->queueRecordToBeCleanedUp(record);
+
if (shouldQueueFinalizationRegistryForCleanup(queue)) {
gc->queueFinalizationRegistryForCleanup(queue);
}
@@ -435,6 +448,7 @@ bool FinalizationObservers::shouldQueueFinalizationRegistryForCleanup(
//
// In this case we defer queuing the registry and this happens when the
// registry is swept.
+ MOZ_ASSERT(queue->hasRegistry());
Zone* zone = queue->zone();
return !zone->wasGCStarted() || zone->gcState() >= Zone::Sweep;
}
Loading diff…
References
On This Page