From 7ff4a5567a6df129d16e01285f2fe8183ac63808 Mon Sep 17 00:00:00 2001 From: j-berman Date: Thu, 3 Sep 2026 17:10:24 -0700 Subject: [PATCH] cryptonote_basic: serialization checks (cherry picked from commit d869dc0981ac55f90d5b52a1a794b124ae274716) --- src/cryptonote_basic/cryptonote_basic.h | 112 +++++++++++++++++- .../cryptonote_basic_impl.cpp | 11 -- src/cryptonote_basic/cryptonote_basic_impl.h | 2 - .../cryptonote_format_utils.cpp | 3 +- src/cryptonote_core/blockchain.cpp | 2 +- src/cryptonote_core/tx_sanity_check.cpp | 2 +- src/serialization/container.h | 12 +- src/serialization/containers.h | 8 +- src/serialization/json_object.cpp | 2 +- src/serialization/serialization.h | 10 ++ src/simplewallet/simplewallet.cpp | 2 +- src/wallet/wallet2.cpp | 12 +- tests/unit_tests/serialization.cpp | 8 +- 13 files changed, 143 insertions(+), 43 deletions(-) diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index 215903a0c..2c3c14c25 100644 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -53,8 +53,12 @@ namespace cryptonote { - typedef std::vector ring_signature; - + // These are guaranteed ceilings for the entire chain. Some fork rules may limit some further. + constexpr size_t COINBASE_VIN_COUNT = 1; + constexpr size_t MAX_VIN_COUNT = CRYPTONOTE_MAX_TX_SIZE / sizeof(crypto::key_image); + constexpr size_t MAX_NON_COINBASE_VOUT_COUNT = CRYPTONOTE_MAX_TX_SIZE / sizeof(crypto::public_key); + constexpr size_t MAX_COINBASE_VOUT_COUNT = /*LEVIN_DEFAULT_MAX_PACKET_SIZE=*/100000000 / sizeof(crypto::public_key); + constexpr size_t MAX_TOTAL_KEY_OFFSETS = CRYPTONOTE_MAX_TX_SIZE / sizeof(crypto::public_key); /* outputs */ @@ -143,7 +147,7 @@ namespace cryptonote BEGIN_SERIALIZE_OBJECT() VARINT_FIELD(amount) - FIELD(key_offsets) + CONTAINER_FIELD_CAPPED(key_offsets, MAX_TOTAL_KEY_OFFSETS) FIELD(k_image) END_SERIALIZE() }; @@ -184,11 +188,109 @@ namespace cryptonote VARINT_FIELD(version) if(version == 0 || CURRENT_TRANSACTION_VERSION < version) return false; VARINT_FIELD(unlock_time) - FIELD(vin) - FIELD(vout) + if (typename Archive::is_saving()) + { + // Serializing... + FIELD(vin) + FIELD(vout) + } + else + { + // De-serializing... + // vin + if (!this->deserialize_vin(ar)) + { + ar.set_fail(); + return false; + } + + // vout + const size_t MAX_VOUT_COUNT = (this->is_coinbase() || vin.empty()) ? MAX_COINBASE_VOUT_COUNT : MAX_NON_COINBASE_VOUT_COUNT; + CONTAINER_FIELD_CAPPED(vout, MAX_VOUT_COUNT) + } FIELD(extra) END_SERIALIZE() + //----------------------------------------------------------------------- + bool is_coinbase() const + { + if (vin.size() != COINBASE_VIN_COUNT) + return false; + + if (vin.at(0).type() != typeid(txin_gen)) + return false; + + return true; + } + //----------------------------------------------------------------------- + private: + // Do some validation on vin before doing allocations and reading the archive: + // 1. Can't exceed MAX_VIN_COUNT. + // 2. If coinbase, only 1 txin_gen. + // 3. If not coinbase: type txin_to_key (see check_inputs_types_supported). + // 4. If not coinbase: key offsets don't exceed max allowed for whole tx. + // Note: can't enforce non-empty because wallets may store empty vin. + template