[JSC] ScopedArgumentsTable ScopeOffset buffer allocates from fastMalloc
Component: JSC runtime | 72272dc
Source/JavaScriptCore/runtime/ScopedArgumentsTable.h
Source/JavaScriptCore/runtime/ScopedArgumentsTable.cpp
Gigacage::Primitive is a large isolated virtual memory region JSC uses to contain typed-array and ArrayBuffer backing stores, so that an out-of-bounds write in JS-controlled typed arrays cannot easily reach unrelated heap objects — the cage boundary is a key JIT exploit mitigation. ScopedArgumentsTable is internal VM bookkeeping, not JS-reachable data: it maps arguments-object slots to ScopeOffset values that are later used directly as indices into a JSLexicalEnvironment's variable storage, without additional bounds checking, because that storage is assumed trusted. This commit moves the table's buffer out of the Primitive cage onto plain fastMalloc-backed Vector<ScopeOffset> storage, and removes a stale m_watchpointSets.resize() call in the locked branch of trySetLength that mutated a supposedly-immutable locked table.
Before: After:
Gigacage::Primitive region Regular fastMalloc heap
|- Float64Array backing store ScopedArgumentsTable::m_arguments
|- ScopedArgumentsTable::m_arguments <-- reachable via cage OOB write
\- other typed array buffers
Cage OOB write -> corrupt ScopeOffset -> Cage OOB write -> typed arrays only
unchecked index into
JSLexicalEnvironment::variables()
Significance
Placing VM-internal indices inside the Primitive cage effectively gave a typed-array OOB write a path to attacker-controlled lexical-environment indexing. The move restores the cage's trust model: what is inside it should be JS-controlled data, not the metadata that indexes trusted storage.
Audit directions
Narrow: review the surrounding allocation and resize paths for regressions from the migration — Vector::tryGrow() has different growth and reallocation semantics than the old CagedUniquePtr allocation, so check whether any code implicitly relied on the caged allocator's behaviour, and verify trySetLength's growth path zero-fills only the newly added watchpoint slots. The removed m_watchpointSets.resize(newLength) in the locked branch is worth independently confirming against SymbolTable::trySetArgumentsLength rather than taking the commit message's assertion that no caller observed the old resized state. Wider and more valuable: this pattern — VM-internal metadata mistakenly placed inside a cage meant for JS-controlled data — is worth grepping for across the remaining CagedUniquePtr usages in JSC, since sibling classes could carry the same exposure. Match tell: a CagedUniquePtr<Gigacage::Primitive, T> whose T is an index, offset, or pointer type consumed without bounds checking.