CVE-2025-43368
Overview
Background
- IPC Connection/Client
- WebKit’s inter-process message channel delivers decoded messages to a Client object via callbacks like didReceiveMessage.
- CheckedPtr vs RefPtr
- CheckedPtr detects use-after-free in debug but does not extend lifetime; RefPtr holds a reference that keeps the object alive.
- Re-entrant teardown
- A message handler can invalidate/close the connection and destroy the client while a dispatch call is still executing.
Root Cause Analysis
This fixes a use-after-free of the IPC Connection’s Client during message dispatch by replacing non-owning CheckedPtr access with a ref-holding RefPtr. Connection dispatches incoming messages to its Client (didReceiveMessage, didReceiveSyncMessage, didClose, didReceiveInvalidMessage) via checkedClient(), which returned a CheckedPtr<Client>. A CheckedPtr only verifies (in debug) that the pointee has not been freed; it does NOT keep the Client alive. Message handlers can run code that re-entrantly tears down the connection’s client (or the client can be destroyed on another thread), so a call like checkedClient()->didReceiveMessage(*this, decoder) could dereference a Client that is being or has been destroyed — a use-after-free.
The fix renames checkedClient() to protectedClient() returning RefPtr<Client> (m_client.get()), and updates the hot dispatch paths to take a local RefPtr client = m_client.get(), null-check it, and call through it, so the Client is guaranteed to stay alive for the duration of each dispatch call. dispatchDidCloseAndInvalidate and the throttling-termination path likewise load a RefPtr and bail if null.
The restored invariant is that the Client outlives every dispatch call made on it; a checked (non-owning) pointer was insufficient across calls that can drop the last reference.
Attack Path
- Send messages that re-enter A process on one end of the IPC connection sends messages whose handlers cause the connection’s Client to be torn down (invalidate/close) during dispatch.
- Dispatch through a non-owning pointer Connection calls checkedClient()->didReceive… which does not retain the Client across the call.
- Free during the call The Client is destroyed while a dispatch call is in flight, leaving a dangling pointer.
- Use-after-free Continued use of the freed Client corrupts memory in the receiving process.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
Connection::protectedClient (was checkedClient)Source/WebKit/Platform/IPC/Connection.h |
modified | Returns RefPtr<Client> (retaining) instead of CheckedPtr<Client> (non-owning). |
Connection::dispatchMessageSource/WebKit/Platform/IPC/Connection.cpp |
modified | Loads a local RefPtr client, RELEASE_ASSERTs it, and calls didReceiveMessage through it, keeping the client alive across dispatch. |
Connection::dispatchSyncMessage / dispatchDidReceiveInvalidMessage / dispatchDidCloseAndInvalidate / enqueueIncomingMessageSource/WebKit/Platform/IPC/Connection.cpp |
modified | Switch from checkedClient() to protectedClient()/RefPtr client with null checks before invoking client callbacks. |
Connection::waitForAndDispatchImmediatelySource/WebKit/Platform/IPC/Connection.cpp |
modified | Uses protectedClient()->didReceiveMessage to retain the client across the synchronous dispatch. |
Files Changed
Source/WebKit/Platform/IPC/Connection.cppSource/WebKit/Platform/IPC/Connection.h
Audit Directions
- Same file: remaining checkedClient usersGrep Connection.cpp/.h for any residual checkedClient()/CheckedPtr client uses on paths that can run client code and confirm they hold a RefPtr.
- CheckedPtr across callbacksAcross WebKit, look for CheckedPtr members dereferenced across virtual callbacks that can free the pointee; these need protected*/RefPtr upgrades.