<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!DOCTYPE bugzilla SYSTEM "https://bugs.webkit.org/page.cgi?id=bugzilla.dtd">

<bugzilla version="5.0.4.1"
          urlbase="https://bugs.webkit.org/"
          
          maintainer="admin@webkit.org"
>

    <bug>
          <bug_id>312681</bug_id>
          
          <creation_ts>2026-04-18 08:26:33 -0700</creation_ts>
          <short_desc>Incorrect Property Read in Megamorphic Cache due to ownProperty Flag Confusion in GetByIdWithThis</short_desc>
          <delta_ts>2026-04-21 09:37:36 -0700</delta_ts>
          <reporter_accessible>1</reporter_accessible>
          <cclist_accessible>1</cclist_accessible>
          <classification_id>1</classification_id>
          <classification>Unclassified</classification>
          <product>WebKit</product>
          <component>JavaScriptCore</component>
          <version>WebKit Local Build</version>
          <rep_platform>PC</rep_platform>
          <op_sys>Linux</op_sys>
          <bug_status>RESOLVED</bug_status>
          <resolution>FIXED</resolution>
          
          
          <bug_file_loc></bug_file_loc>
          <status_whiteboard></status_whiteboard>
          <keywords>InRadar</keywords>
          <priority>P2</priority>
          <bug_severity>Minor</bug_severity>
          <target_milestone>---</target_milestone>
          
          
          <everconfirmed>1</everconfirmed>
          <reporter>parkjuny</reporter>
          <assigned_to name="Shu-yu Guo">syg</assigned_to>
          <cc>bfulgham</cc>
    
    <cc>syg</cc>
    
    <cc>webkit-bug-importer</cc>
          

      

      

      

          <comment_sort_order>oldest_to_newest</comment_sort_order>  
          <long_desc isprivate="0" >
    <commentid>2201853</commentid>
    <comment_count>0</comment_count>
      <attachid>479197</attachid>
    <who name="">parkjuny</who>
    <bug_when>2026-04-18 08:26:33 -0700</bug_when>
    <thetext>Created attachment 479197
poc.js

## Summary

In JavaScriptCore&apos;s megamorphic property cache, the `ownProperty` flag passed to `MegamorphicCache::initAsHit` is computed as `slot.slotBase() == thisValue` at JITOperations.cpp:441. For `GetByIdWithThis` (`super.x`), `thisValue` (the receiver) and `baseObject` (the lookup-start) are always distinct, so the flag is always `false` even when the property is an own property of `baseObject`. This causes the cache to store a raw pointer to `baseObject` as the holder instead of the sentinel value, so a subsequent megamorphic `GetById` on a different object with the same `StructureID` loads the property value from the stale, wrong object. The bug only manifests under JIT (the megamorphic cache fast path is JIT-only), demonstrating a clear optimized/unoptimized discrepancy.

**While I think this is not a security bug, I thought it was safer to report this as security. Feel free to demote this to a bug.**

## Bug

### Summary

`getByIdMegamorphic` (JITOperations.cpp:441) populates the global `MegamorphicCache` with `ownProperty = (slot.slotBase() == thisValue)`. For `GetByIdWithThis`, `thisValue` is the method receiver while `baseObject` is the lookup-start (`homeObject.[[GetPrototypeOf]]()`); these are never equal, so `ownProperty` is always `false`. When property `x` is an own property of `baseObject` (meaning `slot.slotBase() == baseObject`), the cache stores `holder = baseObject` (a raw pointer) instead of the sentinel `JSCell::seenMultipleCalleeObjects()`. A later DFG/FTL-compiled `GetById` on a different object with the same `StructureID` uses `loadMegamorphicProperty`, which finds `holder != sentinel` and loads from the stale `baseObject`, returning the wrong property value.

### Detail

The buggy call (JITOperations.cpp:441):

```cpp
// Source/JavaScriptCore/jit/JITOperations.cpp:439-441
if (cacheable &amp;&amp; slot.isCacheableValue() &amp;&amp; slot.cachedOffset() &lt;= MegamorphicCache::maxOffset) [[likely]] {
    if (slot.slotBase() == baseObject || !baseObject-&gt;structure()-&gt;isDictionary())
        vm.megamorphicCache()-&gt;initAsHit(baseObject-&gt;structureID(), uid, slot.slotBase(),
            slot.cachedOffset(), slot.slotBase() == thisValue);  // BUG
```

The correct version in `getByValMegamorphic` (JITOperations.cpp:3720-3722):

```cpp
        vm.megamorphicCache()-&gt;initAsHit(baseObject-&gt;structureID(), uid, slot.slotBase(),
            slot.cachedOffset(), slot.slotBase() == baseObject);  // CORRECT
```

