Skip to content

inspector: reject reserved HashMap keys as Debugger scriptId - #36843

Draft
robobun wants to merge 1 commit into
mainfrom
farm/ad0f8d74/inspector-scriptid-validkey
Draft

inspector: reject reserved HashMap keys as Debugger scriptId#36843
robobun wants to merge 1 commit into
mainfrom
farm/ad0f8d74/inspector-scriptid-validkey

Conversation

@robobun

@robobun robobun commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Depends on oven-sh/WebKit#386. WEBKIT_VERSION currently points at that PR's preview build and must be updated to the merged sha once it lands.

Problem

InspectorDebuggerAgent::m_scripts is an UncheckedKeyHashMap<JSC::SourceID, Script>. WTF's default integer hash traits reserve 0 as the empty-bucket key and UINT32_MAX as the deleted-bucket key. Several Debugger.* protocol handlers parse the client-supplied scriptId string with parseIntegerAllowingTrailingJunk<uint32_t>(...).value_or(0) and pass the result directly to m_scripts.find().

When an inspector client sends scriptId: "0" (or "-1", "not-a-number", or an overflow string, all of which value_or(0) collapses to 0):

  • ASSERT_ENABLED builds hit ASSERTION FAILED: isValidKey(*entry) at wtf/HashTable.h:692 and SIGABRT.
  • release builds match the first empty bucket and return a phantom default-constructed Script: Debugger.getScriptSource replies {"scriptSource":""} for a script that does not exist, and Debugger.setBreakpoint then dereferences the phantom's null sourceProvider and SIGSEGVs the debuggee.

Repro

// against bun --inspect=127.0.0.1:<port>/tok
ws.send(JSON.stringify({id:1, method:"Debugger.enable", params:{}}));
ws.send(JSON.stringify({id:2, method:"Debugger.getScriptSource", params:{scriptId:"0"}}));
// release: {"result":{"scriptSource":""},"id":2}
// debug-asan: ASSERTION FAILED: isValidKey(*entry) ... SIGABRT

ws.send(JSON.stringify({id:3, method:"Debugger.setBreakpoint", params:{location:{scriptId:"0",lineNumber:0}}}));
// release: debuggee dies, signal=SIGSEGV (Segmentation fault at address 0x70)

Debugger.searchInContent, Debugger.continueToLocation and Debugger.getBreakpointLocations take the same path.

Fix

oven-sh/WebKit#386 guards each protocol-supplied m_scripts.find() with m_scripts.isValidKey(sourceID) so the reserved keys take the same "Missing script ..." error path as any other unknown id (same pattern as InspectorDOMAgent::nodeForId). This PR carries the WEBKIT_VERSION bump and the regression tests.

Verification

With the current prebuilt WebKit (fail-before, bun bd):

(fail) Debugger.getScriptSource returns an error for the reserved hash keys
(fail) Debugger.searchInContent returns an error for the reserved hash keys
(fail) Debugger.setBreakpoint / getBreakpointLocations reject scriptId 0 in location

With oven-sh/WebKit#386 applied (pass-after, bun run build:local):

(pass) Debugger.getScriptSource returns an error for the reserved hash keys [563.02ms]
(pass) Debugger.searchInContent returns an error for the reserved hash keys [490.78ms]
(pass) Debugger.setBreakpoint / getBreakpointLocations reject scriptId 0 in location [512.08ms]
3 pass, 42 expect() calls

The fix lives in the WebKit prebuilt (bumped via scripts/build/deps/webkit.ts), so the src-stash gate cannot observe fail-before; the proof above is the manual equivalent.

Requires --inspect + the inspector token; loopback-bound by default.

Debugger.getScriptSource/searchInContent/setBreakpoint/getBreakpointLocations
parse the protocol scriptId to an unsigned SourceID and look it up directly
in m_scripts, an UncheckedKeyHashMap<unsigned, Script>. WTF integer hash
traits reserve 0 (empty) and UINT32_MAX (deleted) as sentinel keys, so a
client-supplied scriptId of "0" (or "-1"/"not-a-number"/overflow, which
value_or(0) collapses to 0) violates the table contract:

  * asserts builds: ASSERTION FAILED: isValidKey(*entry), SIGABRT
  * release: the lookup matches an empty bucket and returns a phantom
    default-constructed Script; getScriptSource replies scriptSource:""
    for a nonexistent script, and setBreakpoint dereferences the null
    sourceProvider and SIGSEGVs.

Fixed in oven-sh/WebKit#386 by guarding each protocol-supplied
m_scripts.find() with isValidKey(), same as InspectorDOMAgent::nodeForId.
This commit carries the test coverage and the WEBKIT_VERSION bump to pick
up that fix.
@robobun

robobun commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator Author

Status: waiting on the WebKit preview build.

The fix lives in the WebKit prebuilt (oven-sh/WebKit#386), bumped here via scripts/build/deps/webkit.ts. Until that PR's Preview Build workflow publishes autobuild-preview-pr-386-71c8e36d (in progress, typically ~40 minutes), bun bd fails at the WebKit download step. CI will go green once the release is available.

The src-stash gate cannot observe fail-before for this change: the fix is not under src/ or packages/, so both gate runs build the same (fixed) WebKit. Local proof using bun run build:local against the patched vendor/WebKit is in the PR description; bun bd on main (old WebKit) fails all three new tests with the expected assert/segfault.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants