diff --git a/libraries/chain/qc.cpp b/libraries/chain/qc.cpp index a3d5da7b62..390b647ae6 100644 --- a/libraries/chain/qc.cpp +++ b/libraries/chain/qc.cpp @@ -71,11 +71,15 @@ void qc_t::verify_basic(const finalizer_policies_t& policies) const { EOS_ASSERT(policies.pending_finalizer_policy, invalid_qc, "qc ${bn} contains pending policy signature for nonexistent pending finalizer policy", ("bn", block_num)); - // verify that every finalizer included in both policies voted the same - verify_dual_finalizers_votes(policies, active_policy_sig, *pending_policy_sig, block_num); - + // Validate the pending signature's vote format and weights BEFORE comparing dual finalizers. + // verify_dual_finalizers_votes() -> vote_same_at() indexes the pending vote bitset, so its + // size must be validated first; otherwise a malformed pending bitset could reach unchecked + // indexing instead of being rejected here. pending_policy_sig->verify_vote_format(policies.pending_finalizer_policy); pending_policy_sig->verify_weights(policies.pending_finalizer_policy); + + // verify that every finalizer included in both policies voted the same + verify_dual_finalizers_votes(policies, active_policy_sig, *pending_policy_sig, block_num); } else { EOS_ASSERT(!policies.pending_finalizer_policy, invalid_qc, "qc ${bn} does not contain pending policy signature for pending finalizer policy", ("bn", block_num)); @@ -84,8 +88,13 @@ void qc_t::verify_basic(const finalizer_policies_t& policies) const { // returns true iff the other and I voted in the same way. bool qc_sig_t::vote_same_at(const qc_sig_t& other, uint32_t my_vote_index, uint32_t other_vote_index) const { - assert(!strong_votes || my_vote_index < strong_votes->size()); - assert(!weak_votes || my_vote_index < weak_votes->size()); + // Runtime bounds checks for BOTH signatures' vote indices. The previous assert()s are compiled + // out under NDEBUG and only guarded `my_vote_index`; validate `other`'s index too so a + // malformed/undersized bitset is rejected rather than reaching unchecked dynamic_bitset access. + EOS_ASSERT(!strong_votes || my_vote_index < strong_votes->size(), invalid_qc, "qc active strong_votes index out of range"); + EOS_ASSERT(!weak_votes || my_vote_index < weak_votes->size(), invalid_qc, "qc active weak_votes index out of range"); + EOS_ASSERT(!other.strong_votes || other_vote_index < other.strong_votes->size(), invalid_qc, "qc pending strong_votes index out of range"); + EOS_ASSERT(!other.weak_votes || other_vote_index < other.weak_votes->size(), invalid_qc, "qc pending weak_votes index out of range"); // We have already verified the same index has not voted both strong // and weak for a given qc_sig_t (I or other). diff --git a/libraries/chain/webassembly/crypto.cpp b/libraries/chain/webassembly/crypto.cpp index 4b1fef0028..55ad6f371f 100644 --- a/libraries/chain/webassembly/crypto.cpp +++ b/libraries/chain/webassembly/crypto.cpp @@ -285,7 +285,10 @@ namespace eosio::chain::webassembly { } int32_t interface::bls_g1_weighted_sum(span points, span scalars, const uint32_t n, span result) const { - if(n == 0 || points.size() != n*96 || scalars.size() != n*32 || result.size() != 96) + // Compute the expected span lengths in 64-bit. `n` is uint32 and the products were previously + // computed in 32-bit, which can overflow for large `n` and let malformed inputs pass this + // length check before allocation. 64-bit math keeps the comparison exact. + if(n == 0 || points.size() != static_cast(n)*96u || scalars.size() != static_cast(n)*32u || result.size() != 96) return return_code::failure; // Use much efficient scale for the special case of n == 1. @@ -320,7 +323,8 @@ namespace eosio::chain::webassembly { } int32_t interface::bls_g2_weighted_sum(span points, span scalars, const uint32_t n, span result) const { - if(n == 0 || points.size() != n*192 || scalars.size() != n*32 || result.size() != 192) + // 64-bit length math to avoid the 32-bit n*192 / n*32 wraparound (see bls_g1_weighted_sum). + if(n == 0 || points.size() != static_cast(n)*192u || scalars.size() != static_cast(n)*32u || result.size() != 192) return return_code::failure; // Use much efficient scale for the special case of n == 1. @@ -355,7 +359,8 @@ namespace eosio::chain::webassembly { } int32_t interface::bls_pairing(span g1_points, span g2_points, const uint32_t n, span result) const { - if(n == 0 || g1_points.size() != n*96 || g2_points.size() != n*192 || result.size() != 576) + // 64-bit length math to avoid the 32-bit n*96 / n*192 wraparound (see bls_g1_weighted_sum). + if(n == 0 || g1_points.size() != static_cast(n)*96u || g2_points.size() != static_cast(n)*192u || result.size() != 576) return return_code::failure; std::vector> v; v.reserve(n);