From bfe699082ef93945c8e30ea3e2b7e9b6c8f6d554 Mon Sep 17 00:00:00 2001 From: j-berman Date: Fri, 7 Aug 2026 13:24:53 -0700 Subject: [PATCH] cryptonote_basic: sanity check on key offsets Nice early check on allowed n key offsets per tx based on the guaranteed ceiling for the entire chain. --- .../cryptonote_format_utils.cpp | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 09ec8ff68..ddc97c5fe 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -99,6 +99,27 @@ namespace cryptonote const uint64_t bp_clawback = (bp_base * n_padded_outputs - bp_size) * 4 / 5; return bp_clawback; } + + std::size_t max_total_key_offsets() + { + // This is a 100% guaranteed ceiling for the entire chain + return get_max_tx_size() / sizeof(crypto::public_key); + } + + bool n_key_offsets_exceeds_max(const transaction_prefix& tx) + { + const std::size_t max_allowed = max_total_key_offsets(); + std::size_t total_key_offsets = 0; + for (const auto &vin : tx.vin) + { + if (vin.type() != typeid(cryptonote::txin_to_key)) + continue; + const std::size_t n_key_offsets = boost::get(vin).key_offsets.size(); + CHECK_AND_ASSERT_MES((n_key_offsets + total_key_offsets) >= total_key_offsets, true, "key offsets overflow"); + total_key_offsets += n_key_offsets; + } + return total_key_offsets >= max_allowed; + } //--------------------------------------------------------------- } @@ -203,6 +224,7 @@ namespace cryptonote binary_archive ba{epee::strspan(tx_blob)}; bool r = ::serialization::serialize(ba, tx); CHECK_AND_ASSERT_MES(r, false, "Failed to parse transaction from blob"); + CHECK_AND_ASSERT_MES(!n_key_offsets_exceeds_max(tx), false, "Transaction contains too many ring members"); CHECK_AND_ASSERT_MES(expand_transaction_1(tx, false), false, "Failed to expand transaction data"); tx.invalidate_hashes(); tx.set_blob_size(tx_blob.size()); @@ -214,6 +236,7 @@ namespace cryptonote binary_archive ba{epee::strspan(tx_blob)}; bool r = tx.serialize_base(ba); CHECK_AND_ASSERT_MES(r, false, "Failed to parse transaction from blob"); + CHECK_AND_ASSERT_MES(!n_key_offsets_exceeds_max(tx), false, "Transaction contains too many ring members"); CHECK_AND_ASSERT_MES(expand_transaction_1(tx, true), false, "Failed to expand transaction data"); tx.invalidate_hashes(); return true; @@ -224,6 +247,7 @@ namespace cryptonote binary_archive ba{epee::strspan(tx_blob)}; bool r = ::serialization::serialize_noeof(ba, tx); CHECK_AND_ASSERT_MES(r, false, "Failed to parse transaction prefix from blob"); + CHECK_AND_ASSERT_MES(!n_key_offsets_exceeds_max(tx), false, "Transaction contains too many ring members"); return true; } //--------------------------------------------------------------- @@ -232,6 +256,7 @@ namespace cryptonote binary_archive ba{epee::strspan(tx_blob)}; bool r = ::serialization::serialize(ba, tx); CHECK_AND_ASSERT_MES(r, false, "Failed to parse transaction from blob"); + CHECK_AND_ASSERT_MES(!n_key_offsets_exceeds_max(tx), false, "Transaction contains too many ring members"); CHECK_AND_ASSERT_MES(expand_transaction_1(tx, false), false, "Failed to expand transaction data"); tx.invalidate_hashes(); tx.set_blob_size(tx_blob.size());