[4] LiteralParser structure confusion via __proto__ setter re-entrancy
Severity: High | Component: JSC runtime LiteralParser | aa55894
Rated High because the diff adds a re-validation of a cached structure transition that was previously applied to write into a JSFinalObject's butterfly across a JS re-entrancy boundary, yielding a controlled wrong-offset/out-of-bounds write reachable from web content; developing that into full read/write requires heap grooming, which the diff does not establish.
LiteralParser has a fast path for caching transitions when parsing a literal with an existing transition, done before the object literal is actually parsed. During parsing, user code may run due to setters for __proto__, which may invalidate the original object's structure and thus its cached transition. The PR fixes this by taking the slow path if the structure changes.
Source/JavaScriptCore/runtime/LiteralParser.cpp
- auto* structure = object->structure();
+ auto* originalStructure = object->structure();
auto property = [&, &vm = vm] ALWAYS_INLINE_LAMBDA -> Variant<ExistingProperty, Identifier> {
- if (Structure* transition = structure->trySingleTransition()) {
+ if (Structure* transition = originalStructure->trySingleTransition()) {
...
+ // After parseRecursively, user code may have run (e.g. due to a __proto__ setter in a
+ // nested object), which may have changed the structure of the object. This invalidates
+ // any cached transition, so reset it to Identifier to take the slow path.
+ if (object->structure() != originalStructure && std::holds_alternative<ExistingProperty>(property)) [[unlikely]]
+ property = Identifier::fromUid(vm, std::get<ExistingProperty>(property).structure->transitionPropertyName());
...
auto& [newStructure, offset] = std::get<ExistingProperty>(property);
Butterfly* newButterfly = object->butterfly();
- if (structure->outOfLineCapacity() != newStructure->outOfLineCapacity()) {
- newButterfly = object->allocateMoreOutOfLineStorage(vm, structure->outOfLineCapacity(), newStructure->outOfLineCapacity());
- object->nukeStructureAndSetButterfly(vm, structure->id(), newButterfly);
+ if (originalStructure->outOfLineCapacity() != newStructure->outOfLineCapacity()) {
+ newButterfly = object->allocateMoreOutOfLineStorage(vm, originalStructure->outOfLineCapacity(), newStructure->outOfLineCapacity());
+ object->nukeStructureAndSetButterfly(vm, originalStructure->id(), newButterfly);
}
validateOffset(offset);
JSTests/stress/literal-parser-proto-setter.js
+let fired = false;
+Object.prototype.__defineSetter__("__proto__", function(v) {
+ if (fired) return;
+ fired = true;
+ Object.prototype.__defineSetter__(0, function(){});
+});
+let ks = '"0":null,"1":2,"5":3';
+eval("({"+ks+",a:1})");
+eval("({"+ks+",a:1})");
+let o = eval("({"+ks+",a:{__proto__:0}})");
+if (o[1] !== 2)
+ throw new Error("incorrect eval result");
Patch Details
The fast path caches a Structure transition (ExistingProperty { newStructure, offset }) computed from the object's structure before the property value is parsed. The patch renames the captured local to originalStructure and adds a post-parseRecursively guard: after the nested value is parsed (which may have run arbitrary JS), it checks object->structure() != originalStructure; if so and the cached result is still an ExistingProperty, it discards the transition by resetting property to an Identifier, forcing the slow lookup. The butterfly-resize/nukeStructureAndSetButterfly block now compares and allocates against originalStructure.
Failure to re-validate a cached object-structure transition across a JavaScript re-entrancy boundary (a proto setter) before applying it to mutate the object's butterfly.
Background
A Structure is JSC's hidden-class object describing an object's property layout; adding a property follows or creates a transition to a new Structure. trySingleTransition() returns the single cached property-addition transition, letting the parser skip a hash lookup. The Butterfly is out-of-line storage holding named-property and indexed values; a property's offset indexes into it. nukeStructureAndSetButterfly atomically swaps an object's structure id and butterfly pointer during a resize. The LiteralParser fast path is used for JSON.parse and for evaluating object/array literals: it builds a JSFinalObject and, for each property, either follows a cached transition (writing the value straight into the butterfly at the transition offset) or falls back to a generic put. Setting __proto__ in an object literal invokes a user-defined __proto__ setter if one is installed on Object.prototype — so native parsing code synchronously calls into JavaScript that can mutate engine state before returning.
Analysis
This is a structure/type confusion (a TOCTOU across a JS re-entrancy boundary) leading to an out-of-bounds or wrong-slot butterfly write. Before the fix, parseRecursively snapshotted the in-progress object's Structure and used it to cache a single property-addition transition, then parsed the property value via a recursive call. That recursive parse can execute arbitrary JavaScript when a nested literal contains __proto__ and a setter is installed on Object.prototype. The setter can mutate the in-progress object's shape, so object->structure() no longer equals the snapshotted structure — yet the cached newStructure/offset are still relative to the old structure.
Aaa Aaaaaaa Aaaa Aaaaaaa Aaaa Aaaaa Aaaaaaaaaa Aa Aaa Aaaaaaaaaaaaaaaaaaaaaa Aaaaaaa Aa Aaaaaaa Aaa Aaaaaaaaa Aaaaa Aaa Aaa Aaaaaaaaaaaaaaaaaaaaaa Aaa Aaa Aaaaaaaaa Aa Aa Aaa Aaaaa Aaaaaa Aaa Aaaaa Aaa Aaaaaa Aaaaa Aa Aaa Aaaaa Aaaaaaaaa Aaaaaaa Aaaaaaaa Aaa Aaa Aaaaaaaa Aaaaa Aaaa Aaaaaaaa Aaa a Aaaaaaaaa Aaaa Aa Aaaaaa Aaaaaaaaa Aaa Aaaaaaa Aaa Aaaaa Aaaaa Aa Aa Aaaaaa Aaaa Aaaa Aaa Aaaaaaaaaa Aa Aaa Aaaaaaaa Aaaa Aaaaaaa Aaa Aaaaaaaaaa Aaaa Aaaaaaaaaaaa Aaa Aaaaaaaa Aaa Aaaaaaaaaaa Aaaaaaa Aa Aaaaa Aaaaa Aaaaaaaa a Aaaaaa Aaa Aaaaa Aaaa Aaaaa Aaaaaa Aaaaaaaaaa Aaaaaaaa Aaaaaaaaaa Aaaaaaa Aaaaaa Aaaaaaaaaaaaaaaaaaaaaa Aaaaaa Aaaaa Aaa Aaaaa Aaaaaaaa Aaaaaaaaa Aaaa a Aaaaaa Aaaaaa Aaaaaaaaaa Aaa Aaaaaaaa Aaaa Aaa Aaa Aaaaa Aaaaaa Aaaa Aaaaaaaaaaaaaaaaa Aaaaa Aaa Aaaaaa Aaaaaaaaaa Aaa Aaaaaaa Aaaaaaaaa Aa Aaaaa Aaaaaaaaaa Aaa Aaaaaaaaa Aaaaaa Aaaaaaaa Aaaaaaaaaaaaaaaaaaa Aa Aaaaaaaa Aaaa Aaa Aaaaa Aaaaaa Aaa Aaaaaaaa Aaaaaa
Aa Aa Aaaaaaaa Aaaaaa Aaa Aaaaaaaa Aaaaaaaaaa Aaa Aa Aaa Aaaaa Aaaaaa Aaaaa Aaaaaaa Aaa Aaaaaaaa Aaaa Aaaaaaaaaaa Aaaaaaaa Aaaa Aaaaa Aaaaa a Aaaaaaaaaa Aaaaaaaaaaaa a Aaaaaaaaaaaaa Aaaaa Aaaa a Aaaaaaaaaaaaaaa Aaaaaaaaaa Aaaa a Aaaaaaaaa Aa Aaa Aa a Aaaaa Aaaa Aaaa Aaa Aaaaa Aaa Aaaaaaaa Aaaa Aaaaaa a a Aaaaaaaaaaaaaaaaaaa Aaaaaaaaa Aaaa Aaaaa Aa Aaaaaaaaa Aaaaaa Aaaaaaaa Aaaaaaaaaa Aa Aaaaaaaa Aaaa Aaaaa Aaaaaaaaa a Aaaaaa Aaaaaaaaa Aaaaa Aaaaaaa Aaaa Aaaaaaaa Aa Aaaaa a Aaaaaa Aaaaaa Aaaaaaaa Aa Aaa Aaaaaaaaa Aaaaaaaaaa Aaa Aaaaaaaaaaaa Aaaaaa Aaaaaaaaaa Aaaa Aaaaaa Aaa Aaaaaaaaaa Aaaaaaaaa
Aaaa Aaaaaaaaaaaaa Aaaaaaa Aaaaaaaaaaa Aaaaaa Aaaaaa Aaa Aaaaaaaaa Aaa Aaaaaaaa Aaaaa Aaaaaaa Aaa Aaaaaaaaaaa a Aaaaaaaaaaaaaaa Aaaa Aa Aaaaaaa a Aaaaaaaa Aaaaaa Aaaaa Aaaaaaaaa Aaa Aaaaaa Aaaa Aaa Aaaaa Aa Aaaaaaa Aaaa Aaa Aaaaaaaaaa Aaaaaa Aaa Aaa Aaaa Aaaaaaaaa Aaa Aaaaaaaa Aaaaaaaa a Aaaaaaaaaaaaaa Aaaaaaaaaaa Aaaaaa Aaaaaaaaaa Aa Aaaaaaaaaa Aaaaaaaaaaa Aa Aaaaaaaa Aaaaaaaaa Aaaaaa Aaaaaaa Aaaaaaa a Aa Aa Aaa Aaa Aaaaaaa Aaaaa Aaaa Aaa Aaaaaaaaaaaaa Aaaaaa a Aaaa Aaaaaa Aaaaaa Aaaaaaaaaaaa a Aa Aaa Aaaa Aaaa Aaaa Aaaaaa Aaaaaa Aaaaa Aaaaaa Aaaaaaa a Aaaaaaaaaaaaaaaaaaa Aaaaa Aaaa Aaaaaa Aaaaaaaaa Aaaaaa Aaaaaaaa Aaaaaaaaa
Aaaaaaaaa Aaa Aaaaa Aaaaaaaaaaaaaaaaaaaa Aaaaaaaaa Aaa Aaa Aaaaaaaaa Aaaaaaaaaaaa Aaa Aaaaaaaa Aaaa Aaa Aaaaa Aaaaa Aaa Aaaa Aaaaaa Aaaa Aaaaaaaa Aaaaaaaa Aaa Aaaa Aaaaaa Aaa Aaa Aaa Aaaaaaaa Aaaaaaaaaaaaaaaaaaaaa Aaa Aaaa Aa Aaa Aaaa Aaaa Aa Aaaaaaaaa Aaa Aaaaa Aaaaaaaaa Aa Aaa Aaaaa
🔒A cached object-shape decision is applied across a JavaScript re-entrancy boundary — the ownership and corruption implications of the stale layout are explored in depth.
Subscribe to read more
Audit directions
a Aaaaaaaaaaa Aaaaaaaaa Aaaaaaa Aaaaa a Aa Aaaaaaaaaaa Aaaaaaaaaaa Aaaaaa Aaaa Aaaa Aaaaaaaaa Aa Aaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaa Aaaaaaa a Aa Aaaaaaaaaaa Aaaaaaaaa Aaaa Aaaaaaa Aaa Aaaaaaaa Aa Aaaaa Aaaa Aaa Aaaaaaaaaa Aaaaa Aaaaa Aaaaaaaaaaaaaaaaaa Aaaa Aaaaa Aaa Aaaaaaaaaaaaaaaaaaaaaa Aaaaaaa Aaa Aaaaaa Aaa Aaaaaaaaaaaa Aaaaa a Aaaaaaaaa Aaaaa Aaaaaa Aaaaa Aa Aaaaaaaaaaaaaaaaaaa Aaaaaa Aaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaa Aaaa Aaaa Aaaaaaa a Aaaaa Aaaaa
a Aaaaaaaaaaaaa Aa a Aaaaaaaaaaa Aaaaaaa Aaaaaa Aaaaaaaaaaaaaaa Aaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaa Aa Aaaaaaaaaaaaaa Aaa Aaaa Aaaaaaaaaaaa Aaaaa Aaa Aaaaaa Aaaa Aaaaaa a Aaaaaaaaaaa Aaaaaaaaaaa Aa a Aaaaa Aaaaa Aaaaa Aaaaaaaaaa Aaaaaaaaaa Aaa Aaaaaaaaa Aaa Aaaa Aaaaaaaa Aaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaa Aaa Aaa Aaaaaaaa Aaaaaaaaaaaaaa Aaaaaaa Aaaaaaaaaaaaaaaa a Aaaaaaaaa Aaaa Aaaaaa Aaa Aaa Aaaa Aaaaaaa
a Aaaaaaaaaaaaa Aaaaaaa Aaaaaaa Aa Aaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaa Aaaaa Aaaa Aaaaa Aaaa Aaaaa a Aaaaaaaaaaaaa Aaaaaaaaaaaaaaaa Aaaa Aaa Aaaaa Aaaaa Aa Aaaa Aaaaaaa Aaaaaa Aaa Aaaaaaa Aaaaaaaaa Aaaaa Aaaaaaa Aaaaaa Aaa Aaaaaa Aaaaaa Aaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaaa Aaaaaaaaa Aaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaa Aaaaa Aaaaaa a Aaaaaaaaaaaaaaaa Aaaaaaaaaa
🔒Multiple reusable audit patterns identified for re-entrancy-induced structure staleness, with concrete LiteralParser and runtime starting points for variant discovery.
Subscribe to read more