Review report

bitcoin/bitcoin: PR #36753

Head: 3667e19fe9b1 路 Base: 377c7711cf66
Completed: 2026-11-05T13:25:41Z

Run archive 路 Previous runs

1 major finding 路 3 minor findings

Findings

馃敶 Major 路 Refused-batch summaries amplify memory and CPU use

An authenticated, whitelist-restricted HTTP caller can make a small refused batch build gigabytes of error strings, while distinct denied names also cause quadratic work.

Location: src/httprpc.cpp:140 (head).

Details and suggested fix

WhitelistErrorBatch joins every distinct denied method name without bounding count or length, then copies the complete summary into each permitted, parseable, non-notification member's -39 error. For a user permitted to call getblockcount, one denied name of 1 MiB followed by 4096 legacy {"method":"getblockcount"} members requires about 4 GiB of message copies from roughly 1.1 MiB of input. Legacy members without IDs are not notifications. Serialization adds more storage, and neither the 32 MiB input limit nor send-buffer backpressure bounds this construction. Base returned an empty 403 at the first denial. Separately, the vector-based deduplication takes O(K虏) comparisons for K distinct denied names, occupying one worker without a cancellation check. Users denied all methods through -rpcwhitelistdefault now reach that path too. These are source-derived costs, not measured peaks or durations; memory exhaustion may terminate the process or produce a recoverable allocation failure and HTTP 500, depending on the platform. Bound the collected name count and each name's length before building the summary, stop deduplicating once that budget is reached, and indicate truncation. This preserves whole-batch refusal and the per-member error codes. Add a regression test that combines a long denied name with many permitted members and checks bounded reply growth.

馃煚 Minor 路 bitcoin-cli -generate reports success when generatetoaddress is denied

With getnewaddress allowed and generatetoaddress denied, bitcoin-cli -generate now prints "blocks": null and exits successfully instead of reporting the whitelist error.

Location: src/httprpc.cpp:199 (head).

Details and suggested fix

When the initial getnewaddress call succeeds, CommandLineRPC sends generatetoaddress through GenerateToAddressRequestHandler as JSON-RPC 2.0. The new HTTP 200 response containing -38 reaches ProcessReply, which reads only result and constructs a replacement reply with no error. The missing result becomes null, so the CLI prints an address with "blocks": null and exits 0 even though generation did not run. The handler omission predates this PR, but changing whitelist denials from empty HTTP 403 responses to JSON-RPC errors newly exposes it for these refusals. Return the original reply unchanged when its error is non-null, and add a whitelist test that permits getnewaddress, denies generatetoaddress, and checks the permission message and nonzero exit. No deployment with this exact whitelist split was identified, so production impact is unconfirmed.

馃煚 Minor 路 Non-object batch members inherit another request's reply identity

Whitelisted callers' non-object batch members now reach a loop that reuses the previous member's ID and version, causing incorrectly identified errors or suppressing them as notifications.

Location: src/httprpc.cpp:223 (head).

Details and suggested fix

When no string method in the batch is denied, the preflight now skips non-object members and passes them to the ordinary execution loop. That loop reuses jreq, while parse() rejects a non-object before resetting its ID or version. With getblockcount permitted, [{"jsonrpc":"2.0","method":"getblockcount","id":0},7] therefore produces both the success and invalid-request error with ID 0. Omitting the first member's ID instead makes the scalar inherit notification state, suppresses its error, and yields HTTP 204. Base rejected this batch during whitelist preflight; the underlying reuse already affected unrestricted users. Keep the original connection context unchanged and parse each member into a fresh copy, as the refused-batch helper does. Add cases with a scalar after an identified request and after a notification; the new malformed-member tests only use objects that reset the ID. No current client sending non-object batch members was identified, so production impact is unconfirmed.

馃煚 Minor 路 Default-denied malformed requests can now abort Debug builds

httprpc.cpp:185 now parses requests from default-denied users before the whitelist check. A 2.0 request that fails later validation reaches JSONErrorReply's Assume (line 46): Debug builds abort, release builds return 400. Already true for other users; #36758 fixes the root cause.

Location: src/httprpc.cpp:183 (head).

Details and suggested fix

Earlier report and explanation. Check its locations and suggested fix against this revision.

Coverage details