fix ubsan

This commit is contained in:
Peter Chen
2026-06-22 17:49:48 -07:00
parent d87102f061
commit d6e5aff363
2 changed files with 21 additions and 11 deletions

View File

@@ -15,6 +15,9 @@
#include <secp256k1_mpt.h>
#include <cstdint>
#include <limits>
namespace xrpl {
/**
@@ -61,10 +64,12 @@ struct EcPair
inline void
incrementConfidentialVersion(STObject& mptoken)
{
// Retrieve current version and increment.
// Unsigned integer overflow is defined behavior in C++ (wraps to 0),
// which is acceptable here.
mptoken[sfConfidentialBalanceVersion] = mptoken[~sfConfidentialBalanceVersion].valueOr(0u) + 1u;
// Retrieve current version and increment, wrapping back to 0 at UINT32_MAX.
// The wrap is computed explicitly rather than relying on unsigned overflow
// of `+ 1u`, as it trips the unsigned-integer-overflow sanitizer in the UBSan CI build.
auto const current = mptoken[~sfConfidentialBalanceVersion].valueOr(0u);
mptoken[sfConfidentialBalanceVersion] =
current == std::numeric_limits<std::uint32_t>::max() ? 0u : current + 1u;
}
/**

View File

@@ -506,10 +506,16 @@ ValidConfidentialMPToken::visitEntry(
return beast::kZero;
};
// Clamp to the cap (== INT64_MAX) before the signed conversion. We do INT64_MAX + 1
// which the invariant tests inject, which would result in undefined behavior (under UBSan).
auto const toSignedAmount = [](std::uint64_t v) -> std::int64_t {
return static_cast<std::int64_t>(std::min(v, kMaxMpTokenAmount));
};
if (before && before->getType() == ltMPTOKEN)
{
uint192 const id = getMptID(before);
changes_[id].mptAmountDelta -= static_cast<std::int64_t>(before->getFieldU64(sfMPTAmount));
changes_[id].mptAmountDelta -= toSignedAmount(before->getFieldU64(sfMPTAmount));
// Cannot delete MPToken with non-zero confidential state or non-zero public amount
if (isDelete)
@@ -527,7 +533,7 @@ ValidConfidentialMPToken::visitEntry(
if (after && after->getType() == ltMPTOKEN)
{
uint192 const id = getMptID(after);
changes_[id].mptAmountDelta += static_cast<std::int64_t>(after->getFieldU64(sfMPTAmount));
changes_[id].mptAmountDelta += toSignedAmount(after->getFieldU64(sfMPTAmount));
// Encrypted field existence consistency
bool const hasIssuerBalance = after->isFieldPresent(sfIssuerEncryptedBalance);
@@ -565,10 +571,9 @@ ValidConfidentialMPToken::visitEntry(
if (before->isFieldPresent(sfConfidentialOutstandingAmount))
{
changes_[id].coaDelta -=
static_cast<std::int64_t>(before->getFieldU64(sfConfidentialOutstandingAmount));
toSignedAmount(before->getFieldU64(sfConfidentialOutstandingAmount));
}
changes_[id].outstandingDelta -=
static_cast<std::int64_t>(before->getFieldU64(sfOutstandingAmount));
changes_[id].outstandingDelta -= toSignedAmount(before->getFieldU64(sfOutstandingAmount));
}
if (after && after->getType() == ltMPTOKEN_ISSUANCE)
@@ -581,9 +586,9 @@ ValidConfidentialMPToken::visitEntry(
std::uint64_t const oa = after->getFieldU64(sfOutstandingAmount);
if (hasCOA)
change.coaDelta += static_cast<std::int64_t>(coa);
change.coaDelta += toSignedAmount(coa);
change.outstandingDelta += static_cast<std::int64_t>(oa);
change.outstandingDelta += toSignedAmount(oa);
change.issuance = after;
// COA <= OutstandingAmount