`MegamorphicCache::initAsHit` (MegamorphicCache.h:219) delegates to `LoadEntry::initAsHit` (line 79), which stores either the sentinel or a raw holder pointer based on the flag:

```cpp
m_holder = (ownProperty) ? JSCell::seenMultipleCalleeObjects() : holder;
```

The JIT fast path `loadMegamorphicProperty` (AssemblyHelpers.cpp:548-552) uses a conditional move: if `holder == sentinel`, load from the current `baseGPR`; otherwise load from `holder`:

```cpp
loadPtr(Address(scratch3GPR, MegamorphicCache::LoadEntry::offsetOfHolder()), scratch2GPR);
auto missed = branchTestPtr(Zero, scratch2GPR);
moveConditionally64(Equal, scratch2GPR,
    TrustedImm32(std::bit_cast&lt;uintptr_t&gt;(JSCell::seenMultipleCalleeObjects())),
    baseGPR, scratch2GPR, scratch1GPR);
load16(Address(scratch3GPR, MegamorphicCache::LoadEntry::offsetOfOffset()), scratch2GPR);
loadProperty(scratch1GPR, scratch2GPR, JSValueRegs { resultGPR });
```

Both `compileGetByIdMegamorphic` and `compileGetByIdWithThisMegamorphic` in DFGSpeculativeJIT64.cpp (lines 7985–8029) use the same `loadMegamorphicProperty` call with `baseGPR`, and both write to the same global `MegamorphicCache` keyed by `{StructureID, uid}`. There is no per-access-kind separation.

**For `super.x` where `x` is an own property of `baseObject`:**
- `slot.slotBase() == baseObject` → true → correct `ownProperty` would be `true`
- Bug: `slot.slotBase() == thisValue` → always `false` for normal super access
- Cache stores `holder = baseObject` (raw pointer)

**Later, `readProp(victimObj)` via DFG `compileGetByIdMegamorphic`:**
- `victimObj.structureID == baseObject.structureID` (same shape)
- Cache hit: `holder = baseObject ≠ sentinel`
- Fast path loads from `baseObject.x` instead of `victimObj.x` → wrong value

For regular `GetById`, `thisValue == baseValue == baseObject`, so the bug is invisible. It only surfaces for `GetByIdWithThis` where receiver and lookup-start are distinct.

### Trigger Conditions

1. A `super.x` access goes megamorphic (IC sees many structures).
2. `x` is an own property of the super-base (`Object.getPrototypeOf(HomeObject)`).
3. A separate `obj.x` megamorphic access (DFG/FTL) encounters an object with the same `StructureID` as the super-base.
4. The global megamorphic cache epoch matches (same GC cycle).

## Version

### Reproduced Version

- `main` branch (2026/04/18): `a4390137a403`

## Reproduction Case

The bug is a JIT-only discrepancy: triggers under DFG/FTL (`jsc poc.js`), absent under the LLInt interpreter (`jsc --useJIT=false poc.js`).

### With JIT (release and debug)

```bash
jsc poc.js
```

```
FAIL: readProp(victimObj) = 0xcafe (expected 0xdead)
```

### Without JIT (`--useJIT=false`)

```bash
jsc --useJIT=false poc.js
```

```
PASS: readProp(victimObj) = 0xdead
```

No assertion failures fire in either build because the cache entry is structurally valid (`StructureID`, `uid`, `epoch` all match); only the holder semantics are wrong.

### PoC

```js
// Bug: JITOperations.cpp:441 — slot.slotBase() == thisValue should be == baseObject
// For GetByIdWithThis (super.x), thisValue != baseObject, so own properties of the
// lookup-start object are cached with a raw holder pointer instead of the sentinel,
// causing a later GetById on a different object of the same StructureID to read from
// the stale holder. Only triggers under JIT (megamorphic cache fast path).
//
// FAIL:  jsc poc.js
// PASS:  jsc --useJIT=false poc.js

function makeBecomeProto(val) {
    let obj = { x: val };
    Object.create(obj);  // BecomePrototype transition
    return obj;
}

let poisonObj = makeBecomeProto(0xcafe);
let victimObj = makeBecomeProto(0xdead);  // same StructureID as poisonObj

function readProp(obj) { return obj.x; }

// Drive readProp IC to megamorphic
for (let i = 0; i &lt; 200; i++) { let o = { x: i }; o[&apos;w&apos; + i] = i; Object.create(o); readProp(o); }
for (let i = 0; i &lt; 5000; i++) readProp(makeBecomeProto(i));

// Drive super accessor IC to megamorphic
class Base { readSuper() { return super.x; } }
for (let i = 0; i &lt; 200; i++) {
    let p = { x: i }; p[&apos;s&apos; + i] = i; Object.create(p);
    Object.setPrototypeOf(Base.prototype, p);
    new Base().readSuper();
}

// Poison: super.x on poisonObj writes {StructureID, &quot;x&quot;, holder=poisonObj} (wrong; should be sentinel)
gc();
Object.setPrototypeOf(Base.prototype, poisonObj);
new Base().readSuper();

// readProp(victimObj) hits the poisoned entry (same StructureID), reads from poisonObj
let result = readProp(victimObj);
print(result === 0xcafe
    ? &quot;FAIL: readProp(victimObj) = 0x&quot; + result.toString(16) + &quot; (expected 0xdead)&quot;
    : &quot;PASS: readProp(victimObj) = 0x&quot; + result.toString(16));
```

