diff --git a/src/libxrpl/ledger/helpers/PermissionedDEXHelpers.cpp b/src/libxrpl/ledger/helpers/PermissionedDEXHelpers.cpp index 0151c5b2df..4f2b15a08c 100644 --- a/src/libxrpl/ledger/helpers/PermissionedDEXHelpers.cpp +++ b/src/libxrpl/ledger/helpers/PermissionedDEXHelpers.cpp @@ -19,6 +19,14 @@ namespace xrpl::permissioned_dex { bool accountInDomain(ReadView const& view, AccountID const& account, Domain const& domainID) { + // A zero domainID is malformed: it would build a keylet with a zero key + // and violate Ledger::read's invariant. Defense in depth: callers should + // reject this at preflight, but guard here too so any future caller and + // the order-book sweep path (offerInDomain -> here) cannot trip the + // invariant. + if (domainID.isZero()) + return false; + auto const sleDomain = view.read(keylet::permissionedDomain(domainID)); if (!sleDomain) return false; diff --git a/src/libxrpl/tx/transactors/dex/OfferCreate.cpp b/src/libxrpl/tx/transactors/dex/OfferCreate.cpp index 8bf69d25c0..a55dcf9ae3 100644 --- a/src/libxrpl/tx/transactors/dex/OfferCreate.cpp +++ b/src/libxrpl/tx/transactors/dex/OfferCreate.cpp @@ -94,6 +94,16 @@ OfferCreate::preflight(PreflightContext const& ctx) if (tx.isFlag(tfHybrid) && !tx.isFieldPresent(sfDomainID)) return temINVALID_FLAG; + // A present but zero DomainID is malformed: it would build a keylet with + // a zero key and violate Ledger::read's invariant. Reject at preflight + // (temMALFORMED) instead of letting it slip to preclaim and be + // misclassified as tecNO_PERMISSION. + if (tx.isFieldPresent(sfDomainID) && tx.getFieldH256(sfDomainID).isZero()) + { + JLOG(j.debug()) << "Malformed offer: zero DomainID"; + return temMALFORMED; + } + bool const bImmediateOrCancel(tx.isFlag(tfImmediateOrCancel)); bool const bFillOrKill(tx.isFlag(tfFillOrKill)); diff --git a/src/libxrpl/tx/transactors/payment/Payment.cpp b/src/libxrpl/tx/transactors/payment/Payment.cpp index 1848d34786..1bc33bf20c 100644 --- a/src/libxrpl/tx/transactors/payment/Payment.cpp +++ b/src/libxrpl/tx/transactors/payment/Payment.cpp @@ -125,6 +125,16 @@ Payment::preflight(PreflightContext const& ctx) if (!mpTokensV2 && isDstMPT && ctx.tx.isFieldPresent(sfPaths)) return temMALFORMED; + // A present but zero DomainID is malformed: it would build a keylet with + // a zero key and violate Ledger::read's invariant. Reject at preflight + // (temMALFORMED) instead of letting it slip to preclaim and be + // misclassified as tecNO_PERMISSION. + if (tx.isFieldPresent(sfDomainID) && tx.getFieldH256(sfDomainID).isZero()) + { + JLOG(j.debug()) << "Malformed payment: zero DomainID"; + return temMALFORMED; + } + bool const partialPaymentAllowed = tx.isFlag(tfPartialPayment); bool const limitQuality = tx.isFlag(tfLimitQuality); bool const defaultPathsAllowed = !tx.isFlag(tfNoRippleDirect);