mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-21 14:20:56 +00:00
fix: Reject oversized validator manifest before decoding
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/UnorderedContainers.h>
|
||||
#include <xrpl/basics/base64.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/SecretKey.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <shared_mutex>
|
||||
#include <string>
|
||||
@@ -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<Manifest>
|
||||
deserializeManifest(Slice s, beast::Journal journal);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user