Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

Consolidated Security Fixes for 2.0.10 #10091

Merged
merged 1 commit into from
Feb 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1403,7 +1403,8 @@ struct controller_impl {
transaction_trace_ptr push_transaction( const transaction_metadata_ptr& trx,
fc::time_point deadline,
uint32_t billed_cpu_time_us,
bool explicit_billed_cpu_time )
bool explicit_billed_cpu_time,
uint32_t subjective_cpu_bill_us )
{
EOS_ASSERT(deadline != fc::time_point(), transaction_exception, "deadline cannot be uninitialized");

Expand Down Expand Up @@ -1432,6 +1433,7 @@ struct controller_impl {
trx_context.deadline = deadline;
trx_context.explicit_billed_cpu_time = explicit_billed_cpu_time;
trx_context.billed_cpu_time_us = billed_cpu_time_us;
trx_context.subjective_cpu_bill_us = subjective_cpu_bill_us;
trace = trx_context.trace;
try {
if( trx->implicit ) {
Expand Down Expand Up @@ -1504,6 +1506,7 @@ struct controller_impl {
trace->error_code = controller::convert_exception_to_error_code( e );
trace->except = e;
trace->except_ptr = std::current_exception();
trace->elapsed = fc::time_point::now() - trx_context.start;
}

emit( self.accepted_transaction, trx );
Expand Down Expand Up @@ -1645,7 +1648,7 @@ struct controller_impl {
in_trx_requiring_checks = old_value;
});
in_trx_requiring_checks = true;
push_transaction( onbtrx, fc::time_point::maximum(), self.get_global_properties().configuration.min_transaction_cpu_usage, true );
push_transaction( onbtrx, fc::time_point::maximum(), self.get_global_properties().configuration.min_transaction_cpu_usage, true, 0 );
} catch( const std::bad_alloc& e ) {
elog( "on block transaction failed due to a std::bad_alloc" );
throw;
Expand Down Expand Up @@ -1903,7 +1906,7 @@ struct controller_impl {
: ( !!std::get<0>( trx_metas.at( packed_idx ) ) ?
std::get<0>( trx_metas.at( packed_idx ) )
: std::get<1>( trx_metas.at( packed_idx ) ).get() ) );
trace = push_transaction( trx_meta, fc::time_point::maximum(), receipt.cpu_usage_us, true );
trace = push_transaction( trx_meta, fc::time_point::maximum(), receipt.cpu_usage_us, true, 0 );
++packed_idx;
} else if( receipt.trx.contains<transaction_id_type>() ) {
trace = push_scheduled_transaction( receipt.trx.get<transaction_id_type>(), fc::time_point::maximum(), receipt.cpu_usage_us, true );
Expand Down Expand Up @@ -2688,11 +2691,12 @@ void controller::push_block( std::future<block_state_ptr>& block_state_future,
}

transaction_trace_ptr controller::push_transaction( const transaction_metadata_ptr& trx, fc::time_point deadline,
uint32_t billed_cpu_time_us, bool explicit_billed_cpu_time ) {
uint32_t billed_cpu_time_us, bool explicit_billed_cpu_time,
uint32_t subjective_cpu_bill_us ) {
validate_db_available_size();
EOS_ASSERT( get_read_mode() != db_read_mode::IRREVERSIBLE, transaction_type_exception, "push transaction not allowed in irreversible mode" );
EOS_ASSERT( trx && !trx->implicit && !trx->scheduled, transaction_type_exception, "Implicit/Scheduled transaction not allowed" );
return my->push_transaction(trx, deadline, billed_cpu_time_us, explicit_billed_cpu_time );
return my->push_transaction(trx, deadline, billed_cpu_time_us, explicit_billed_cpu_time, subjective_cpu_bill_us );
}

transaction_trace_ptr controller::push_scheduled_transaction( const transaction_id_type& trxid, fc::time_point deadline,
Expand Down
3 changes: 2 additions & 1 deletion libraries/chain/include/eosio/chain/controller.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,8 @@ namespace eosio { namespace chain {
*
*/
transaction_trace_ptr push_transaction( const transaction_metadata_ptr& trx, fc::time_point deadline,
uint32_t billed_cpu_time_us, bool explicit_billed_cpu_time );
uint32_t billed_cpu_time_us, bool explicit_billed_cpu_time,
uint32_t subjective_cpu_bill_us );

/**
* Attempt to execute a specific transaction in our deferred trx database
Expand Down
2 changes: 2 additions & 0 deletions libraries/chain/include/eosio/chain/resource_limits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ namespace eosio { namespace chain { namespace resource_limits {
bool set_account_limits( const account_name& account, int64_t ram_bytes, int64_t net_weight, int64_t cpu_weight);
void get_account_limits( const account_name& account, int64_t& ram_bytes, int64_t& net_weight, int64_t& cpu_weight) const;

bool is_unlimited_cpu( const account_name& account ) const;

void process_account_limit_updates();
void process_block_usage( uint32_t block_num );

Expand Down
65 changes: 65 additions & 0 deletions libraries/chain/include/eosio/chain/resource_limits_private.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,71 @@ namespace eosio { namespace chain { namespace resource_limits {
}
};

/**
* This class accumulates a value that decays over quantums based on inputs
* The decay is linear between updates and exponential if the set of inputs has no gaps
*
* The value stored is Precision times the sum of the inputs.
*/
template<uint64_t Precision = config::rate_limiting_precision>
struct exponential_decay_accumulator
{
static_assert( Precision > 0, "Precision must be positive" );
static constexpr uint64_t max_raw_value = std::numeric_limits<uint64_t>::max() / Precision;

exponential_decay_accumulator()
: last_ordinal(0)
, value_ex(0)
{
}

uint32_t last_ordinal; ///< The ordinal of the last period which has contributed to the accumulator
uint64_t value_ex; ///< The current accumulated value pre-multiplied by Precision

/**
* return the extended value at a current or future ordinal
*/
uint64_t value_ex_at( uint32_t ordinal, uint32_t window_size ) const {
if( last_ordinal < ordinal ) {
if( (uint64_t)last_ordinal + window_size > (uint64_t)ordinal ) {
const auto delta = ordinal - last_ordinal; // clearly 0 < delta < window_size
const auto decay = make_ratio(
(uint128_t)window_size - delta,
(uint128_t)window_size
);

return downgrade_cast<uint64_t>((uint128_t)value_ex * decay);
} else {
return 0;
}
} else {
return value_ex;
}
}

/**
* return the value at a current or future ordinal
*/
uint64_t value_at( uint32_t ordinal, uint32_t window_size ) const {
return integer_divide_ceil(value_ex_at(ordinal, window_size), Precision);
}

void add( uint64_t units, uint32_t ordinal, uint32_t window_size /* must be positive */ )
{
// check for some numerical limits before doing any state mutations
EOS_ASSERT(units <= max_raw_value, rate_limiting_state_inconsistent, "Usage exceeds maximum value representable after extending for precision");

uint128_t units_ex = (uint128_t)units * Precision;
if (last_ordinal < ordinal) {
value_ex = value_ex_at(ordinal, window_size);
last_ordinal = ordinal;
}

// saturate the value
uint128_t new_value_ex = std::min<uint128_t>(units_ex + (uint128_t)value_ex, std::numeric_limits<uint64_t>::max());
value_ex = downgrade_cast<uint64_t>(new_value_ex);
}
};
}

using usage_accumulator = impl::exponential_moving_average_accumulator<>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ namespace eosio { namespace chain {
fc::time_point deadline = fc::time_point::maximum();
fc::microseconds leeway = fc::microseconds( config::default_subjective_cpu_leeway_us );
int64_t billed_cpu_time_us = 0;
uint32_t subjective_cpu_bill_us = 0;
bool explicit_billed_cpu_time = false;

transaction_checktime_timer transaction_timer;
Expand Down
7 changes: 7 additions & 0 deletions libraries/chain/resource_limits.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,13 @@ void resource_limits_manager::get_account_limits( const account_name& account, i
}
}

bool resource_limits_manager::is_unlimited_cpu( const account_name& account ) const {
const auto* buo = _db.find<resource_limits_object,by_owner>( boost::make_tuple(false, account) );
if (buo) {
return buo->cpu_weight == -1;
}
return false;
}

void resource_limits_manager::process_account_limit_updates() {
auto& multi_index = _db.get_mutable_index<resource_limits_index>();
Expand Down
4 changes: 3 additions & 1 deletion libraries/chain/transaction_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,9 @@ namespace eosio { namespace chain {

if( !explicit_billed_cpu_time ) {
// Fail early if amount of the previous speculative execution is within 10% of remaining account cpu available
int64_t validate_account_cpu_limit = account_cpu_limit - EOS_PERCENT( account_cpu_limit, 10 * config::percent_1 );
int64_t validate_account_cpu_limit = account_cpu_limit - subjective_cpu_bill_us;
if( validate_account_cpu_limit > 0 )
validate_account_cpu_limit -= EOS_PERCENT( validate_account_cpu_limit, 10 * config::percent_1 );
if( validate_account_cpu_limit < 0 ) validate_account_cpu_limit = 0;
validate_account_cpu_usage( billed_cpu_time_us, validate_account_cpu_limit, true );
}
Expand Down
6 changes: 3 additions & 3 deletions libraries/testing/tester.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ namespace eosio { namespace testing {

if( !skip_pending_trxs ) {
for( auto itr = unapplied_transactions.begin(); itr != unapplied_transactions.end(); ) {
auto trace = control->push_transaction( itr->trx_meta, fc::time_point::maximum(), DEFAULT_BILLED_CPU_TIME_US, true );
auto trace = control->push_transaction( itr->trx_meta, fc::time_point::maximum(), DEFAULT_BILLED_CPU_TIME_US, true, 0 );
traces.emplace_back( trace );
if(!no_throw && trace->except) {
// this always throws an fc::exception, since the original exception is copied into an fc::exception
Expand Down Expand Up @@ -549,7 +549,7 @@ namespace eosio { namespace testing {
fc::microseconds::maximum() :
fc::microseconds( deadline - fc::time_point::now() );
auto fut = transaction_metadata::start_recover_keys( ptrx, control->get_thread_pool(), control->get_chain_id(), time_limit );
auto r = control->push_transaction( fut.get(), deadline, billed_cpu_time_us, billed_cpu_time_us > 0 );
auto r = control->push_transaction( fut.get(), deadline, billed_cpu_time_us, billed_cpu_time_us > 0, 0 );
if( r->except_ptr ) std::rethrow_exception( r->except_ptr );
if( r->except ) throw *r->except;
return r;
Expand All @@ -574,7 +574,7 @@ namespace eosio { namespace testing {
fc::microseconds( deadline - fc::time_point::now() );
auto ptrx = std::make_shared<packed_transaction>( trx, c );
auto fut = transaction_metadata::start_recover_keys( ptrx, control->get_thread_pool(), control->get_chain_id(), time_limit );
auto r = control->push_transaction( fut.get(), deadline, billed_cpu_time_us, billed_cpu_time_us > 0 );
auto r = control->push_transaction( fut.get(), deadline, billed_cpu_time_us, billed_cpu_time_us > 0, 0 );
if (no_throw) return r;
if( r->except_ptr ) std::rethrow_exception( r->except_ptr );
if( r->except) throw *r->except;
Expand Down
81 changes: 62 additions & 19 deletions plugins/chain_plugin/account_query_db.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ using namespace boost::bimaps;
namespace {
/**
* Structure to hold indirect reference to a `property_object` via {owner,name} as well as a non-standard
* index over `last_updated` for roll-back support
* index over `last_updated_height` (which is truncated at the LIB during initialization) for roll-back support
*/
struct permission_info {
// indexed data
chain::name owner;
chain::name name;
fc::time_point last_updated;
uint32_t last_updated_height;

// un-indexed data
uint32_t threshold;
Expand All @@ -37,10 +37,10 @@ namespace {
};

struct by_owner_name;
struct by_last_updated;
struct by_last_updated_height;

/**
* Multi-index providing fast lookup for {owner,name} as well as {last_updated}
* Multi-index providing fast lookup for {owner,name} as well as {last_updated_height}
*/
using permission_info_index_t = multi_index_container<
permission_info,
Expand All @@ -53,8 +53,8 @@ namespace {
>
>,
ordered_non_unique<
tag<by_last_updated>,
member<permission_info, fc::time_point, &permission_info::last_updated>
tag<by_last_updated_height>,
member<permission_info, uint32_t, &permission_info::last_updated_height>
>
>
>;
Expand Down Expand Up @@ -143,8 +143,19 @@ namespace eosio::chain_apis {
auto start = fc::time_point::now();
const auto& index = controller.db().get_index<chain::permission_index>().indices().get<by_id>();

// build a initial time to block number map
const auto lib_num = controller.last_irreversible_block_num();
const auto head_num = controller.head_block_num();

for (uint32_t block_num = lib_num + 1; block_num <= head_num; block_num++) {
const auto block_p = controller.fetch_block_by_number(block_num);
EOS_ASSERT(block_p, chain::plugin_exception, "cannot fetch reversible block ${block_num}, required for account_db initialization", ("block_num", block_num));
time_to_block_num.emplace(block_p->timestamp.to_time_point(), block_num);
}

for (const auto& po : index ) {
const auto& pi = permission_info_index.emplace( permission_info{ po.owner, po.name, po.last_updated, po.auth.threshold } ).first;
uint32_t last_updated_height = last_updated_time_to_height(po.last_updated);
const auto& pi = permission_info_index.emplace( permission_info{ po.owner, po.name, last_updated_height, po.auth.threshold } ).first;
add_to_bimaps(*pi, po);
}
auto duration = fc::time_point::now() - start;
Expand Down Expand Up @@ -185,37 +196,57 @@ namespace eosio::chain_apis {

bool is_rollback_required( const chain::block_state_ptr& bsp ) const {
std::shared_lock read_lock(rw_mutex);
const auto t = bsp->block->timestamp.to_time_point();
const auto& index = permission_info_index.get<by_last_updated>();
const auto bnum = bsp->block->block_num();
const auto& index = permission_info_index.get<by_last_updated_height>();

if (index.empty()) {
return false;
} else {
const auto& pi = (*index.rbegin());
if (pi.last_updated < t) {
if (pi.last_updated_height < bnum) {
return false;
}
}

return true;
}

uint32_t last_updated_time_to_height( const fc::time_point& last_updated) {
const auto lib_num = controller.last_irreversible_block_num();
const auto lib_time = controller.last_irreversible_block_time();

uint32_t last_updated_height = lib_num;
if (last_updated > lib_time) {
const auto iter = time_to_block_num.find(last_updated);
EOS_ASSERT(iter != time_to_block_num.end(), chain::plugin_exception, "invalid block time encountered in on-chain accounts ${time}", ("time", last_updated));
last_updated_height = iter->second;
}

return last_updated_height;
}

/**
* Given a time_point, remove all permissions that were last updated at or after that time_point
* this will effectively remove any updates that happened at or after that time point
* Given a block number, remove all permissions that were last updated at or after that block number
* this will effectively roll back the database to just before the incoming block
*
* For each removed entry, this will create a new entry if there exists an equivalent {owner, name} permission
* at the HEAD state of the chain.
* @param bsp - the block to rollback before
*/
void rollback_to_before( const chain::block_state_ptr& bsp ) {
const auto t = bsp->block->timestamp.to_time_point();
auto& index = permission_info_index.get<by_last_updated>();
const auto bnum = bsp->block->block_num();
auto& index = permission_info_index.get<by_last_updated_height>();
const auto& permission_by_owner = controller.db().get_index<chain::permission_index>().indices().get<chain::by_owner>();

// roll back time-map
auto time_iter = time_to_block_num.rbegin();
while (time_iter != time_to_block_num.rend() && time_iter->second >= bnum) {
time_iter = decltype(time_iter){time_to_block_num.erase( std::next(time_iter).base() )};
}

while (!index.empty()) {
const auto& pi = (*index.rbegin());
if (pi.last_updated < t) {
if (pi.last_updated_height < bnum) {
break;
}

Expand All @@ -228,8 +259,11 @@ namespace eosio::chain_apis {
index.erase(index.iterator_to(pi));
} else {
const auto& po = *itr;
index.modify(index.iterator_to(pi), [&po](auto& mutable_pi) {
mutable_pi.last_updated = po.last_updated;

uint32_t last_updated_height = po.last_updated == bsp->header.timestamp ? bsp->block_num : last_updated_time_to_height(po.last_updated);

index.modify(index.iterator_to(pi), [&po, last_updated_height](auto& mutable_pi) {
mutable_pi.last_updated_height = last_updated_height;
mutable_pi.threshold = po.auth.threshold;
});
add_to_bimaps(pi, po);
Expand Down Expand Up @@ -331,6 +365,11 @@ namespace eosio::chain_apis {
std::unique_lock write_lock(rw_mutex);

rollback_to_before(bsp);

// insert this blocks time into the time map
time_to_block_num.emplace(bsp->header.timestamp, bsp->block_num);

const auto bnum = bsp->block_num;
auto& index = permission_info_index.get<by_owner_name>();
const auto& permission_by_owner = controller.db().get_index<chain::permission_index>().indices().get<chain::by_owner>();

Expand All @@ -342,11 +381,11 @@ namespace eosio::chain_apis {
auto itr = index.find(key);
if (itr == index.end()) {
const auto& po = *source_itr;
itr = index.emplace(permission_info{ po.owner, po.name, po.last_updated, po.auth.threshold }).first;
itr = index.emplace(permission_info{ po.owner, po.name, bnum, po.auth.threshold }).first;
} else {
remove_from_bimaps(*itr);
index.modify(itr, [&](auto& mutable_pi){
mutable_pi.last_updated = source_itr->last_updated;
mutable_pi.last_updated_height = bnum;
mutable_pi.threshold = source_itr->auth.threshold;
});
}
Expand Down Expand Up @@ -440,6 +479,10 @@ namespace eosio::chain_apis {
cached_trace_map_t cached_trace_map; ///< temporary cache of uncommitted traces
onblock_trace_t onblock_trace; ///< temporary cache of on_block trace

using time_map_t = std::map<fc::time_point, uint32_t>;
time_map_t time_to_block_num;



using name_bimap_t = bimap<multiset_of<weighted<chain::permission_level>>, multiset_of<permission_info::cref>>;
using key_bimap_t = bimap<multiset_of<weighted<chain::public_key_type>>, multiset_of<permission_info::cref>>;
Expand Down
Loading