fix: Cap untrusted manifests per message and drop oversized ones

This commit is contained in:
Pratik Mankawde
2026-08-03 17:00:11 +01:00
committed by Ayaz Salikhov
parent 4eece4003d
commit 8461ded0d8
11 changed files with 175 additions and 56 deletions

View File

@@ -56,16 +56,16 @@ encodedSize(std::size_t const nBytes)
}
/**
* Returns the maximum number of bytes a base64 string of @p nChars characters
* Returns the maximum number of bytes a base64 string of @p numChars characters
* decodes to.
*
* @param nChars Number of base64 characters.
* @param numChars Number of base64 characters.
* @return Upper bound on the number of decoded bytes.
*/
constexpr std::size_t
decodedSize(std::size_t const nChars)
decodedSize(std::size_t const numChars)
{
return ((nChars / 4) * 3) + 2;
return ((numChars / 4) * 3) + 2;
}
} // namespace base64

View File

@@ -51,8 +51,8 @@ namespace xrpl {
entry (which contains the manifest for this validator) is decoded and
added to the manifest cache. Other manifests are added as "gossip"
received from xrpld peers, including ones for validators this node does not
list. Manifests for unlisted validators are capped (kMaxUntrustedCount)
so peer gossip cannot grow the cache without bound; listed validators are
trust. Manifests for untrusted validators are capped (kMaxUntrustedCount)
so peer gossip cannot grow the cache without bound; trusted validators are
not capped. Entries are never evicted, so a stored revocation is permanent.
When an ephemeral key is compromised, a new signing key pair is created,
@@ -170,14 +170,14 @@ std::string
to_string(Manifest const& m);
/**
*Largest a valid manifest can be, in decoded bytes.
* Largest a valid manifest can be, in decoded bytes.
*
* A manifest has a fixed set of fields. Each is serialized as a field header
* (1-2 bytes), an optional length prefix (1 byte for these sizes), and the
* field body. Taking every field at its largest gives the maximum below, so
* anything larger cannot be a valid manifest.
*
* Field header + length + body = bytes
* Field header + length + body = bytes
* sfVersion (U16) 2 0 2 4
* sfSequence (U32) 1 0 4 5
* sfPublicKey (33) 1 1 33 35
@@ -200,6 +200,23 @@ constexpr std::size_t kMaxManifestBytes = 358;
*/
constexpr std::size_t kMaxManifestBase64 = base64::encodedSize(kMaxManifestBytes);
/**
* Maximum number of manifests carried in a single TMManifests message.
*
* Outbound, the TMManifests message sent to a peer includes every trusted
* manifest and fills the rest of this budget with untrusted gossip, so it
* never exceeds this size. Inbound, trusted manifests are always processed
* and untrusted ones are processed up to this many, so a peer sending its
* whole cache cannot force unbounded work.
*
* The trusted set is tiny relative to this bound, so trusted manifests are
* not dropped in practice. This is a transitional per-message cap; the cache
* already bounds untrusted manifests (see kMaxUntrustedCount), so it is no
* longer needed once the network has upgraded past nodes that send their
* whole cache in one message.
*/
constexpr std::size_t kMaxManifestsPerMessage = 200;
/**
* Constructs Manifest from serialized string
*
@@ -208,7 +225,7 @@ constexpr std::size_t kMaxManifestBase64 = base64::encodedSize(kMaxManifestBytes
* @return `std::nullopt` if string is invalid
*
* @note This does not verify manifest signatures.
* `Manifest::verify` should be called after constructing manifest.
* `Manifest::verify` should be called after constructing manifest.
*/
/** @{ */
std::optional<Manifest>
@@ -303,7 +320,7 @@ to_string(ManifestDisposition m)
* must choose. `Capped` is the safe, flood-resistant value; only listed or
* configured keys should use `Uncapped`.
*/
enum class ManifestRateLimitCap : std::uint8_t {
enum class ManifestRateLimitCapPolicy : std::uint8_t {
Capped, ///< Subject to the untrusted cap (unlisted peer gossip)
Uncapped ///< Bypasses the cap (listed/trusted or config manifests)
};
@@ -469,7 +486,7 @@ public:
* May be called concurrently
*/
ManifestDisposition
applyManifest(Manifest m, ManifestRateLimitCap cap);
applyManifest(Manifest m, ManifestRateLimitCapPolicy cap);
/**
* Stop counting a master key against the untrusted cap.