diff --git a/CMakeLists.txt b/CMakeLists.txt index 1451955870c..0309bb0b5e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,7 +35,7 @@ set( CXX_STANDARD_REQUIRED ON) set(VERSION_MAJOR 1) set(VERSION_MINOR 0) -set(VERSION_PATCH 2) +set(VERSION_PATCH 3) set( CLI_CLIENT_EXECUTABLE_NAME cleos ) set( NODE_EXECUTABLE_NAME nodeos ) diff --git a/Docker/README.md b/Docker/README.md index 9ff75404b7d..ffdfe07e2c5 100644 --- a/Docker/README.md +++ b/Docker/README.md @@ -20,10 +20,10 @@ cd bos/Docker docker build . -t boscore/bos ``` -The above will build off the most recent commit to the master branch by default. If you would like to target a specific branch/tag, you may use a build argument. For example, if you wished to generate a docker image based off of the v1.0.2 tag, you could do the following: +The above will build off the most recent commit to the master branch by default. If you would like to target a specific branch/tag, you may use a build argument. For example, if you wished to generate a docker image based off of the v1.0.3 tag, you could do the following: ```bash -docker build -t boscore/bos:v1.0.2 --build-arg branch=v1.0.2 . +docker build -t boscore/bos:v1.0.3 --build-arg branch=v1.0.3 . ``` By default, the symbol in eosio.system is set to SYS. You can override this using the symbol argument while building the docker image. diff --git a/README.md b/README.md index 43fcdfa2d61..eecfaf7ec1c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# BOSCore - Born for DApp, be more useable. +# BOSCore - Born for DApps. Born for Usability. -## BOSCore Version: v1.0.2 +## BOSCore Version: v1.0.3 ### Basic EOSIO Version: v1.4.4 # Background diff --git a/README_CN.md b/README_CN.md index d4701484fb9..a1f55247bcf 100644 --- a/README_CN.md +++ b/README_CN.md @@ -1,6 +1,6 @@ # BOSCore - 更可用的链,为DApp而生。 -## BOSCore Version: v1.0.2 +## BOSCore Version: v1.0.3 ### Basic EOSIO Version: v1.4.4 # 背景 diff --git a/libraries/chain/apply_context.cpp b/libraries/chain/apply_context.cpp index 07ab384eeff..4e9807ed85c 100644 --- a/libraries/chain/apply_context.cpp +++ b/libraries/chain/apply_context.cpp @@ -209,6 +209,15 @@ void apply_context::execute_inline( action&& a ) { EOS_ASSERT( code != nullptr, action_validate_exception, "inline action's code account ${account} does not exist", ("account", a.account) ); + bool disallow_send_to_self_bypass = false; // eventually set to whether the appropriate protocol feature has been activated + bool send_to_self = (a.account == receiver); + bool inherit_parent_authorizations = (!disallow_send_to_self_bypass && send_to_self && (receiver == act.account) && control.is_producing_block()); + + flat_set inherited_authorizations; + if( inherit_parent_authorizations ) { + inherited_authorizations.reserve( a.authorization.size() ); + } + for( const auto& auth : a.authorization ) { auto* actor = control.db().find(auth.actor); EOS_ASSERT( actor != nullptr, action_validate_exception, @@ -216,22 +225,45 @@ void apply_context::execute_inline( action&& a ) { EOS_ASSERT( control.get_authorization_manager().find_permission(auth) != nullptr, action_validate_exception, "inline action's authorizations include a non-existent permission: ${permission}", ("permission", auth) ); + + if( inherit_parent_authorizations && std::find(act.authorization.begin(), act.authorization.end(), auth) != act.authorization.end() ) { + inherited_authorizations.insert( auth ); + } } - // No need to check authorization if: replaying irreversible blocks; contract is privileged; or, contract is calling itself. - if( !control.skip_auth_check() && !privileged && a.account != receiver ) { - control.get_authorization_manager() - .check_authorization( {a}, - {}, - {{receiver, config::eosio_code_name}}, - control.pending_block_time() - trx_context.published, - std::bind(&transaction_context::checktime, &this->trx_context), - false - ); - - //QUESTION: Is it smart to allow a deferred transaction that has been delayed for some time to get away - // with sending an inline action that requires a delay even though the decision to send that inline - // action was made at the moment the deferred transaction was executed with potentially no forewarning? + // No need to check authorization if replaying irreversible blocks or contract is privileged + if( !control.skip_auth_check() && !privileged ) { + try { + control.get_authorization_manager() + .check_authorization( {a}, + {}, + {{receiver, config::eosio_code_name}}, + control.pending_block_time() - trx_context.published, + std::bind(&transaction_context::checktime, &this->trx_context), + false, + inherited_authorizations + ); + + //QUESTION: Is it smart to allow a deferred transaction that has been delayed for some time to get away + // with sending an inline action that requires a delay even though the decision to send that inline + // action was made at the moment the deferred transaction was executed with potentially no forewarning? + } catch( const fc::exception& e ) { + if( disallow_send_to_self_bypass || !send_to_self ) { + throw; + } else if( control.is_producing_block() ) { + subjective_block_production_exception new_exception(FC_LOG_MESSAGE( error, "Authorization failure with inline action sent to self")); + for (const auto& log: e.get_log()) { + new_exception.append_log(log); + } + throw new_exception; + } + } catch( ... ) { + if( disallow_send_to_self_bypass || !send_to_self ) { + throw; + } else if( control.is_producing_block() ) { + EOS_THROW(subjective_block_production_exception, "Unexpected exception occurred validating inline action sent to self"); + } + } } _inline_actions.emplace_back( move(a) ); @@ -268,16 +300,30 @@ void apply_context::schedule_deferred_transaction( const uint128_t& sender_id, a require_authorization(payer); /// uses payer's storage } - // if a contract is deferring only actions to itself then there is no need - // to check permissions, it could have done everything anyway. - bool check_auth = false; - for( const auto& act : trx.actions ) { - if( act.account != receiver ) { - check_auth = true; - break; + // Originally this code bypassed authorization checks if a contract was deferring only actions to itself. + // The idea was that the code could already do whatever the deferred transaction could do, so there was no point in checking authorizations. + // But this is not true. The original implementation didn't validate the authorizations on the actions which allowed for privilege escalation. + // It would make it possible to bill RAM to some unrelated account. + // Furthermore, even if the authorizations were forced to be a subset of the current action's authorizations, it would still violate the expectations + // of the signers of the original transaction, because the deferred transaction would allow billing more CPU and network bandwidth than the maximum limit + // specified on the original transaction. + // So, the deferred transaction must always go through the authorization checking if it is not sent by a privileged contract. + // However, the old logic must still be considered because it cannot objectively change until a consensus protocol upgrade. + + bool disallow_send_to_self_bypass = false; // eventually set to whether the appropriate protocol feature has been activated + + auto is_sending_only_to_self = [&trx]( const account_name& self ) { + bool send_to_self = true; + for( const auto& act : trx.actions ) { + if( act.account != self ) { + send_to_self = false; + break; + } } - } - if( check_auth ) { + return send_to_self; + }; + + try { control.get_authorization_manager() .check_authorization( trx.actions, {}, @@ -286,6 +332,22 @@ void apply_context::schedule_deferred_transaction( const uint128_t& sender_id, a std::bind(&transaction_context::checktime, &this->trx_context), false ); + } catch( const fc::exception& e ) { + if( disallow_send_to_self_bypass || !is_sending_only_to_self(receiver) ) { + throw; + } else if( control.is_producing_block() ) { + subjective_block_production_exception new_exception(FC_LOG_MESSAGE( error, "Authorization failure with sent deferred transaction consisting only of actions to self")); + for (const auto& log: e.get_log()) { + new_exception.append_log(log); + } + throw new_exception; + } + } catch( ... ) { + if( disallow_send_to_self_bypass || !is_sending_only_to_self(receiver) ) { + throw; + } else if( control.is_producing_block() ) { + EOS_THROW(subjective_block_production_exception, "Unexpected exception occurred validating sent deferred transaction consisting only of actions to self"); + } } } diff --git a/libraries/chain/authorization_manager.cpp b/libraries/chain/authorization_manager.cpp index 832f69c71cd..6725468cf97 100644 --- a/libraries/chain/authorization_manager.cpp +++ b/libraries/chain/authorization_manager.cpp @@ -431,7 +431,8 @@ namespace eosio { namespace chain { const flat_set& provided_permissions, fc::microseconds provided_delay, const std::function& _checktime, - bool allow_unused_keys + bool allow_unused_keys, + const flat_set& satisfied_authorizations )const { const auto& checktime = ( static_cast(_checktime) ? _checktime : _noop_checktime ); @@ -488,9 +489,11 @@ namespace eosio { namespace chain { } } - auto res = permissions_to_satisfy.emplace( declared_auth, delay ); - if( !res.second && res.first->second > delay) { // if the declared_auth was already in the map and with a higher delay - res.first->second = delay; + if( satisfied_authorizations.find( declared_auth ) == satisfied_authorizations.end() ) { + auto res = permissions_to_satisfy.emplace( declared_auth, delay ); + if( !res.second && res.first->second > delay) { // if the declared_auth was already in the map and with a higher delay + res.first->second = delay; + } } } } diff --git a/libraries/chain/include/eosio/chain/authorization_manager.hpp b/libraries/chain/include/eosio/chain/authorization_manager.hpp index 9a75b5f80b1..a6df7ad2568 100644 --- a/libraries/chain/include/eosio/chain/authorization_manager.hpp +++ b/libraries/chain/include/eosio/chain/authorization_manager.hpp @@ -84,7 +84,8 @@ namespace eosio { namespace chain { const flat_set& provided_permissions = flat_set(), fc::microseconds provided_delay = fc::microseconds(0), const std::function& checktime = std::function(), - bool allow_unused_keys = false + bool allow_unused_keys = false, + const flat_set& satisfied_authorizations = flat_set() )const;