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

Commit

Permalink
Ref #2, #7: Progress towards authorizing transactions
Browse files Browse the repository at this point in the history
As of now, almost all the ingredients are in place for authorizing
transactions. We check that the transaction bears signatures sufficient to
satisfy its declared authorizations, and as we execute the transaction,
we ensure that the declared authorizations are sufficient to satisfy the
authorizations required by executing the message handlers, and we ensure
that all of the transaction's declared authorizations were used.

What's not done:
 - Detecting/rejecting duplicate signatures and unnecessary signatures
 - Choosing the correct permission level when a message handler requires
an authorization

Choosing the correct permission level is the big ticket item. To
implement this, we need to implement a missing chunk of system contract
functionality, specifically around defining links from a user's
permissions tree to contracts and message types.
  • Loading branch information
nathanielhourt committed Jul 27, 2017
1 parent 9b76ee8 commit 67c9b8b
Show file tree
Hide file tree
Showing 18 changed files with 197 additions and 132 deletions.
57 changes: 46 additions & 11 deletions libraries/chain/chain_controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,8 @@ try {
} FC_CAPTURE_AND_RETHROW( (trx) ) }

void chain_controller::validate_authority( const SignedTransaction& trx )const {
if (_skip_flags | skip_transaction_signatures)
return;

auto getAuthority = [&db=_db](const types::AccountPermission& permission) {
auto key = boost::make_tuple(permission.account, permission.permission);
Expand All @@ -493,8 +495,10 @@ void chain_controller::validate_authority( const SignedTransaction& trx )const {
#warning TODO: Use a real chain_id here (where is this stored? Do we still need it?)
auto checker = MakeAuthorityChecker(std::move(getAuthority), trx.get_signature_keys(chain_id_type{}));

for (const auto& requiredAuthority : trx.authorizations)
EOS_ASSERT(checker.satisfied(requiredAuthority), tx_missing_auth, "Transaction is not authorized.");
for (const auto& declaredAuthority : trx.authorizations)
EOS_ASSERT(checker.satisfied(declaredAuthority), tx_missing_sigs,
"Transaction declares authority '${auth}', but does not have signatures for it.",
("auth", declaredAuthority));
}

void chain_controller::validate_scope( const SignedTransaction& trx )const {
Expand Down Expand Up @@ -563,18 +567,18 @@ void chain_controller::validate_expiration(const SignedTransaction& trx) const
* The order of execution of precondition and apply can impact the validity of the
* entire message.
*/
void chain_controller::process_message( const ProcessedTransaction& trx, AccountName code, const Message& message, MessageOutput& output) {
apply_context apply_ctx(*this, _db, trx, message, code);
void chain_controller::process_message( const ProcessedTransaction& trx, AccountName code, const Message& message,
TransactionAuthorizationChecker* authChecker, MessageOutput& output) {
apply_context apply_ctx(*this, _db, trx, message, code, authChecker);
apply_message(apply_ctx);

output.notify.reserve( apply_ctx.notified.size() );
wdump((output));

for( uint32_t i = 0; i < apply_ctx.notified.size(); ++i ) {
try {
auto notify_code = apply_ctx.notified[i];
output.notify.push_back( {notify_code} );
process_message( trx, notify_code, message, output.notify.back().output );
process_message( trx, notify_code, message, authChecker, output.notify.back().output );
} FC_CAPTURE_AND_RETHROW((apply_ctx.notified[i]))
}

Expand Down Expand Up @@ -633,13 +637,44 @@ ProcessedTransaction chain_controller::_apply_transaction(const SignedTransactio
*/
ProcessedTransaction chain_controller::process_transaction( const SignedTransaction& trx )
{ try {
// This lambda takes an AccountPermission, and returns its parent. It caches the permission_object it last returned,
// which allows it to simplify the database lookups when it is next used to fetch that object's parent.
auto getParentPermission =
[&index = _db.get_index<permission_index>().indices(), cache = static_cast<const permission_object*>(nullptr)]
(const types::AccountPermission& child) mutable -> fc::optional<types::AccountPermission> {
// Ensure cache points to permission_object corresponding to child
if (!(cache && cache->owner == child.account && cache->name == child.permission)) {
auto& ownerIndex = index.get<by_owner>();
auto itr = ownerIndex.find(boost::make_tuple(child.account, child.permission));
FC_ASSERT(itr != ownerIndex.end(), "Unable to find permission_object for AccountPermission '${perm}'",
("perm", child));
cache = &*itr;
}

// Make cache point to the parent of child and return result
if (cache) {
if (cache->parent._id == 0) {
cache = nullptr;
return {};
} else {
cache = &*index.get<by_id>().find(cache->parent);
return types::AccountPermission(cache->owner, cache->name);
}
}
return {};
};

ProcessedTransaction ptrx( trx );
TransactionAuthorizationChecker authChecker(trx.authorizations, getParentPermission);
ptrx.output.resize( trx.messages.size() );

for( uint32_t i = 0; i < ptrx.messages.size(); ++i ) {
process_message(ptrx, ptrx.messages[i].code, ptrx.messages[i], ptrx.output[i] );
process_message(ptrx, ptrx.messages[i].code, ptrx.messages[i], &authChecker, ptrx.output[i] );
}

EOS_ASSERT(authChecker.allPermissionsUsed(), tx_irrelevant_auth,
"Transaction declared an authorization it did not need");

return ptrx;
} FC_CAPTURE_AND_RETHROW( (trx) ) }

Expand Down Expand Up @@ -795,11 +830,11 @@ void chain_controller::initialize_chain(chain_initializer_interface& starter)
auto messages = starter.prepare_database(*this, _db);
std::for_each(messages.begin(), messages.end(), [&](const Message& m) {
MessageOutput output;
ProcessedTransaction trx; /// dummy tranaction required for scope validation
ProcessedTransaction trx; /// dummy transaction required for scope validation
trx.scope = { config::EosContractName, "inita" };
std::sort(trx.scope.begin(), trx.scope.end() );
with_skip_flags( skip_scope_check, [&](){
process_message(trx,m.code,m,output);
process_message(trx,m.code,m,nullptr,output);
} );
});
});
Expand Down Expand Up @@ -1068,6 +1103,7 @@ ProcessedTransaction chain_controller::transaction_from_variant( const fc::varia
GET_FIELD( vo, expiration, result );
GET_FIELD( vo, scope, result );
GET_FIELD( vo, signatures, result );
GET_FIELD( vo, authorizations, result );

if( vo.contains( "messages" ) ) {
const vector<variant>& msgs = vo["messages"].get_array();
Expand All @@ -1076,7 +1112,6 @@ ProcessedTransaction chain_controller::transaction_from_variant( const fc::varia
const auto& vo = msgs[i].get_object();
GET_FIELD( vo, code, result.messages[i] );
GET_FIELD( vo, type, result.messages[i] );
GET_FIELD( vo, authorization, result.messages[i] );

if( vo.contains( "data" ) ) {
const auto& data = vo["data"];
Expand Down Expand Up @@ -1127,6 +1162,7 @@ fc::variant chain_controller::transaction_to_variant( const ProcessedTransactio
SET_FIELD( trx_mvo, trx, expiration );
SET_FIELD( trx_mvo, trx, scope );
SET_FIELD( trx_mvo, trx, signatures );
SET_FIELD( trx_mvo, trx, authorizations );

vector<fc::mutable_variant_object> msgs( trx.messages.size() );
vector<fc::variant> msgsv(msgs.size());
Expand All @@ -1136,7 +1172,6 @@ fc::variant chain_controller::transaction_to_variant( const ProcessedTransactio
auto& msg = trx.messages[i];
SET_FIELD( msg_mvo, msg, code );
SET_FIELD( msg_mvo, msg, type );
SET_FIELD( msg_mvo, msg, authorization );

const auto& code_account = _db.get<account_object,by_name>( msg.code );
if( code_account.abi.size() > 4 ) { /// 4 == packsize of empty Abi
Expand Down
3 changes: 2 additions & 1 deletion libraries/chain/include/eos/chain/chain_controller.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,8 @@ namespace eos { namespace chain {
void validate_authority(const SignedTransaction& trx )const;
/// @}

void process_message(const ProcessedTransaction& trx, AccountName code, const Message& message, MessageOutput& output);
void process_message(const ProcessedTransaction& trx, AccountName code, const Message& message,
TransactionAuthorizationChecker* authChecker, MessageOutput& output);
void apply_message(apply_context& c);

bool should_check_for_duplicate_transactions()const { return !(_skip_flags&skip_transaction_dupe_check); }
Expand Down
2 changes: 2 additions & 0 deletions libraries/chain/include/eos/chain/exceptions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ namespace eos { namespace chain {
FC_DECLARE_DERIVED_EXCEPTION( unknown_block_exception, eos::chain::chain_exception, 3110000, "unknown block" )

FC_DECLARE_DERIVED_EXCEPTION( tx_missing_auth, eos::chain::transaction_exception, 3030001, "missing required authority" )
FC_DECLARE_DERIVED_EXCEPTION( tx_missing_sigs, eos::chain::transaction_exception, 3030002, "signatures do not satisfy declared authorizations" )
FC_DECLARE_DERIVED_EXCEPTION( tx_irrelevant_auth, eos::chain::transaction_exception, 3030003, "irrelevant authority included" )
FC_DECLARE_DERIVED_EXCEPTION( tx_irrelevant_sig, eos::chain::transaction_exception, 3030004, "irrelevant signature included" )
FC_DECLARE_DERIVED_EXCEPTION( tx_duplicate_sig, eos::chain::transaction_exception, 3030005, "duplicate signature included" )
FC_DECLARE_DERIVED_EXCEPTION( invalid_committee_approval, eos::chain::transaction_exception, 3030006, "committee account cannot directly approve transaction" )
Expand Down
8 changes: 4 additions & 4 deletions libraries/chain/include/eos/chain/message.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ namespace eos { namespace chain {
struct Message : public types::Message {
Message() = default;
template<typename T>
Message(const AccountName& code, const vector<types::AccountPermission>& authorization, const types::FuncName& type, T&& value)
:types::Message(code, type, authorization, Bytes()) {
Message(const AccountName& code, const types::FuncName& type, T&& value)
:types::Message(code, type, Bytes()) {
set<T>(type, std::forward<T>(value));
}

Message(const AccountName& code, const vector<types::AccountPermission>& authorization, const types::FuncName& type)
:types::Message(code, type, authorization, Bytes()) {}
Message(const AccountName& code, const types::FuncName& type)
:types::Message(code, type, Bytes()) {}

Message(const types::Message& m) : types::Message(m) {}

Expand Down
22 changes: 11 additions & 11 deletions libraries/chain/include/eos/chain/message_handling_contexts.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ class message_validate_context {
const chainbase::database& d,
const chain::Transaction& t,
const chain::Message& m,
types::AccountName c )
:controller(control),db(d),trx(t),msg(m),code(c),used_authorizations(msg.authorization.size(), false){}
types::AccountName c,
TransactionAuthorizationChecker* authChecker)
:controller(control),db(d),trx(t),msg(m),code(c),authChecker(authChecker){}

/**
* @brief Require @ref account to have approved of this message
Expand All @@ -29,15 +30,15 @@ class message_validate_context {
*/
void require_authorization(const types::AccountName& account);
void require_scope(const types::AccountName& account)const;
bool has_authorization( const types::AccountName& account )const;
bool all_authorizations_used() const;

const chain_controller& controller;
const chainbase::database& db; ///< database where state is stored
const chain::Transaction& trx; ///< used to gather the valid read/write scopes
const chain::Message& msg; ///< message being applied
types::AccountName code; ///< the code that is currently running

TransactionAuthorizationChecker* authChecker;


int32_t load_i64( Name scope, Name code, Name table, Name Key, char* data, uint32_t maxlen );

Expand All @@ -58,9 +59,6 @@ class message_validate_context {
uint128_t* primary, uint128_t* secondary, char* data, uint32_t maxlen );
int32_t lowerbound_secondary_i128i128( Name scope, Name code, Name table,
uint128_t* primary, uint128_t* secondary, char* data, uint32_t maxlen );

///< Parallel to msg.authorization; tracks which permissions have been used while processing the message
vector<bool> used_authorizations;
};

class precondition_validate_context : public message_validate_context {
Expand All @@ -69,8 +67,9 @@ class precondition_validate_context : public message_validate_context {
const chainbase::database& db,
const chain::Transaction& t,
const chain::Message& m,
const types::AccountName& code)
:message_validate_context(con, db, t, m, code){}
const types::AccountName& code,
TransactionAuthorizationChecker* authChecker)
:message_validate_context(con, db, t, m, code, authChecker){}
};

class apply_context : public precondition_validate_context {
Expand All @@ -79,8 +78,9 @@ class apply_context : public precondition_validate_context {
chainbase::database& db,
const chain::Transaction& t,
const chain::Message& m,
const types::AccountName& code)
:precondition_validate_context(con,db,t,m,code),mutable_controller(con),mutable_db(db){}
const types::AccountName& code,
TransactionAuthorizationChecker* authChecker)
:precondition_validate_context(con,db,t,m,code,authChecker),mutable_controller(con),mutable_db(db){}

int32_t store_i64( Name scope, Name table, Name key, const char* data, uint32_t len);
int32_t remove_i64( Name scope, Name table, Name key );
Expand Down
74 changes: 74 additions & 0 deletions libraries/chain/include/eos/chain/transaction.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,80 @@ namespace eos { namespace chain {
vector<MessageOutput> output;
};

/**
* @brief This class aids in checking that a transaction is properly authorized, and that no unnecessary
* authorizations are present in it.
*
* In order for a transaction to be valid, it must declare the permissions it requires to execute all of its
* contained messages (@see SignedTransaction::authorizations). The blockchain can verify that the transaction bears
* signatures necessary and sufficient to satisfy its declared permissions before the transaction is executed;
* however, the blockchain cannot know whether the declared permissions are necessary and sufficient to authorize
* the transaction until the transaction is fully executed. This is because the permissions required is a
* contract-layer concern, so the way we discover what permissions are required is by executing the messages, and as
* the message handlers execute, they assert that certain permissions are required.
*
* This class takes the list of declared permissions provided by a transaction at construction. As the transaction
* is subsequently executed, and the message handlers assert that permissions are required, and these required
* permissions can be passed to @ref requirePermission which will verify that the declared permissions satisfy the
* required permissions. This class also tracks which of the declared permissions have been used to satisfy a
* permission passed to @ref requirePermission and which ones have not.
*
* When the transaction is finished executing, call @ref allPermissionsUsed to determine whether any declared
* permissions were unnecessary to fully authorize the transaction.
*/
class TransactionAuthorizationChecker {
public:
using ParentGetter = std::function<fc::optional<types::AccountPermission>(const types::AccountPermission&)>;

/**
* @param declaredPermissions The permissions declared by the transaction as necessary and sufficient to
* authorize it
* @param getParentPermission A callable which takes a @ref types::AccountPermission and returns its parent, or
* an empty optional if no parent exists
*/
TransactionAuthorizationChecker(const vector<types::AccountPermission>& declaredPermissions,
ParentGetter getParentPermission)
: declaredPermissions(declaredPermissions), getParentPermission(getParentPermission) {}

bool requirePermission(const types::AccountPermission& permission) {
auto Matches = [](const types::AccountPermission& permission) {
return [&permission](const types::AccountPermission& other) {
return permission.account == other.account && permission.permission == other.permission;
};
};

auto itr = std::find_if(declaredPermissions.begin(), declaredPermissions.end(), Matches(permission));
if (itr != declaredPermissions.end()) {
usedPermissions[itr - declaredPermissions.begin()] = true;
return true;
}

auto parent = getParentPermission(permission);
while (parent) {
itr = std::find_if(declaredPermissions.begin(), declaredPermissions.end(), Matches(*parent));
if (itr != declaredPermissions.end()) {
usedPermissions[itr - declaredPermissions.begin()] = true;
return true;
}
parent = getParentPermission(*parent);
}

return false;
}

bool allPermissionsUsed() const {
return std::all_of(usedPermissions.begin(), usedPermissions.end(), [](bool b){return b;});
}

private:
/// The list of permissions declared by the transaction
const vector<types::AccountPermission> declaredPermissions;
/// Parallel to @ref declaredPermissions; usedPermissions[N] is true iff declaredPermissions[N] has been required
vector<bool> usedPermissions = vector<bool>(declaredPermissions.size(), false);

ParentGetter getParentPermission;
};

/// @} transactions group

} } // eos::chain
Expand Down
27 changes: 4 additions & 23 deletions libraries/chain/message_handling_contexts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,11 @@

namespace eos { namespace chain {

bool message_validate_context::has_authorization(const types::AccountName& account)const {

auto itr = boost::find_if(msg.authorization, [&account](const types::AccountPermission& ap) {
return ap.account == account;
});
return itr != msg.authorization.end();

}

void message_validate_context::require_authorization(const types::AccountName& account) {

auto itr = boost::find_if(msg.authorization, [&account](const types::AccountPermission& ap) {
return ap.account == account;
});

EOS_ASSERT(itr != msg.authorization.end(), tx_missing_auth,
"Required authorization ${auth} not found", ("auth", account));

used_authorizations[itr - msg.authorization.begin()] = true;
#warning TODO: Look up the permission_object that account has specified to use for this message type
if (authChecker)
EOS_ASSERT(authChecker->requirePermission({account, "active"}), tx_missing_auth,
"Transaction does not declare required authority '${auth}'", ("auth", account));
}

void message_validate_context::require_scope(const types::AccountName& account)const {
Expand All @@ -53,15 +39,10 @@ bool apply_context::has_recipient( const types::AccountName& account )const {

void apply_context::require_recipient(const types::AccountName& account) {
if( !has_recipient( account ) ) {
idump((account));
notified.push_back(account);
}
}

bool message_validate_context::all_authorizations_used() const {
return boost::algorithm::all_of_equal(used_authorizations, true);
}

int32_t message_validate_context::load_i64( Name scope, Name code, Name table, Name key, char* value, uint32_t valuelen ) {
require_scope( scope );

Expand Down
4 changes: 0 additions & 4 deletions libraries/chain/wasm_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,6 @@ DEFINE_INTRINSIC_FUNCTION1(env,requireNotice,requireNotice,none,i64,account) {
DEFINE_INTRINSIC_FUNCTION1(env,hasRecipient,hasRecipient,i32,i64,account) {
return wasm_interface::get().current_apply_context->has_recipient( account );
}
DEFINE_INTRINSIC_FUNCTION1(env,hasAuth,hasAuth,i32,i64,account) {
return wasm_interface::get().current_validate_context->has_authorization( account );
}

DEFINE_INTRINSIC_FUNCTION1(env,requireScope,requireScope,none,i64,scope) {
wasm_interface::get().current_validate_context->require_scope( scope );
}
Expand Down
Loading

0 comments on commit 67c9b8b

Please sign in to comment.