Skip to content

Bound and time-limit JSON-RPC response reads - #256

Merged
agoodminute merged 1 commit into
Blockstream:new-indexfrom
agoodminute:fix/daemon-rpc-read-bounds
Sep 8, 2026
Merged

Bound and time-limit JSON-RPC response reads#256
agoodminute merged 1 commit into
Blockstream:new-indexfrom
agoodminute:fix/daemon-rpc-read-bounds

Conversation

@agoodminute

@agoodminute agoodminute commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

Connection::recv had no real bounds on reading the daemon's HTTP-framed response: the header loop had no count or byte cap, the body was read as a single unbounded line, and Content-Length was only checked after the read completed, so it didn't actually bound anything. A zero Content-Length also caused an integer underflow. Separately, the read timeout applied per syscall rather than per request, so a slow or partial response could keep resetting it indefinitely.

This PR replaces the Lines reader with a BufReader plus two bounded readers that check their budget before growing the buffer, read the body by an already-validated Content-Length, reject a zero length outright, and give each recv call a whole-request deadline derived from the socket timeout.

Why

These paths had no upper bound on memory growth or wait time for an unusual or malformed response, which could show up as excessive memory use or a stuck worker thread in edge cases — for example a very large reply, a response delivered in very small increments, or an empty body.

Changes

  • Connection::recv now enforces a whole-request deadline (recv_within), re-armed before every blocking read rather than relying on the socket's per-syscall timeout alone.

  • New bounded readers:

    • read_line_bounded — caps a single line's length before it's buffered, used for both the status line and headers.
    • read_body_bounded — reads exactly Content-Length bytes, growing incrementally rather than allocating up front.
  • New caps, environment-tunable like the existing daemon timeouts:

    Variable Default Purpose
    DAEMON_MAX_HEADER_LINE_BYTES 8 KiB max length of one header/status line
    DAEMON_MAX_HEADER_COUNT 100 max number of header lines
    DAEMON_MAX_HEADER_TOTAL_BYTES 64 KiB max cumulative header bytes
    DAEMON_MAX_BODY_BYTES 128 MiB max response body size
  • A zero Content-Length is now rejected outright instead of underflowing.

@EddieHouston EddieHouston left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm 5c47dd8

@agoodminute
agoodminute merged commit a70fdf6 into Blockstream:new-index Sep 8, 2026
6 checks passed
@agoodminute
agoodminute deleted the fix/daemon-rpc-read-bounds branch September 8, 2026 11:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants