Skip to content

fix(network): cap recv_file() cumulative bytes at 1 GiB#156

Merged
adequatelimited merged 1 commit intoaudit-fixesfrom
audit/F-18-recv-file-size-limit
Apr 13, 2026
Merged

fix(network): cap recv_file() cumulative bytes at 1 GiB#156
adequatelimited merged 1 commit intoaudit-fixesfrom
audit/F-18-recv-file-size-limit

Conversation

@adequatelimited
Copy link
Copy Markdown
Collaborator

Summary

  • New constant MAX_RECV_FILE_BYTES = 1 GiB in src/types.h
  • recv_file() now tracks cumulative received bytes and aborts the transfer + cleans up the partial file when the cap is exceeded
  • Empirically validated with a fork-based test harness committed as .disable per the existing convention

Problem

recv_file() appended incoming OP_SEND_FILE payloads to disk until the peer sent a short (EOF-signalling) packet or the connection dropped. A peer streaming full-size packets forever can fill the node's disk — remote DoS.

Fix

/* src/types.h */
#define MAX_RECV_FILE_BYTES  ((size_t)(1024ULL * 1024 * 1024))
/* src/network.c — recv_file() */
size_t total = 0;
...
total += len;
if (total > MAX_RECV_FILE_BYTES) {
   pdebug("(%s, %s) *** exceeded MAX_RECV_FILE_BYTES", np->id, fname);
   break;
}

1 GiB sits well above any legitimate file the protocol transfers (tfile.dat: tens-to-hundreds of MB on a mature chain; block files: ≤ 448 MB by protocol limits). The existing error path (fclose(fp); remove(fname); return VERROR;) handles cleanup when the cap trips.

Verification

Built a fork-based harness (src/test/_f18-recv-file-limit.c.disable) that:

  1. Forks a fake peer on 127.0.0.1 that accepts one connection, does the 3-way handshake using library send_tx/recv_tx, then streams OP_SEND_FILE packets with max-size buffers indefinitely (never sends a short EOF packet).
  2. Parent calls callserver() + recv_file() against the fake peer, then inspects the return code and whether the partial file was deleted.

Ran with the cap temporarily lowered to 16 MiB for a fast cycle (cap restored to 1 GiB before this commit). Result:

parent: cap = 16777216 bytes
parent: calling callserver() then recv_file()...
child: handshake done, streaming OP_SEND_FILE...
parent: recv_file returned 1 (expect 1 = VERROR)
parent: partial file correctly removed
[PASS] F-18: recv_file enforced the size cap

The harness is committed as a .disable file matching the existing repo convention for reference test code not integrated into make test (see gettx-tag-resolve.c.disable, proof-checkproof.c.disable, proof-support.c.disable). It builds cleanly against the library and can be run manually by compiling against libmochimo.a.

Test plan

  • CI builds pass on Ubuntu x64 + arm64
  • CodeQL passes
  • Reviewer (optional): compile and run the harness locally to reproduce the trip
  • Live mainnet sync continues to work with the 1 GiB cap in place (tfile.dat + block files are all well under)

Closes #92

recv_file() had no upper limit on total bytes received. The loop
appended OP_SEND_FILE packet payloads to the output file until the
peer sent a short (EOF-signalling) packet or the connection dropped.
A peer that streams full-size packets forever can fill the node's
disk -- remote DoS.

Added MAX_RECV_FILE_BYTES (1 GiB) in types.h and a cumulative `total`
counter in recv_file(). When total exceeds the cap, the loop breaks
and the existing error path deletes the partial file via
fclose+remove, returning VERROR. 1 GiB is well above any legitimate
single-file transfer the protocol performs (tfile.dat: tens-to-
hundreds of MB; block files: <= 448 MB by protocol limits).

Verified with a fork-based test harness (committed as .disable per
the existing convention for reference test code not wired into
`make test`). The harness forks a fake peer that streams OP_SEND_FILE
indefinitely; the parent calls recv_file() and confirms VERROR return
plus partial-file removal. Tested with cap temporarily lowered to
16 MiB for a fast cycle; cap restored to 1 GiB before commit.

Closes #92
@adequatelimited
Copy link
Copy Markdown
Collaborator Author

Simple fix to count downloaded bytes from a potentially malicious peer to prevent them from sending perpetual byte streams when responding to legitimate data requests. Hard cap on all file transfers now limited to 1GB. No hard fork required to extend this if we start seeing larger file transfers in 20 years.

@adequatelimited adequatelimited merged commit 41403ca into audit-fixes Apr 13, 2026
4 of 5 checks passed
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.

1 participant