Medium CVSS 5.5 webkit Logic Error 🔧 Commit mapped

Overview

Medium
Severity
5.5
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionProcessing maliciously crafted web content may lead to an unexpected process crash
ComponentJSC Runtime
Bug ClassLogic Error
Tracker303357
Fix commit672cdd38781c (WebKit/WebKit) +36/-13
CWECWE-770, CWE-120 (Buffer overflow)
CVSS vectorCVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H
CISA KEVNot listed
CreditedHanQing from TSDubhe and Nan Wang (@eternalsakura13)
Disclosed2026-02-11

Background

PropertyTable
The per-structure table in JSC that records each own property’s key, storage offset, and attribute bits, and which also stores engine-internal private-name slots.
Private name
An engine-internal, non-spec-visible key used to back JavaScript class private fields (#x), managed by dedicated private-field opcodes rather than ordinary property access.
PropertyAttribute (DontDelete/ReadOnly/Accessor)
Attribute bits on a property controlling deletability (DontDelete = non-configurable), writability (ReadOnly = non-writable), and whether it is a getter/setter.
Object.seal / Object.freeze
ECMAScript operations that make an object’s own properties non-configurable (seal) and additionally non-writable for data properties (freeze); by spec they apply only to normal own properties.
LogicError crash
A non-memory-safety fault where a violated internal invariant trips an assertion/release-assert, terminating the process rather than corrupting memory.

Root Cause Analysis

The patch touches JSC’s PropertyTable, which backs the layout of an object’s properties, including engine-internal private-name slots created for JavaScript class private fields (e.g. #field). PropertyTable::seal() and PropertyTable::freeze() are invoked when user code runs Object.seal()/Object.freeze() on an object. Per the ECMAScript spec these operations apply only to normal, spec-visible own properties: seal marks them non-configurable (DontDelete) and freeze additionally marks data properties non-writable (ReadOnly).

Before the fix, both routines iterated over EVERY entry in the property table via forEachPropertyMutable and unconditionally OR’d in DontDelete (seal) or DontDelete|ReadOnly (freeze), with no check for whether the entry’s key was a private name. Private fields are stored in the same property table as ordinary properties but are not real object properties; they are engine-managed storage that the private-field access opcodes assume they fully control. When freeze stamped ReadOnly onto a private-field slot, a subsequent private-field write such as this.#field = v (the PutPrivateName / put_private_name path) hit a slot that the engine believed must always be writable, violating that internal invariant. The mismatch between the private-field machinery’s assumption (‘private slots are engine-controlled and writable’) and the attribute now present on the slot (ReadOnly/DontDelete) leads to an assertion failure / release-assert crash — the LogicError described. The read-side predicates isSealed()/isFrozen() had the mirror problem: they inspected private-name entries when deciding whether an object was sealed or frozen, so an object holding private fields could be classified inconsistently with how seal/freeze had (or should have) treated it.

The fix wraps every one of these attribute mutations and checks in if (!PropertyName(entry.key()).isPrivateName()), so private-name slots are skipped entirely: seal/freeze no longer alter their attributes, and isSealed/isFrozen no longer consider them. This restores the invariant that private-field slots are exempt from user-triggered seal/freeze semantics and remain writable/engine-managed, matching the regression test where 10000 instances of a class with a private field are sealed or frozen and then have their private field written. The concrete crashing opcode and assertion live in the private-field write path (not shown in this diff); that mechanism is an inference from the test and the attribute change, though the attribute corruption itself is exactly what the patch shows.

Key insight
Private-field slots share the PropertyTable with ordinary properties but must be exempt from user-triggered seal/freeze semantics; treating them like normal properties marks engine-managed storage ReadOnly/DontDelete and breaks the private-field access invariant that such slots are always writable.

Attack Path

  1. Define a class with a private field Author JS declares a class with an instance private field (e.g. class C { #field; setField(v){ this.#field = v; } }), which causes JSC to allocate a private-name slot in the instance’s PropertyTable.
  2. Seal or freeze an instance Call Object.seal(obj) or Object.freeze(obj) on an instance. PropertyTable::seal/freeze iterate all entries and, pre-patch, stamp DontDelete (seal) and DontDelete|ReadOnly (freeze) onto the private-field slot as well.
  3. Trigger a private-field write on the frozen/sealed slot Invoke a method that executes this.#field = v. The private-name put path operates on a slot that is now marked ReadOnly/DontDelete, contradicting its invariant that private storage is always engine-writable.
  4. Force the assertion/crash Repeat in a hot loop (the test uses 10000 iterations) so JIT tiers and the private-field write path are exercised; the invariant violation surfaces as a release-assert / unexpected termination of the WebContent process.

Impact Assessment

This is a controlled crash (denial of service) in the WebContent process, matching the advisory’s ‘unexpected process crash’ and LogicError classification. The primitive is weak from a memory-corruption standpoint: the patch shows only attribute-bit corruption on private-field slots leading to an invariant violation in the private-field write path, not an out-of-bounds or use-after-free write, so there is no evident path to memory disclosure or code execution. It is confined to the sandboxed WebContent process; no sandbox escape is implied by the diff.

Changed Functions

FunctionChangeNotes
PropertyTable::seal
Source/JavaScriptCore/runtime/PropertyTable.cpp
modified Now skips private-name entries before OR'ing in DontDelete, so private-field slots are no longer marked non-configurable by Object.seal.
PropertyTable::freeze
Source/JavaScriptCore/runtime/PropertyTable.cpp
modified Wraps the DontDelete/ReadOnly attribute stamping in a private-name guard, preventing private-field slots from being made ReadOnly and thereby preventing the crashing private-field write.
PropertyTable::isSealed
Source/JavaScriptCore/runtime/PropertyTable.cpp
modified Excludes private-name entries from the DontDelete check so an object with private fields is still correctly reported as sealed.
PropertyTable::isFrozen
Source/JavaScriptCore/runtime/PropertyTable.cpp
modified Excludes private-name entries from the DontDelete/ReadOnly checks so frozen-state reporting stays consistent with the seal/freeze changes.

Files Changed

  • JSTests/stress/private-names-seal-freeze.js
  • Source/JavaScriptCore/runtime/PropertyTable.cpp

Audit Directions

  • Other PropertyTable mutators/iterators
    Audit every use of forEachPropertyMutable / forEachProperty in PropertyTable.cpp and Structure that bulk-edits or inspects attributes; grep for setAttributes(, entry.attributes(), and confirm each has a PropertyName(entry.key()).isPrivateName() guard where private slots should be exempt.
  • preventExtensions / other integrity operations
    Check JSObject/Structure implementations of preventExtensions, defineOwnProperty, and structure transitions that iterate properties, grepping for PropertyAttribute::DontDelete/ReadOnly OR-assignments applied across all entries without a private-name filter.
  • Symbol-keyed and other special keys
    Look for places that assume isPrivateName() is the only special key class; grep for isSymbol(), isPrivateName(), and well-known-symbol handling near attribute changes to ensure other engine-internal keys are similarly excluded from freeze/seal semantics.
  • Private-field access path assumptions
    Review put_private_name / get_private_name and their DFG/FTL nodes; grep for PutPrivateName, DefinePrivateField, and any ASSERT that a private slot is writable, to find other places that could crash if a private slot ever carried ReadOnly/DontDelete.

Original Bug Report

The reporter's bug is still restricted on the tracker.