diff --git a/src/test/consensus/ExtendedPosition_test.cpp b/src/test/consensus/ExtendedPosition_test.cpp index c5bd3f923..90faf3dc9 100644 --- a/src/test/consensus/ExtendedPosition_test.cpp +++ b/src/test/consensus/ExtendedPosition_test.cpp @@ -522,17 +522,26 @@ class ExtendedPosition_test : public beast::unit_test::suite BEAST_EXPECT(!result.has_value()); } - // Size says extended payload, but no flag byte remains. This accepts - // the legacy hash and ignores the inconsistent advertised size. + // Size says extended payload, but no flag byte remains. { auto const txSet = makeHash("txset-missing-flags"); Serializer s; s.addBitString(txSet); SerialIter sit(s.slice()); auto result = ExtendedPosition::fromSerialIter(sit, 33); - BEAST_EXPECT(result.has_value()); - if (result) - BEAST_EXPECT(result->txSetHash == txSet); + BEAST_EXPECT(!result.has_value()); + } + + // Zero flags are a non-canonical duplicate of the 32-byte legacy form. + { + auto const txSet = makeHash("txset-zero-flags"); + Serializer s; + s.addBitString(txSet); + s.add8(0); + SerialIter sit(s.slice()); + auto result = + ExtendedPosition::fromSerialIter(sit, s.getDataLength()); + BEAST_EXPECT(!result.has_value()); } // Trailing extra bytes after valid fields diff --git a/src/xrpld/app/consensus/RCLCxPeerPos.h b/src/xrpld/app/consensus/RCLCxPeerPos.h index 21d7fab99..7ca0830d8 100644 --- a/src/xrpld/app/consensus/RCLCxPeerPos.h +++ b/src/xrpld/app/consensus/RCLCxPeerPos.h @@ -205,10 +205,14 @@ struct ExtendedPosition // Extended format: flags byte + optional uint256 fields if (sit.empty()) - return pos; + return std::nullopt; std::uint8_t flags = sit.get8(); + // A 32-byte payload is the only canonical no-extension encoding. + if (flags == 0) + return std::nullopt; + // Reject unknown flag bits (reduces wire malleability) if (flags & 0x80) return std::nullopt;