ScopedArgumentsTable moved out of Primitive Gigacage
Source/JavaScriptCore/runtime/ScopedArgumentsTable.cpp
Gigacage is JSC's spatial safety mitigation that confines specific heap allocations to a reserved virtual address region, so a corrupted pointer constrained to the cage cannot escape it. The Primitive cage is intended exclusively for user-payload buffers — TypedArray storage, ArrayBuffer contents, and similar. ScopeOffset is engine-internal metadata that maps a named argument's position in a function's argument list to its variable slot index inside a JSLexicalEnvironment.
This commit moves ScopedArgumentsTable::m_arguments from a CagedUniquePtr<ScopeOffset, Gigacage::Primitive> to a plain Vector<ScopeOffset>, removing engine-internal scope metadata from the Primitive Gigacage. It also removes a stale m_watchpointSets.resize(newLength) call that mutated a supposedly-locked (immutable) table instance in trySetLength.
Significance
This closes a documented cage-pivot path: any write primitive into the Primitive Gigacage (e.g., via a corrupted TypedArray backing store) could previously reach ScopeOffset metadata and turn it into an unchecked index into JSLexicalEnvironment::variables(), enabling out-of-bounds variable slot access.
Audit directions
- Other
CagedUniquePtr<T, Gigacage::Primitive>instances holding engine metadata rather than user payload. This commit proves the pattern existed and was missed; other instances may remain. SearchSource/JavaScriptCoreforCagedUniquePtr<.*Primitive>and classify each by whether the underlying buffer holds user-controlled bytes or engine-internal indices/pointers. - JIT/LLInt offset references. JSC JIT and LLInt emit code referencing
ScopedArgumentsTablefields by byte offset (offsetOfLength(),offsetOfArguments()); the layout has changed fromCagedUniquePtr(pointer + metadata) toVector(pointer + size + capacity). Verify every JIT codegen site using these offsets was updated in sync, particularly in 32-bit builds where alignment may differ. - Locked-table invariant violations. The stale
m_watchpointSets.resize()removal reveals that the locked-table invariant was being silently violated. Audit otherScopedArgumentsTablemethods for similar mutations onm_locked == trueinstances, and check whether thewatchpointSetsresize could have been observed through a data race betweenSymbolTable::trySetArgumentsLengthand any concurrent JIT thread reading the old table before the swap.