http: fix ASAN use-after-poison in onHandshake after checkServerIdentity reject u9sf0l#29819
http: fix ASAN use-after-poison in onHandshake after checkServerIdentity reject u9sf0l#29819robobun wants to merge 1 commit into
Conversation
…hake When the native checkServerIdentity fast path rejects the hostname it calls closeAndFail → fail → result callback, which synchronously destroys the ThreadlocalAsyncHTTP that owns the HTTPClient. The onHandshake caller then wrote client.flags.did_have_handshaking_error and called client.unregisterAbortTracker() on the freed struct; ASAN flags this as use-after-poison on the HTTP thread. All three follow-up operations were already performed by closeAndFail (socket terminated, abort tracker unregistered inside fail()), so drop them. Same pattern in ProxyTunnel.onHandshake.
|
Updated 4:42 AM PT - Apr 28th, 2026
❌ @robobun, your commit ed32ece has 3 failures in
🧪 To try this PR locally: bunx bun-pr 29819That installs a local version of the PR into your bun-29819 --bun |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (4)
WalkthroughRemoves redundant error handling in SSL handshake failure paths for HTTPContext and ProxyTunnel by relying on the existing Changes
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
|
Found 3 issues this PR may fix:
🤖 Generated with Claude Code |
|
Superseded by #29829, which includes this UAF fix plus the CN-fallback / |
What
HTTPContext.onHandshake(and the mirror inProxyTunnel.onHandshake) touched theHTTPClientaftercheckServerIdentity()returnedfalse, but that path has already freed it.Repro
Any
node:httpsrequest whose CA chain verifies but whose hostname doesn't match the server cert's SAN — e.g. connecting tolocalhostwith a cert forCN=agent1andca1trusted — takes the nativecheckX509ServerIdentityfast path, which fails and returnsfalsefromHTTPClient.checkServerIdentity:Surfaced by
test-https-agent-additional-options,test-https-agent-session-injection,test-https-agent-session-reuse,test-https-agent-sockets-leak,test-https-client-checkServerIdentity,test-https-client-reject,test-tls-set-secure-contextunder release-asan.Cause
HTTPClient.checkServerIdentityreturningfalsemeans it already calledcloseAndFail:So by the time control returns to
onHandshake,clientpoints into freed (poisoned) memory. The next lines did a read-modify-write of the packedclient.flags(the 2-byte read ASAN caught), calledclient.unregisterAbortTracker()again, and re-terminated the already-terminated socket.Fix
Just
returnwhencheckServerIdentityyieldsfalse. Everything those lines did is already done insidecloseAndFail/fail:terminateSocket—closeAndFailalready called itunregisterAbortTracker— first thingfail()doesdid_have_handshaking_error = true— dead store; the only reads of that flag are… and !reject_unauthorized, and this branch is insideif (reject_unauthorized)Same change applied to the proxy-tunnel handshake which had the identical pattern.
Test
test/js/node/http/node-https-checkServerIdentity-uaf.test.tsspawns a fixture that fires 8 concurrenthttps.requests whose CA verifies but hostname doesn't, and asserts they all surfaceERR_TLS_CERT_ALTNAME_INVALIDand exit 0. Underbun bdwithout this fix the fixture aborts with the ASAN report above; with it, the error events fire and the process exits cleanly.Note on the Node tests
The 7 Node tests above no longer crash under ASAN but still time out for an unrelated reason:
_http_client.tsdoesn't forwardcheckServerIdentityintofetchOptions.tls, sonode:httpsalways uses the native fast path, which (unlike Node'stls.checkServerIdentity) doesn't fall back to Subject CN when no SAN is present. That's a separate compat gap and not addressed here, so those tests aren't checked in.