## Suggested Patch

```diff
--- a/Source/JavaScriptCore/jit/JITOperations.cpp
+++ b/Source/JavaScriptCore/jit/JITOperations.cpp
@@ -438,7 +438,7 @@ static ALWAYS_INLINE JSValue getByIdMegamorphic(JSGlobalObject* globalObject, VM
         if (hasProperty) {
             if (cacheable &amp;&amp; slot.isCacheableValue() &amp;&amp; slot.cachedOffset() &lt;= MegamorphicCache::maxOffset) [[likely]] {
                 if (slot.slotBase() == baseObject || !baseObject-&gt;structure()-&gt;isDictionary())
-                    vm.megamorphicCache()-&gt;initAsHit(baseObject-&gt;structureID(), uid, slot.slotBase(), slot.cachedOffset(), slot.slotBase() == thisValue);
+                    vm.megamorphicCache()-&gt;initAsHit(baseObject-&gt;structureID(), uid, slot.slotBase(), slot.cachedOffset(), slot.slotBase() == baseObject);
```

Matches the correct logic already used in `getByValMegamorphic` (line 3722). Confirmed fix: applying this patch causes `jsc poc.js` to output `PASS`.

### Credit Information

Reporter credit: Junyoung Park (@candymate) of KAIST Hacking Lab</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>2201854</commentid>
    <comment_count>1</comment_count>
    <who name="Radar WebKit Bug Importer">webkit-bug-importer</who>
    <bug_when>2026-04-18 08:26:40 -0700</bug_when>
    <thetext>&lt;rdar://problem/175079685&gt;</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>2202480</commentid>
    <comment_count>2</comment_count>
    <who name="Shu-yu Guo">syg</who>
    <bug_when>2026-04-20 16:54:59 -0700</bug_when>
    <thetext>This is a correctness bug, not a security bug.</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>2202482</commentid>
    <comment_count>3</comment_count>
    <who name="Shu-yu Guo">syg</who>
    <bug_when>2026-04-20 17:00:56 -0700</bug_when>
    <thetext>Pull request: https://github.com/WebKit/WebKit/pull/63178</thetext>
  </long_desc><long_desc isprivate="0" >
    <commentid>2202782</commentid>
    <comment_count>4</comment_count>
    <who name="EWS">ews-feeder</who>
    <bug_when>2026-04-21 09:37:34 -0700</bug_when>
    <thetext>Committed 311692@main (9b1a02808762): &lt;https://commits.webkit.org/311692@main&gt;

Reviewed commits have been landed. Closing PR #63178 and removing active labels.</thetext>
  </long_desc>
      
          <attachment
              isobsolete="0"
              ispatch="0"
              isprivate="0"
          >
            <attachid>479197</attachid>
            <date>2026-04-18 08:26:33 -0700</date>
            <delta_ts>2026-04-18 08:26:33 -0700</delta_ts>
            <desc>poc.js</desc>
            <filename>poc.js</filename>
            <type>text/javascript</type>
            <size>1699</size>
            <attacher>parkjuny</attacher>
            
              <data encoding="base64">Ly8gQnVnOiBKSVRPcGVyYXRpb25zLmNwcDo0NDEg4oCUIHNsb3Quc2xvdEJhc2UoKSA9PSB0aGlz
