Audit report AR-16

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 73: Event loop never exits if a client's setup call fails while it is created

Summary

If the implicit construct() call made while creating an IPC client fails, the ProxyClient is never fully constructed, so its destructor never frees the Connection, and the leaked reference keeps the event loop from ever exiting. The report restates the public issue mp#309. Real bug, fixed in libmultiprocess by mp#298 four days after the report; not yet in Bitcoin Core's copy of the library.

The report

Creating the root proxy client (ProxyClient<InitInterface>) can make a blocking construct() call to the server, for interfaces that define one; Bitcoin Core's Init does, to exchange ThreadMap capabilities. ConnectStream hands the raw Connection pointer to the ProxyClient constructor (connection.release()), and the plan is for ~ProxyClient to delete it. If construct() throws, the object never finishes construction, C++ never runs its destructor, and the Connection is leaked. The leaked connection holds an EventLoopRef, so the event loop's reference count never reaches zero, and any thread that joins or shuts down the loop hangs.

The reproduction is a standalone C++ program, not a test against the library. It models an event loop reference count, a Connection that increments it, and a ProxyClient whose constructor takes the released raw pointer and then throws. After the exception it prints event_loop_refcount_after_failure=1 and event_loop_shutdown=hung.

Proposed change

Keep the Connection in a local RAII owner until every constructor and bootstrap step that can throw has completed, rather than relying on the destructor of an object whose constructor may fail. The report suggests a test with a failing implicit construct() followed by event-loop shutdown. There is no code for this part in the patch sketch.

Assessment

Correct, and the report marks it as already public. The bug was seen as an mptest hang during development of mp#298, written up as mp#309, and fixed in the same PR. On libmultiprocess master, ProxyClientBase's constructor wraps Sub::construct(*this) in a try/catch that runs the cleanup functions, including deleting an owned connection, before rethrowing. The code comment says why: ~ProxyClientBase does not run for an object whose constructor threw.

No hostile peer is needed; a server whose construct() fails, for example because it is shutting down, is enough. Only short-lived clients are affected, such as bitcoin-mine or bitcoin-cli -ipcconnect connecting to a node, and the symptom is a client that fails to exit.

Bug page: A throwing Init.construct() call during client creation leaks the Connection and its EventLoopRef. The same PR fixed the early-disconnect use-after-free in AR-01.

Status

Fixed in libmultiprocess master by mp#298 (merged 2026-08-11). Bitcoin Core master still carries the 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.