Audit report AR-22

Reported privately in an independent security review, 2026-08-16.

Report date
2026-08-16
Kind
Hardening
Status on master
Present
Proposed issue state
Open

Summary

A libmultiprocess server admits any number of unresolved calls on one connection, with no limit on their count or on the size of their parameters. A client that keeps sending calls while the server's worker threads are busy makes the server hold all their request state until the calls finish. The report is accurate and presents this as resource hardening: it measured no memory threshold or failure, and a client that can reach the socket already has unauthenticated RPC access. Today it is up to clients not to use too many resources.

The report

serverInvoke() admits every incoming call without a per-connection count or parameter-size quota. Calls that carry a Context are dispatched to a worker thread, and while they wait for it they keep their parameters, request storage and promise state. Bitcoin Core does not listen on an IPC socket by default, and the -ipcbind help warns that socket access grants unauthenticated RPC access. The same absence of limits applies to connections with spawned IPC processes.

The report states that the client needs access to the local connection and must keep concurrent work unresolved, that the concern is retained request state in one server process, and that no memory-exhaustion threshold, crash, failure of a protected operation, or lasting outage was measured. Completion or disconnect releases the state, provided workers release the storage they still hold. No remote entry point or effect on other nodes was shown. No affected release range is established.

Its tests are KJ tests against the real library, but they test the proposed limits rather than demonstrate a failure without them (see below).

Proposed change

A large patch to the libmultiprocess subtree (about 570 added lines in proxy-io.h, proxy-types.h, type-context.h, proxy.cpp and the tests). Each Connection gets a CallBacklog with limits on unresolved call count (default 32) and retained parameter words (default 8 Mi words), and a finite Cap'n Proto flow limit with headroom so that a rejected call cannot block callback replies. serverInvoke admits each call through it:

+    auto ticket = server.m_context.connection->m_call_backlog->admit(params.totalSize().wordCount);
+    if (!ticket) {
+        MP_LOG(loop, Log::Warning) << "IPC server reject request #" << req << " "
+            << TypeName<typename Params::Reads>() << ": call backlog limit exceeded";
+        throw std::runtime_error("IPC call backlog limit exceeded");
+    }

The ticket is attached to the request storage that the worker holds, so cancelling a call does not free its quota while the worker still uses that storage, and ~Connection releases all accounting on disconnect. New KJ tests: "Bound unresolved IPC calls and recover after cancellation", "Canceled blocked IPC call retains call quota until worker release", "Canceled blocked IPC call retains word quota until worker release", "Bound unresolved IPC call words and tiny call count", and "Preserve nested callback progress below finite flow limit".

The report says the API and its provisional defaults should be developed in libmultiprocess first, that limits change overload behaviour and need review against legitimate concurrent workloads, and that they bound each connection, not total process memory. It considered transport flow control alone, which is smaller but does not account for worker-owned request storage after cancellation.

Assessment

Accurate: on libmultiprocess master nothing bounds the number of in-flight calls on a connection. This is resource hardening under a trust model where clients are trusted. ryanofsky, replying to the report, said it "Could be useful to upstream, might be useful for hardening against untrusted IPC clients. Right now it is up to clients not to use too many resources".

The patch is a new admission and flow-control mechanism with provisional defaults. A default of 32 unresolved calls could reject legitimate clients that use many threads, and the flow-control change affects every connection. The flow-control part could be proposed on its own.

Related resource-limit reports: AR-04 (threads), AR-07 (per-method quotas), AR-09 and AR-19 (connections), AR-23 (block templates).

Status

Present on master by design. Next step, if wanted: a libmultiprocess design discussion on per-connection limits and flow control, before any code. Idea; no issue open.

Reported privately in an independent security review on 2026-08-16.