diff --git a/src/xrpld/app/ledger/InboundLedgers.h b/src/xrpld/app/ledger/InboundLedgers.h index e288201c66..97182644e7 100644 --- a/src/xrpld/app/ledger/InboundLedgers.h +++ b/src/xrpld/app/ledger/InboundLedgers.h @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -20,6 +21,36 @@ namespace xrpl { +// Per-node cap for AS state leaves stashed via `gotStaleData`. +// +// `gotStaleData` only handles `liAS_NODE` payloads, which carry +// SHAMap state-map leaves (ledger objects). +// +// Sizing: worst-case serialized size across all 31 ledger entry +// types is ~53 KB (`XChainOwnedCreateAccountClaimID`, 256 +// attestations x ~209 B, capped by `kMaxAttestations` in +// `include/xrpl/protocol/XChainAttestations.h`), followed by +// `XChainOwnedClaimID` ~40 KB, `NFTokenPage` ~9.5 KB, and +// `LedgerHashes` ~8.2 KB. 256 KiB leaves ~4.8x headroom over the +// current worst case. +// +// Future-proofing: this cap is NOT derived from a single protocol +// constant — it is a soft bound over independently-tuned caps +// (`kMaxAttestations`, `kDirMaxTokensPerPage`, `kMaxTokenUriLength`, +// etc.). Two types (`Amendments`, `NegativeUNL`) have no hard schema +// cap and grow with network state. Revisit if a new object type or +// a lifted array cap approaches ~256 KiB. The downstream +// `SHAMapAccountStateLeafNode` construction rejects anything above +// the 16 MiB SHAMapItem invariant regardless. +inline constexpr std::size_t kMaxFetchPackNodeBytes = 256 * 1024; + +// Aggregate cap on the sum of `nodedata().size()` across all entries +// in a single `TMLedgerData` message. Rejects amplification-shaped +// payloads (many nodes, each individually under `kMaxFetchPackNodeBytes`, +// that together dwarf the per-message budget) at ingress in PeerImp, +// before dispatch into `InboundLedger::gotData` or `gotStaleData`. +inline constexpr std::size_t kMaxLedgerDataBytes = megabytes(1); + /** * Manages the lifetime of inbound ledgers. * diff --git a/src/xrpld/app/ledger/detail/InboundLedgers.cpp b/src/xrpld/app/ledger/detail/InboundLedgers.cpp index dc361694cf..81544fd234 100644 --- a/src/xrpld/app/ledger/detail/InboundLedgers.cpp +++ b/src/xrpld/app/ledger/detail/InboundLedgers.cpp @@ -259,6 +259,9 @@ public: if (!node.has_nodeid() || !node.has_nodedata()) return; + if (node.nodedata().size() > kMaxFetchPackNodeBytes) + return; + auto newNode = SHAMapTreeNode::makeFromWire(makeSlice(node.nodedata())); if (!newNode) diff --git a/src/xrpld/overlay/detail/PeerImp.cpp b/src/xrpld/overlay/detail/PeerImp.cpp index 962ab0f408..0b969792ba 100644 --- a/src/xrpld/overlay/detail/PeerImp.cpp +++ b/src/xrpld/overlay/detail/PeerImp.cpp @@ -1699,6 +1699,19 @@ PeerImp::onMessage(std::shared_ptr const& m) return; } + { + std::size_t totalNodeBytes = 0; + for (int i = 0; i < m->nodes_size(); ++i) + totalNodeBytes += m->nodes(i).nodedata().size(); + if (totalNodeBytes > kMaxLedgerDataBytes) + { + JLOG(pJournal_.warn()) + << "Ledger data: oversized nodes (" << totalNodeBytes << " bytes)"; + fee_.update(Resource::kFeeInvalidData, "oversized ledger nodes"); + return; + } + } + // If there is a request cookie, attempt to relay the message if (m->has_requestcookie()) {