Audit report AR-01
Reported privately in an independent security review, 2026-08-07.
- Report date
- 2026-08-07
- Kind
- Bug
- Status on master
- Fixed in mp#298
- Proposed issue state
- Closed
- Linked bugs
- Bug 72: Client can use freed memory if the server closes the socket right away
Summary
A server that closes a new connection right away can make the connecting client free its Connection object before the ProxyClient that should own it exists, a use-after-free in the client process. The report restates a bug that was already public as mp#308. Real bug, fixed in libmultiprocess by mp#298 four days after the report; the fix has not yet reached Bitcoin Core's copy of the library.
The report
ConnectStream in include/mp/proxy-io.h (as of libmultiprocess commit 67302cd, 2026-08-03) created the Connection inside loop.sync(), fetched the bootstrap capability, and registered this disconnect handler:
Connection* connection_ptr = connection.get();
connection->onDisconnect([&loop, connection_ptr] {
delete connection_ptr;
});
After sync() returned, the calling thread constructed ProxyClient<InitInterface> and only then handed over ownership with connection.release(). The report describes the failure sequence:
- The server accepts and immediately closes the socket.
- On the event-loop thread, the disconnect handler runs and deletes the
Connection, including itsEventLoopRef. - The calling thread, still holding the raw pointer in its local
unique_ptr, constructs theProxyContextfrom it. ProxyContextreadsconnection->m_loopfrom freed memory.
It cites the real NetBSD CI abort from mp#308 and notes that the race exists on every platform. Its impact statement: a server that closes at the right moment corrupts memory in the connecting process, and a crash is the demonstrated outcome.
The reproduction is a standalone C++ program that models the ordering with plain new/delete and a pair of promises, not a test against the library. The event-loop thread deletes the object, then the main thread builds a ProxyContext from the stale pointer; AddressSanitizer reports heap-use-after-free ... in ProxyContext::ProxyContext(Connection*).
Proposed change
Keep a single stable owner of the Connection until the proxy is fully constructed, and register the delete-on-disconnect behaviour only after ownership is final. The report also asks for a deterministic test with an already-closed socket and an injected delay between bootstrap and client construction, run under ASan and TSan. Its patch sketch for ConnectStream is a comment describing this, with no code.
Assessment
The description matches the code at the pinned commit and the public report. The bug was found on 2026-07-10 in mp#298 CI on NetBSD, where it aborted deterministically with assertion "m_loop" failed, and it could be reproduced elsewhere with an added sleep. The standalone model adds nothing beyond that crash.
The fix is the one the report proposes. On libmultiprocess master, ConnectStream only creates the connection and bootstraps, and ProxyClientBase's constructor registers the delete-on-disconnect handler as its last step ("If this client owns the connection, delete the connection on disconnect."). mp#298 (commit bb473690c97c) merged on 2026-08-11. The report was right that it was unmerged at the commit it examined.
No hostile peer is needed: any server that dies while a client connects can trigger it. In Bitcoin Core the affected processes are short-lived clients such as bitcoin-mine or bitcoin-cli -ipcconnect, and the realistic outcome is a crash of that client.
Bug page: ConnectStream's delete-on-disconnect handler can free the Connection before the ProxyClient that will own it is constructed. The same PR fixed the construct-failure leak described in AR-16.
Status
Fixed in libmultiprocess master by mp#298. Bitcoin Core master still carries the libmultiprocess subtree imported on 2026-07-14, so the fix arrives with the next subtree update. No further action.
Reported privately in an independent security review on 2026-08-07.