From 749353ec055f5751c0e906b6c090fdc74ce4f52c Mon Sep 17 00:00:00 2001 From: Areg Hayrapetian Date: Fri, 5 Aug 2022 03:09:58 -0700 Subject: [PATCH 1/3] Use more flexible subjective limits on mod_exp. A new approach to constraining the bit sizes of the arguments passed into mod_exp is taken which allows for larger bit sizes for the base and modulus arguments when the exponent is small. This now allows for mod_exp to be used for 4096-bit RSA. --- libraries/chain/webassembly/crypto.cpp | 26 +++++++++-- libraries/fc | 2 +- unittests/crypto_primitives_tests.cpp | 63 ++++++++++++++++++++------ 3 files changed, 73 insertions(+), 18 deletions(-) diff --git a/libraries/chain/webassembly/crypto.cpp b/libraries/chain/webassembly/crypto.cpp index f18b326f3..fae7e70e7 100644 --- a/libraries/chain/webassembly/crypto.cpp +++ b/libraries/chain/webassembly/crypto.cpp @@ -8,6 +8,16 @@ #include #include +namespace { + uint32_t ceil_log2(uint32_t n) + { + if (n <= 1) { + return 0; + } + return 32 - __builtin_clz(n - 1); + }; +} + namespace eosio { namespace chain { namespace webassembly { void interface::assert_recover_key( legacy_ptr digest, @@ -159,10 +169,20 @@ namespace eosio { namespace chain { namespace webassembly { span modulus, span out) const { if (context.control.is_producing_block()) { - static constexpr int byte_size_limit = 256; // Allow up to 2048-bit values for base, exp, and modulus. + unsigned int base_modulus_size = std::max(base.size(), modulus.size()); + + if (base_modulus_size < exp.size()) { + EOS_THROW(subjective_block_production_exception, + "mod_exp restriction: exponent bit size cannot exceed bit size of either base or modulus"); + } + + static constexpr uint64_t bit_calc_limit = 101; + + uint64_t bit_calc = 5 * ceil_log2(exp.size()) + 8 * ceil_log2(base_modulus_size); - if (std::max(std::max(base.size(), exp.size()), modulus.size()) > byte_size_limit) { - EOS_THROW(subjective_block_production_exception, "Bit size too large for values passed into mod_exp"); + if (bit_calc_limit < bit_calc) { + EOS_THROW(subjective_block_production_exception, + "mod_exp restriction: bit size too large for input arguments"); } } diff --git a/libraries/fc b/libraries/fc index 12898316a..159336e85 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit 12898316ae323deb4a928614d1b4fb456853453a +Subproject commit 159336e85dd4770eda601d5192df08bc061e34c1 diff --git a/unittests/crypto_primitives_tests.cpp b/unittests/crypto_primitives_tests.cpp index ccf933f6f..3b0a4528a 100644 --- a/unittests/crypto_primitives_tests.cpp +++ b/unittests/crypto_primitives_tests.cpp @@ -380,7 +380,7 @@ BOOST_AUTO_TEST_CASE( modexp_test ) { try { { { "01", - "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", + "ff", "", }, return_code::failure, @@ -415,9 +415,12 @@ BOOST_AUTO_TEST_CASE( modexp_subjective_limit_test ) { try { // Given the need to respect the deadline timer and the current limitation that the deadline timer is not plumbed into the // inner loops of the implementation of mod_exp (which currently exists in the gmp shared library), only a small enough duration for // mod_exp can be tolerated to avoid going over the deadline timer by too much. A good threshold for small may be less than 5 ms. - // Then based on benchmarks within the test_modular_arithmetic test within fc, it seems safe to limit the bit size to 2048 bits. + // Based on benchmarks within the test_modular_arithmetic test within fc, the following constraints are subjectively enforced on the + // base, exp, and modulus input arguments of the mod_exp host function: + // 1. exp.size() <= std::max(base.size(), modulus.size()) + // 2. 5 * ceil(log2(exp.size())) + 8 * ceil(log2(std::max(base.size(), modulus.size()))) <= 101 - // This test case verifies that the subjective bit size limit for mod_exp is properly enforced within libchain. + // This test case verifies that the above constraints on mod_exp are subjectively enforced properly within libchain. // To allow mod_exp to be more useful, the limits on bit size need to be removed and the deadline timer plumbing into the implementation // needs to occur. When that happens, this test case can be removed. @@ -439,26 +442,58 @@ BOOST_AUTO_TEST_CASE( modexp_subjective_limit_test ) { try { c.set_abi( tester1_account, contracts::crypto_primitives_test_abi().data() ); c.produce_block(); - bytes exponent(256); // 2048 bits of all zeros is fine + auto exponent = h2bin("010001"); + + BOOST_CHECK_EXCEPTION(c.push_action(tester1_account, "testmodexp"_n, tester1_account, mutable_variant_object() + ("base", h2bin("01")) + ("exp", exponent) + ("modulo", h2bin("0F")) + ("expected_error", static_cast(return_code::success)) + ("expected_result", h2bin("01"))), + eosio::chain::subjective_block_production_exception, + fc_exception_message_is("mod_exp restriction: exponent bit size cannot exceed bit size of either base or modulus") + ); + + std::vector modulus(2048 - 1); + std::vector expected_result(modulus.size()); + modulus.push_back(0x0F); + expected_result.push_back(0x01); + + auto ceil_log2 = [](uint32_t n) -> uint32_t { + if (n <= 1) { + return 0; + } + return 32 - __builtin_clz(n - 1); + }; + + BOOST_CHECK(5 * ceil_log2(exponent.size()) + 8 * ceil_log2(modulus.size()) == 98); c.push_action( tester1_account, "testmodexp"_n, tester1_account, mutable_variant_object() ("base", h2bin("01")) ("exp", exponent) - ("modulo", h2bin("0F")) + ("modulo", modulus) ("expected_error", static_cast(return_code::success)) - ("expected_result", h2bin("01")) + ("expected_result", expected_result) ); - exponent.push_back(0); // But 2056 bits of all zeros crosses the subjective limit (even if the value is still technically only zero). + modulus.pop_back(); + expected_result.pop_back(); + + modulus.resize(2048); + expected_result.resize(modulus.size()); + modulus.push_back(0x0F); + expected_result.push_back(0x01); + + BOOST_CHECK(5 * ceil_log2(exponent.size()) + 8 * ceil_log2(modulus.size()) == 106); - BOOST_CHECK_EXCEPTION(c.push_action( tester1_account, "testmodexp"_n, tester1_account, mutable_variant_object() - ("base", h2bin("01")) - ("exp", exponent) - ("modulo", h2bin("0F")) - ("expected_error", static_cast(return_code::success)) - ("expected_result", h2bin("01"))), + BOOST_CHECK_EXCEPTION(c.push_action(tester1_account, "testmodexp"_n, tester1_account, mutable_variant_object() + ("base", h2bin("01")) + ("exp", exponent) + ("modulo", modulus) + ("expected_error", static_cast(return_code::success)) + ("expected_result", expected_result)), eosio::chain::subjective_block_production_exception, - fc_exception_message_is("Bit size too large for values passed into mod_exp") + fc_exception_message_is("mod_exp restriction: bit size too large for input arguments") ); } FC_LOG_AND_RETHROW() } From 4c353f6b24dffaede2b3c1c416930e44e9873041 Mon Sep 17 00:00:00 2001 From: Areg Hayrapetian Date: Fri, 5 Aug 2022 10:10:21 -0700 Subject: [PATCH 2/3] Increase subjective limit for mod_exp by a little bit. --- libraries/chain/webassembly/crypto.cpp | 2 +- libraries/fc | 2 +- unittests/crypto_primitives_tests.cpp | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libraries/chain/webassembly/crypto.cpp b/libraries/chain/webassembly/crypto.cpp index fae7e70e7..62f44392a 100644 --- a/libraries/chain/webassembly/crypto.cpp +++ b/libraries/chain/webassembly/crypto.cpp @@ -176,7 +176,7 @@ namespace eosio { namespace chain { namespace webassembly { "mod_exp restriction: exponent bit size cannot exceed bit size of either base or modulus"); } - static constexpr uint64_t bit_calc_limit = 101; + static constexpr uint64_t bit_calc_limit = 106; uint64_t bit_calc = 5 * ceil_log2(exp.size()) + 8 * ceil_log2(base_modulus_size); diff --git a/libraries/fc b/libraries/fc index 159336e85..18473a4ee 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit 159336e85dd4770eda601d5192df08bc061e34c1 +Subproject commit 18473a4ee232f4a3f693cef322006dca80616a48 diff --git a/unittests/crypto_primitives_tests.cpp b/unittests/crypto_primitives_tests.cpp index 3b0a4528a..148764a68 100644 --- a/unittests/crypto_primitives_tests.cpp +++ b/unittests/crypto_primitives_tests.cpp @@ -454,7 +454,7 @@ BOOST_AUTO_TEST_CASE( modexp_subjective_limit_test ) { try { fc_exception_message_is("mod_exp restriction: exponent bit size cannot exceed bit size of either base or modulus") ); - std::vector modulus(2048 - 1); + std::vector modulus(4096 - 1); std::vector expected_result(modulus.size()); modulus.push_back(0x0F); expected_result.push_back(0x01); @@ -466,7 +466,7 @@ BOOST_AUTO_TEST_CASE( modexp_subjective_limit_test ) { try { return 32 - __builtin_clz(n - 1); }; - BOOST_CHECK(5 * ceil_log2(exponent.size()) + 8 * ceil_log2(modulus.size()) == 98); + BOOST_CHECK(5 * ceil_log2(exponent.size()) + 8 * ceil_log2(modulus.size()) == 106); c.push_action( tester1_account, "testmodexp"_n, tester1_account, mutable_variant_object() ("base", h2bin("01")) @@ -479,12 +479,12 @@ BOOST_AUTO_TEST_CASE( modexp_subjective_limit_test ) { try { modulus.pop_back(); expected_result.pop_back(); - modulus.resize(2048); + modulus.resize(4096); expected_result.resize(modulus.size()); modulus.push_back(0x0F); expected_result.push_back(0x01); - BOOST_CHECK(5 * ceil_log2(exponent.size()) + 8 * ceil_log2(modulus.size()) == 106); + BOOST_CHECK(5 * ceil_log2(exponent.size()) + 8 * ceil_log2(modulus.size()) == 114); BOOST_CHECK_EXCEPTION(c.push_action(tester1_account, "testmodexp"_n, tester1_account, mutable_variant_object() ("base", h2bin("01")) From 5370a21ff1717a961f56f4072c4ed08e8343ed2b Mon Sep 17 00:00:00 2001 From: Areg Hayrapetian Date: Fri, 5 Aug 2022 10:18:55 -0700 Subject: [PATCH 3/3] Update fc submodule. --- libraries/fc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/fc b/libraries/fc index 18473a4ee..c5f5d1876 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit 18473a4ee232f4a3f693cef322006dca80616a48 +Subproject commit c5f5d1876ca1c722769990f49827f34b39e4a6bc