diff --git a/include/xrpl/basics/base64.h b/include/xrpl/basics/base64.h index ed30e40a36..6b4cd26604 100644 --- a/include/xrpl/basics/base64.h +++ b/include/xrpl/basics/base64.h @@ -39,6 +39,35 @@ namespace xrpl { +namespace base64 { + +/** + * Returns the maximum number of characters needed to base64-encode @p nBytes bytes. + * + * @param nBytes Number of input bytes. + * @return Size of the encoded string, including padding. + */ +constexpr std::size_t +encodedSize(std::size_t const nBytes) +{ + return 4 * ((nBytes + 2) / 3); +} + +/** + * Returns the maximum number of bytes a base64 string of @p nChars characters + * decodes to. + * + * @param nChars Number of base64 characters. + * @return Upper bound on the number of decoded bytes. + */ +constexpr std::size_t +decodedSize(std::size_t const nChars) +{ + return ((nChars / 4) * 3) + 2; +} + +} // namespace base64 + std::string base64Encode(std::uint8_t const* data, std::size_t len); diff --git a/include/xrpl/server/Manifest.h b/include/xrpl/server/Manifest.h index 70fc8f6f8f..fe5f1db985 100644 --- a/include/xrpl/server/Manifest.h +++ b/include/xrpl/server/Manifest.h @@ -1,10 +1,16 @@ #pragma once #include +#include +#include #include #include #include +#include +#include +#include +#include #include #include #include @@ -135,15 +141,47 @@ struct Manifest std::string to_string(Manifest const& m); -/** Constructs Manifest from serialized string +/** + *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 + * sfVersion (U16) 2 0 2 4 + * sfSequence (U32) 1 0 4 5 + * sfPublicKey (33) 1 1 33 35 + * sfSigningPubKey (33) 1 1 33 35 + * sfSignature (72) 1 1 72 74 + * sfMasterSignature (72) 2 1 72 75 + * sfDomain (128) 1 1 128 130 + * ----- + * 358 + */ +constexpr std::size_t kMaxManifestBytes = 358; - @param s Serialized manifest string +/** + * Largest a valid manifest can be, in base64 characters. + * + * base64 encodes 3 bytes as 4 characters, so this is the encoded form of + * @ref kMaxManifestBytes. Callers that receive a base64 manifest should + * reject anything longer than this before decoding, to avoid allocating + * memory for an oversized input. + */ +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. -*/ +/** + * Constructs Manifest from serialized string + * + * @param s Serialized manifest string + * + * @return `std::nullopt` if string is invalid + * + * @note This does not verify manifest signatures. + * `Manifest::verify` should be called after constructing manifest. + */ /** @{ */ std::optional deserializeManifest(Slice s, beast::Journal journal); diff --git a/src/libxrpl/basics/base64.cpp b/src/libxrpl/basics/base64.cpp index 541ddd0839..7772752f40 100644 --- a/src/libxrpl/basics/base64.cpp +++ b/src/libxrpl/basics/base64.cpp @@ -76,20 +76,6 @@ getInverse() return &kTab[0]; } -/// Returns max chars needed to encode a base64 string -constexpr std::size_t -encodedSize(std::size_t n) -{ - return 4 * ((n + 2) / 3); -} - -/// Returns max bytes needed to decode a base64 string -constexpr std::size_t -decodedSize(std::size_t n) -{ - return ((n / 4) * 3) + 2; -} - /** Encode a series of octets as a padded, base64 string. The resulting string will not be null terminated. diff --git a/src/libxrpl/server/Manifest.cpp b/src/libxrpl/server/Manifest.cpp index b18fdadbe2..b34955dc28 100644 --- a/src/libxrpl/server/Manifest.cpp +++ b/src/libxrpl/server/Manifest.cpp @@ -62,6 +62,11 @@ deserializeManifest(Slice s, beast::Journal journal) if (s.empty()) return std::nullopt; + // A valid manifest has a fixed maximum size, so reject anything larger + // before parsing it. + if (s.size() > kMaxManifestBytes) + return std::nullopt; + static SOTemplate const kManifestFormat{ // A manifest must include: // - the master public key diff --git a/src/xrpld/app/misc/detail/ValidatorList.cpp b/src/xrpld/app/misc/detail/ValidatorList.cpp index ce07e0ec77..5c95eb553c 100644 --- a/src/xrpld/app/misc/detail/ValidatorList.cpp +++ b/src/xrpld/app/misc/detail/ValidatorList.cpp @@ -1130,6 +1130,15 @@ ValidatorList::applyList( json::Value list; auto const& manifest = localManifest ? *localManifest : globalManifest; + // Reject an oversized manifest before decoding it, so we do not allocate + // memory for an input that cannot be a valid manifest. deserializeManifest + // also enforces the decoded-byte limit, but checking here avoids the + // base64 decode entirely. + if (manifest.size() > kMaxManifestBase64) + { + JLOG(j_.warn()) << "UNL manifest exceeds maximum size"; + return PublisherListStats{ListDisposition::Invalid}; + } auto m = deserializeManifest(base64Decode(manifest)); if (!m) {