Audit report AR-64
Reported privately in an independent security review, 2026-09-27.
- Report date
- 2026-09-27
- Kind
- Hardening
- Status on master
- Present
- Proposed issue state
- Open
Summary
Two mining interface methods, getTransactionsByTxID and getTransactionsByWitnessID, return one serialized transaction for every id in the request, with no limit on how many ids a request may contain. Asking for the same large mempool transaction many times makes the node build a response thousands of times larger than the request. The report is accurate and presents it as resource hardening: the memory is released after the call, nothing failed, and a client that can reach the IPC socket can already stop the node. A reasonable small limit for Bitcoin Core, in the same family as the quota findings AR-04 and AR-22.
The report
The lookup methods serialize one full mempool transaction for each requested position, including repeated ones, before any IPC message size limit applies. A request with 24,768 bytes of transaction ids made the node build at least 68,232,744 bytes of transactions and raised its memory use by 268,112 KiB before the response was rejected. The connection stayed usable afterwards.
The report rates it as hardening. It needs a trusted local IPC client, and no out-of-memory termination, network path, lasting state, funds or consensus effect was shown.
Proposed change
Limit both methods on the IPC server side to what a valid block could need: at most MAX_BLOCK_WEIGHT / MIN_TRANSACTION_WEIGHT ids (16,666), and at most MAX_BLOCK_SERIALIZED_SIZE (4,000,000 bytes) of returned transactions, checked before the response is built. Calls from inside the node are unchanged.
+ if (ids.size() > MAX_BLOCK_WEIGHT / MIN_TRANSACTION_WEIGHT) {
+ throw std::runtime_error{"Mining transaction lookup exceeds maximum block transaction count"};
+ }
+ auto result{ProxyMethodTraits<MethodParams>::invoke(server_context, std::forward<Ids>(ids))};
+ size_t serialized_size{0};
+ for (const auto& tx : result) {
+ if (!tx) continue;
+ const size_t tx_size{::GetSerializeSize(TX_WITH_WITNESS(*tx))};
+ if (tx_size > MAX_BLOCK_SERIALIZED_SIZE - serialized_size) {
+ throw std::runtime_error{"Mining transaction lookup response exceeds maximum block serialized size"};
+ }
+ serialized_size += tx_size;
+ }
The functional test interface_ipc_mining.py gains checks for empty lists, a large transaction requested exactly up to and one past the size limit, the id-count boundary, and a normal lookup on the same connection afterwards.
Assessment
Accurate. On Bitcoin Core master, the node's implementations of both methods look up each requested id in the mempool and return the results in order, with no limit on the count and no deduplication, and the IPC layer serializes every entry into the reply. The methods were added in bitcoin#34020 (merged 2026-07-07) and are in the v32.0 release candidates. The report's measurements were not repeated here.
It is not a stability bug. The memory is used while the reply is built and freed when the call completes, nothing crashed, and the caller is a local client with full RPC access, which can already make the node hold a lot of memory on purpose in other ways, such as many concurrent calls (AR-22) or many threads (AR-04). It is not an explanation for the memory growth seen with Stratum v2 clients (bitcoin#33940), which was reported on v30.0, before these methods existed.
The limits are well chosen: no valid block has more than 16,666 transactions or more than 4 MB of serialized transactions, so lookups for a real block template fit. Deduplicating ids would be simpler but would not bound requests for many distinct large transactions, so the size cap is the better choice. The change belongs in Bitcoin Core rather than libmultiprocess. Applying the same limits in the node's mining implementation would give in-process callers the same contract.
Status
Present on master. Hardening; no public issue or PR yet.
Reported privately in an independent security review on 2026-09-27.