{
 "number": 35888,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/35888",
 "title": "net: reject oversized outbound messages",
 "author": "l0rinc",
 "author_association": "MEMBER",
 "created_at": "2026-08-04T22:36:58Z",
 "updated_at": "2026-09-13T18:25:39Z",
 "age_days": 43,
 "draft": false,
 "labels": [
  "P2P"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "f9188114c75d93cea911f24e6a913e74d2cda587",
 "head_ref": "l0rinc/net-reject-oversized-message-types",
 "head_repo": "l0rinc/bitcoin",
 "head_history": [
  {
   "t": "2026-08-05T19:50:26Z",
   "sha": "18ce84e1ca120ed537701de41cf479b8dbd39952"
  },
  {
   "t": "2026-08-06T19:39:49Z",
   "sha": "bfa10f0578cf9f1a4e67682e134405d4ebb202db"
  },
  {
   "t": "2026-08-06T20:33:43Z",
   "sha": "321b5907e82a31cd26bb276656c05555f966746d"
  },
  {
   "t": "2026-08-07T20:30:25Z",
   "sha": "84eddd6802f48a6fe426a5ff97d807356a97ee70"
  },
  {
   "t": "2026-08-14T20:00:14Z",
   "sha": "5270b3e35f1e485ed7de31aec6674d47e7b20ce8"
  },
  {
   "t": "2026-09-06T21:00:34Z",
   "sha": "f9188114c75d93cea911f24e6a913e74d2cda587"
  }
 ],
 "additions": 87,
 "deletions": 8,
 "changed_files": 5,
 "commit_count": 4,
 "size_bucket": "S",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "ajtowns",
      "url": "https://github.com/bitcoin/bitcoin/pull/35888#issuecomment-5198628979"
     },
     {
      "login": "naiyoma",
      "url": "https://github.com/bitcoin/bitcoin/pull/35888#pullrequestreview-5191744155"
     }
    ]
   },
   "conflicts": []
  }
 },
 "acks_parsed": {
  "ajtowns": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-08-05T23:40:16Z",
   "stale": false
  },
  "naiyoma": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-09-13T18:25:36Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 2,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "ajtowns",
   "mzumsande",
   "naiyoma"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-09-06T21:05:01Z",
  "last_reviewer_activity": "2026-09-13T18:25:36Z",
  "last_reviewer": "naiyoma",
  "author_silent_days": 10,
  "waiting_on_author_days": 3,
  "days_since_update": 3
 },
 "refs": {
  "mentioned": [
   35880
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 35880,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-08-05",
    "title": "fuzz: don't connman.ReceiveMsgFrom oversized msg"
   }
  ],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/net.cpp",
  "src/rpc/net.cpp",
  "src/test/net_tests.cpp"
 ],
 "body": "**Problem:** Outbound transports encode message types in a fixed 12-byte field, but `CConnman` did not ensure internal callers respected this limit before queuing messages.\nAn oversized type aborts V1 encoding, while V2 can overwrite the first payload byte or write past its encoding buffer.\nNormal outbound payloads are also expected to stay within the 4 MB protocol limit, but the send path did not enforce this invariant.\n\n**Fix:** Treat oversized types and payloads as failed assumptions in `CConnman::PushMessage()` before they enter the send queue.\nDebug and fuzz builds expose invalid internal callers, while release builds log and drop the messages.\n The testing-only `sendmsgtopeer` RPC rejects oversized payloads before calling `PushMessage()`, while `p2p_invalid_messages.py` continues to cover inbound handling.",
 "commits": [
  {
   "sha": "b4cdcb2ad8f8a7df20ff68e63896092e22406395",
   "date": "2026-09-06T20:13:45Z",
   "message": "test: characterize outbound message type limits\n\nCover the accepted 12-byte type limit for both outbound transports.\n\n`CConnman::PushMessage()` queues a 13-byte type.\nKeep a valid message pending in the transport to observe queueing without encoding this invalid input.\nThis records the old behavior before the send path treats an oversized type as a failed assumption."
  },
  {
   "sha": "4b10305dee85f428c02381dce151396d707fccd6",
   "date": "2026-09-06T20:13:45Z",
   "message": "net: assume outbound message types fit\n\nOutbound transports encode message types in a fixed 12-byte wire field.\nV1 aborts on a longer type, while V2 can overwrite the first payload byte or write past its encoding buffer.\nNormal outbound types are internal constants, and `sendmsgtopeer` already rejects types longer than 12 bytes.\n\nTreat oversized types from internal callers as failed assumptions in `CConnman::PushMessage()` before they enter the send queue.\nDebug and fuzz builds expose invalid internal callers, while release builds drop the messages.\n\nCo-authored-by: Anthony Towns <aj@erisian.com.au>\nCo-authored-by: Martin Zumsande <mzumsande@gmail.com>"
  },
  {
   "sha": "c10ff78c8010e8e6a911d926edca8b16ef1a7df2",
   "date": "2026-09-06T20:13:45Z",
   "message": "test: characterize outbound payload limits\n\nCover the accepted 4 MB payload limit for both outbound transports.\n\n`CConnman::PushMessage()` queues a 4,000,001-byte payload.\nThis records the old behavior before the send path treats an oversized payload as a failed assumption.\n\nThe existing two-node `sendmsgtopeer` test also confirms that the oversized payload is sent and makes the peer disconnect."
  },
  {
   "sha": "f9188114c75d93cea911f24e6a913e74d2cda587",
   "date": "2026-09-06T20:13:45Z",
   "message": "net: reject oversized outbound payloads\n\nNormal outbound messages stay within the 4 MB protocol limit.\nTreat an oversized payload from an internal caller as a failed assumption in `CConnman::PushMessage()` before it enters the send queue.\nDebug and fuzz builds expose invalid internal callers, while release builds log and drop the message.\n\nThis requires the testing-only `sendmsgtopeer` RPC to reject oversized payloads before calling `PushMessage()`.\nOversized inbound messages remain testable through raw P2P inputs, so this does not remove coverage of their receive-side handling.\nThe `p2p_private_broadcast` fuzz target separately filters synthetic oversized transactions before its loopback send path (#35880).\n\nCo-authored-by: Anthony Towns <aj@erisian.com.au>\nCo-authored-by: Martin Zumsande <mzumsande@gmail.com>"
  }
 ],
 "timeline": [
  {
   "t": "2026-08-05T19:50:26Z",
   "kind": "force_push",
   "who": "l0rinc",
   "commit": "18ce84e1ca120ed537701de41cf479b8dbd39952"
  },
  {
   "t": "2026-08-05T23:35:56Z",
   "kind": "review_comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "path": "src/test/net_tests.cpp",
   "commit": "18ce84e1ca120ed537701de41cf479b8dbd39952",
   "in_reply_to": null,
   "text": "```c++\nconstexpr std::string MAX_MESSAGE_TYPE{\"xxxxxxxxxxxx\"};\nstatic_assert(MAX_MESSAGE_TYPE.size() == MESSAGE_TYPE_SIZE);\n```\n\nwould be easier for the reader I think."
  },
  {
   "t": "2026-08-05T23:39:50Z",
   "kind": "review_comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "path": "src/net.cpp",
   "commit": "18ce84e1ca120ed537701de41cf479b8dbd39952",
   "in_reply_to": null,
   "text": "`Assume(MessageIsWithinLimits(msg))` might read better? Should this test be part of the `CSerializedNetMsg` class in protocol.h? Would probably be a useful place to add documentation that code constructing these msgs must ensure they're not oversized."
  },
  {
   "t": "2026-08-05T23:40:16Z",
   "kind": "comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "text": "Concept ACK\n\nCan you explain your thinking / where you were coming from in relation to https://github.com/bitcoin/bitcoin/pull/35880#pullrequestreview-4858213755 some more? Even if whatever the concerns were are resolved to your satisfaction already, it makes me think there's a hole in my understanding here that I'd like to fill before reviewing properly."
  },
  {
   "t": "2026-08-06T19:39:49Z",
   "kind": "force_push",
   "who": "l0rinc",
   "commit": "bfa10f0578cf9f1a4e67682e134405d4ebb202db"
  },
  {
   "t": "2026-08-06T20:24:51Z",
   "kind": "comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nI don't think you're missing anything on the receive side. I had already found that the related send path was missing checks, so when this fuzz failure showed another oversized-message case, I wanted to verify both paths before excluding it. At that point I hadn't established that the fuzzer was bypassing a receive-side guarantee, so I treated the outbound failure as something the send path should reject.\n\nThere was also a more directly related reason I did not want to assume every receive-side limit was enforced early: https://github.com/bitcoin/bitcoin/pull/35936 found that `getblocks` and `getheaders` enforced `MAX_LOCATOR_SZ` only after deserialization, so a truncated oversized locator could allocate and throw before reaching the disconnect check. (edited)\n\nI was also cautious because similar fuzz constraints and sanitizer workarounds had hidden actual problems before:\n* the [headers-sync target kept the clock above the underflow case](https://github.com/bitcoin/bitcoin/pull/35208/changes#diff-0d080ed70ead25af56b0d1b20d963d716304a8bfac42532cae84bb23c3573789L63)\n* the [coins-view target flushed corrupted accounting while UBSan suppressed the affected methods](https://github.com/bitcoin/bitcoin/pull/32313/changes/d7c9d6c2914aadd711544908d0fad8857a809c72)\n* the [crypto target replaced empty vectors to avoid UBSan instead of exposing the empty-key HMAC issue](https://github.com/bitcoin/bitcoin/pull/35688/changes#diff-d337bc8a42ed6b159b57c4bc5741812bac727131689b77823fc3435631d28705L24-L28)\n* the [bloom target constrained its parameters away from arithmetic UB](https://github.com/bitcoin/bitcoin/pull/35818/changes#diff-08488c05c2dbd34ba9a5fa69d512bc66046f286c2c33e84616986e22bff7076eL22-L23).\n\nAfter tracing this case, I agree that an oversized transaction cannot reach this path from a real peer, so restricting the fuzz target is correct. Thanks for the pushback and feedback.\n\n[quoted text omitted]\nAddressed your suggestions, thanks, pushed.\nEdit: it seems libc++ has some problems with `constexpr` strings, so I changed it to `std::string_view` and materialized on call site."
  },
  {
   "t": "2026-08-06T20:33:43Z",
   "kind": "force_push",
   "who": "l0rinc",
   "commit": "321b5907e82a31cd26bb276656c05555f966746d"
  },
  {
   "t": "2026-08-07T20:30:25Z",
   "kind": "force_push",
   "who": "l0rinc",
   "commit": "84eddd6802f48a6fe426a5ff97d807356a97ee70"
  },
  {
   "t": "2026-08-10T16:14:51Z",
   "kind": "review_comment",
   "who": "mzumsande",
   "assoc": "MEMBER",
   "path": "src/rpc/net.cpp",
   "commit": "f9188114c75d93cea911f24e6a913e74d2cda587",
   "in_reply_to": null,
   "text": "I don't think it's a \"problem\" per se that `sendmsgtopeer` accepts payloads above the 4 MB protocol limit - it's a test RPC after all. The story should be that you want to add the assumption to prevent future regressions etc., and need to change the rpc for that - which is no loss because it's just a test rpc and oversized messages are being tested elsewhere."
  },
  {
   "t": "2026-08-10T17:06:02Z",
   "kind": "review_comment",
   "who": "mzumsande",
   "assoc": "MEMBER",
   "path": "src/net.cpp",
   "commit": "84eddd6802f48a6fe426a5ff97d807356a97ee70",
   "in_reply_to": null,
   "text": "What is the reason for the non-debug build `return` instead of only adding the Assume?\nI think if this was somehow reached in production, returning false would result in a stall of the connection for minutes (until it get disconnected for inactivity), since `SocketSendData` will never make progress.\n\nAlso, wouldn't it be nicer from an architectural point of view if arbitrary application-level rules such as the 4MB maximum message length wouldn't be enforced at the transport level, but only at the higher level (`PushMessage`) anyway?\nThen we wouldn't need the same assumption 3 different places."
  },
  {
   "t": "2026-08-10T19:26:43Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/net.cpp",
   "commit": "84eddd6802f48a6fe426a5ff97d807356a97ee70",
   "in_reply_to": 3751670154,
   "text": "[quoted text omitted]\n\nIt was suggested in https://github.com/bitcoin/bitcoin/pull/35880#issuecomment-5185966715.\nI initially applied it because `SetMessageToSend()` already returns a boolean, and I treated `false` as a safe fallback in release builds while `Assume` exposed invalid callers in debug and fuzz builds.\nGood catch that `false` will just try again later, so a permanently invalid message would remain queued and stall the connection.\nI moved both size assumptions to `PushMessage()`, before the message enters the send queue.\nThis also keeps the 4 MB application-level rule out of the transports - added you as coauthor, thanks for the review."
  },
  {
   "t": "2026-08-14T19:51:31Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/rpc/net.cpp",
   "commit": "f9188114c75d93cea911f24e6a913e74d2cda587",
   "in_reply_to": 3751283699,
   "text": "Pushed, let me know if I understood the request."
  },
  {
   "t": "2026-08-14T20:00:14Z",
   "kind": "force_push",
   "who": "l0rinc",
   "commit": "5270b3e35f1e485ed7de31aec6674d47e7b20ce8"
  },
  {
   "t": "2026-08-14T20:02:04Z",
   "kind": "review",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "5270b3e35f1e485ed7de31aec6674d47e7b20ce8",
   "text": "Rebased and addressed @mzumsande's review: outbound limits are now enforced only in `CConnman::PushMessage()`, before messages enter the send queue, so a permanent rejection cannot stall `SocketSendData()` through `SetMessageToSend(false)`. The `sendmsgtopeer` behavior and the PR rationale now reflect the RPC's testing-only role."
  },
  {
   "t": "2026-09-06T21:00:34Z",
   "kind": "force_push",
   "who": "l0rinc",
   "commit": "f9188114c75d93cea911f24e6a913e74d2cda587"
  },
  {
   "t": "2026-09-06T21:05:01Z",
   "kind": "comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "text": "AI review flagged that I was missing coverage so added `PushMessage()` tests because the transport tests bypassed the guard and didn\u2019t verify rejection before queueing. Also rebased and split the type and payload characterization/fix pairs so each fix updates its own expectations (otherwise it's not obvious why multiple fixes are needed)."
  },
  {
   "t": "2026-09-13T18:25:36Z",
   "kind": "review",
   "who": "naiyoma",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "f9188114c75d93cea911f24e6a913e74d2cda587",
   "text": "Concept ACK\n\nnit: Maybe it also makes sense to restrict the message type copy to `MESSAGE_TYPE_SIZE`?\n```diff\n-        std::copy(msg.m_type.begin(), msg.m_type.end(), contents.data() + 1);\n+        std::copy_n(msg.m_type.begin(),\n+                std::min(msg.m_type.size(), CMessageHeader::MESSAGE_TYPE_SIZE),\n+                contents.data() + 1);\n```"
  }
 ],
 "labels_log": [
  {
   "t": "2026-08-04T22:37:02Z",
   "action": "labeled",
   "label": "P2P",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-06T21:31:33Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-07T21:55:23Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-14T17:49:43Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-14T20:01:37Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-08-05T04:41:28Z",
   "kind": "convert_to_draft",
   "who": "l0rinc"
  },
  {
   "t": "2026-08-05T19:56:16Z",
   "kind": "ready_for_review",
   "who": "l0rinc"
  },
  {
   "t": "2026-08-06T22:21:00Z",
   "kind": "closed",
   "who": "l0rinc"
  },
  {
   "t": "2026-08-06T22:21:03Z",
   "kind": "reopened",
   "who": "l0rinc"
  },
  {
   "t": "2026-08-07T05:57:26Z",
   "kind": "closed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-07T05:57:35Z",
   "kind": "reopened",
   "who": "DrahtBot"
  }
 ],
 "text_chars": 8726,
 "text_tokens_estimate": 2181,
 "changed_paths": [
  "src/net.cpp",
  "src/net.h",
  "src/rpc/net.cpp",
  "src/test/net_tests.cpp",
  "test/functional/rpc_net.py"
 ],
 "files": [
  {
   "path": "src/net.cpp",
   "add": 4,
   "del": 0
  },
  {
   "path": "src/net.h",
   "add": 10,
   "del": 1
  },
  {
   "path": "src/rpc/net.cpp",
   "add": 4,
   "del": 1
  },
  {
   "path": "src/test/net_tests.cpp",
   "add": 60,
   "del": 2
  },
  {
   "path": "test/functional/rpc_net.py",
   "add": 9,
   "del": 4
  }
 ],
 "test_lines": 75,
 "git": {
  "head": "f9188114c75d93cea911f24e6a913e74d2cda587",
  "head_matches_backup": true,
  "base": "a174dd4017cf97f283b1848bb31998bfa28465e9",
  "commits": [
   {
    "sha": "b4cdcb2ad8",
    "subject": "test: characterize outbound message type limits",
    "files": 1,
    "add": 53,
    "del": 2
   },
   {
    "sha": "4b10305dee",
    "subject": "net: assume outbound message types fit",
    "files": 2,
    "add": 2,
    "del": 1
   },
   {
    "sha": "c10ff78c80",
    "subject": "test: characterize outbound payload limits",
    "files": 2,
    "add": 18,
    "del": 5
   },
   {
    "sha": "f9188114c7",
    "subject": "net: reject oversized outbound payloads",
    "files": 5,
    "add": 20,
    "del": 6
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "9986abe2b2827b8e",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}