Critical CVSS 8.8 webkit OOB 🔧 Commit mapped

Overview

Critical
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionProcessing web content may lead to arbitrary code execution
ComponentJSC Inspector
Bug ClassOOB
Tracker270139
Fix commit6a341af34a11 (WebKit/WebKit) +2/-1
CWECWE-119 (Buffer bounds error)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedJeff Johnson of underpassapp.com
Disclosed2024-05-13

Background

Web Inspector debugger agent
JSC instrumentation that tracks async call stacks for debugging; didScheduleAsyncCall records parent traces.
HashMap end() iterator
find() returns end() when a key is absent; dereferencing end() reads invalid memory.
ASSERT vs release
ASSERTs are compiled out of release builds, so an assertion is not a substitute for a runtime iterator check.

Root Cause Analysis

This fixes an unchecked hash-map iterator dereference in JavaScriptCore’s Web Inspector debugger agent. InspectorDebuggerAgent::didScheduleAsyncCall records async call stack traces; when there is a current async call on the stack it looks up the parent: auto it = m_pendingAsyncCalls.find(m_currentAsyncCallIdentifierStack.last()); ASSERT(it != m_pendingAsyncCalls.end()); parentStackTrace = it->value;. The ASSERT documents the expectation that the entry exists, but assertions are compiled out in release builds, so if the identifier is not actually present, find() returns the end() iterator and it->value dereferences that invalid end iterator — an out-of-bounds read of a HashMap bucket (reading and using garbage as the parent stack trace).

The fix guards the dereference with if (LIKELY(it != m_pendingAsyncCalls.end())) parentStackTrace = it->value;, so a missing entry is handled instead of dereferenced.

The restored invariant is that the iterator is validated before use in release builds, not only asserted. NOTE: this code lives in the inspector/automation debugger agent, so reachability from ordinary web content depends on that instrumentation being active; the diff establishes the defensive iterator fix, while the advisory’s arbitrary-code-execution rating implies a stronger path that this one-file change does not fully show (marked as inference).

Key insight
A HashMap lookup was only ASSERTed non-end and then dereferenced, so in release builds a missing async-call entry dereferenced the end() iterator; a runtime it != end() guard fixes it.

Attack Path

  1. Drive async-call instrumentation With the debugger/async-call-stack instrumentation active, cause didScheduleAsyncCall to run with a current async call on the stack.
  2. Miss the pending entry Arrange for the looked-up async-call identifier to be absent from m_pendingAsyncCalls so find() returns end().
  3. Dereference end() In a release build the compiled-out ASSERT does not stop it->value from dereferencing the invalid end iterator.
  4. Read out of bounds Garbage read as the parent stack trace crashes or corrupts state (advisory: code execution; escalation not shown in this diff).

Impact Assessment

An out-of-bounds read from an unchecked end() iterator in the WebContent process’s inspector agent; the advisory rates it arbitrary code execution. The shown change is a defensive iterator guard, so the demonstrated primitive is an OOB read; a fuller exploit path is not evident in this one-file diff and reachability depends on async-call instrumentation being active.

Changed Functions

FunctionChangeNotes
InspectorDebuggerAgent::didScheduleAsyncCall
Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.cpp
modified Guards the parent-stack-trace lookup with if (LIKELY(it != m_pendingAsyncCalls.end())) before dereferencing, so a missing entry is not read as an end() iterator in release builds.

Files Changed

  • Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.cpp

Audit Directions

  • Same file/agent: find() dereferences
    Audit InspectorDebuggerAgent and sibling agents for find() results dereferenced after only an ASSERT(it != end()) with no runtime check.
  • ASSERT-guarded iterators
    Grep JSC for ‘ASSERT(it != ’ followed by it->value/->second dereferences lacking a release-build guard.
diff --git a/Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.cpp b/Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.cpp
index 24352a97dc76..e46f788274a8 100644
--- a/Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.cpp
+++ b/Source/JavaScriptCore/inspector/agents/InspectorDebuggerAgent.cpp
@@ -444,7 +444,8 @@ void InspectorDebuggerAgent::didScheduleAsyncCall(JSC::JSGlobalObject* globalObj
     if (!m_currentAsyncCallIdentifierStack.isEmpty()) {
         auto it = m_pendingAsyncCalls.find(m_currentAsyncCallIdentifierStack.last());
         ASSERT(it != m_pendingAsyncCalls.end());
-        parentStackTrace = it->value;
+        if (LIKELY(it != m_pendingAsyncCalls.end()))
+            parentStackTrace = it->value;
     }
 
     auto identifier = asyncCallIdentifier(asyncCallType, callbackId);
Loading diff…

Original Bug Report

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