VmFsdWUgc2hvdWxkIGJlID09IGJhc2VPYmplY3QKLy8gRm9yIEdldEJ5SWRXaXRoVGhpcyAoc3Vw
ZXIueCksIHRoaXNWYWx1ZSAhPSBiYXNlT2JqZWN0LCBzbyBvd24gcHJvcGVydGllcyBvZiB0aGUK
Ly8gbG9va3VwLXN0YXJ0IG9iamVjdCBhcmUgY2FjaGVkIHdpdGggYSByYXcgaG9sZGVyIHBvaW50
ZXIgaW5zdGVhZCBvZiB0aGUgc2VudGluZWwsCi8vIGNhdXNpbmcgYSBsYXRlciBHZXRCeUlkIG9u
IGEgZGlmZmVyZW50IG9iamVjdCBvZiB0aGUgc2FtZSBTdHJ1Y3R1cmVJRCB0byByZWFkIGZyb20K
Ly8gdGhlIHN0YWxlIGhvbGRlci4gT25seSB0cmlnZ2VycyB1bmRlciBKSVQgKG1lZ2Ftb3JwaGlj
IGNhY2hlIGZhc3QgcGF0aCkuCi8vCi8vIEZBSUw6ICBqc2MgcG9jLmpzCi8vIFBBU1M6ICBqc2Mg
LS11c2VKSVQ9ZmFsc2UgcG9jLmpzCgpmdW5jdGlvbiBtYWtlQmVjb21lUHJvdG8odmFsKSB7CiAg
ICBsZXQgb2JqID0geyB4OiB2YWwgfTsKICAgIE9iamVjdC5jcmVhdGUob2JqKTsgIC8vIEJlY29t
ZVByb3RvdHlwZSB0cmFuc2l0aW9uCiAgICByZXR1cm4gb2JqOwp9CgpsZXQgcG9pc29uT2JqID0g
bWFrZUJlY29tZVByb3RvKDB4Y2FmZSk7CmxldCB2aWN0aW1PYmogPSBtYWtlQmVjb21lUHJvdG8o
MHhkZWFkKTsgIC8vIHNhbWUgU3RydWN0dXJlSUQgYXMgcG9pc29uT2JqCgpmdW5jdGlvbiByZWFk
UHJvcChvYmopIHsgcmV0dXJuIG9iai54OyB9CgovLyBEcml2ZSByZWFkUHJvcCBJQyB0byBtZWdh
bW9ycGhpYwpmb3IgKGxldCBpID0gMDsgaSA8IDIwMDsgaSsrKSB7IGxldCBvID0geyB4OiBpIH07
IG9bJ3cnICsgaV0gPSBpOyBPYmplY3QuY3JlYXRlKG8pOyByZWFkUHJvcChvKTsgfQpmb3IgKGxl
dCBpID0gMDsgaSA8IDUwMDA7IGkrKykgcmVhZFByb3AobWFrZUJlY29tZVByb3RvKGkpKTsKCi8v
IERyaXZlIHN1cGVyIGFjY2Vzc29yIElDIHRvIG1lZ2Ftb3JwaGljCmNsYXNzIEJhc2UgeyByZWFk
U3VwZXIoKSB7IHJldHVybiBzdXBlci54OyB9IH0KZm9yIChsZXQgaSA9IDA7IGkgPCAyMDA7IGkr
KykgewogICAgbGV0IHAgPSB7IHg6IGkgfTsgcFsncycgKyBpXSA9IGk7IE9iamVjdC5jcmVhdGUo
cCk7CiAgICBPYmplY3Quc2V0UHJvdG90eXBlT2YoQmFzZS5wcm90b3R5cGUsIHApOwogICAgbmV3
IEJhc2UoKS5yZWFkU3VwZXIoKTsKfQoKLy8gUG9pc29uOiBzdXBlci54IG9uIHBvaXNvbk9iaiB3
cml0ZXMge1N0cnVjdHVyZUlELCAieCIsIGhvbGRlcj1wb2lzb25PYmp9ICh3cm9uZzsgc2hvdWxk
IGJlIHNlbnRpbmVsKQpnYygpOwpPYmplY3Quc2V0UHJvdG90eXBlT2YoQmFzZS5wcm90b3R5cGUs
IHBvaXNvbk9iaik7Cm5ldyBCYXNlKCkucmVhZFN1cGVyKCk7CgovLyByZWFkUHJvcCh2aWN0aW1P
YmopIGhpdHMgdGhlIHBvaXNvbmVkIGVudHJ5IChzYW1lIFN0cnVjdHVyZUlEKSwgcmVhZHMgZnJv
bSBwb2lzb25PYmoKbGV0IHJlc3VsdCA9IHJlYWRQcm9wKHZpY3RpbU9iaik7CnByaW50KHJlc3Vs
dCA9PT0gMHhjYWZlCiAgICA/ICJGQUlMOiByZWFkUHJvcCh2aWN0aW1PYmopID0gMHgiICsgcmVz
dWx0LnRvU3RyaW5nKDE2KSArICIgKGV4cGVjdGVkIDB4ZGVhZCkiCiAgICA6ICJQQVNTOiByZWFk
UHJvcCh2aWN0aW1PYmopID0gMHgiICsgcmVzdWx0LnRvU3RyaW5nKDE2KSk7Cg==
</data>

          </attachment>
      

    </bug>

</bugzilla>