Skip to content

Commit

Permalink
runtime: vote program hardening, support 'tower sync'
Browse files Browse the repository at this point in the history
- types: fd_treap and fd_deque alloc helper to accept custom max
- sysvar: define max element count for 'slot hashes', 'recent
  block hashes', and 'stake history'
- types: change collection 'max' to 'min', update deserializer to
  always allocate enough capacity for collections
- types: remove 'SMALL_DEQUE' error
- program: support vote program 'tower sync' instructions
- runtime: fix typo in FD_SYSVAR_STAKE_HISTORY_CAP

A note on hardening:

Previously, Firedancer used Agave's size limits for various
dynamically sized bincode data structures, such as the lockouts deque
of the vote instruction and vote state.

A malicious user could provide a oversize structure in instruction data
though.  Similarly, a fuzzer could provide a corrupted vote state, though
this is assumed to not be possible on a real network.

We did not handle this case correctly and would instead fail to
deserialize the input (potentially leading to a different execution
result).

The real item count limit of dynamically sized structures in native
programs is derived from size limitations:  instruction data size is
~1.2kB max, account size is 10MiB max.

This patch updates fd_types to consider the previous 'max' item count
limits as minimums instead.  If any larger structure is provided, the
deserializer will allocate as much as needed.  (Provably bounded as the
deserializer always advances when it makes an allocation, and verifies
that the serialization is valid before allocating)

This handles all cases correctly.  If the user provides 0 vote lockouts,
we allocate 31 (the minimum), which is enough space to correctly
execute native program business logic, which might append more entries
to the structure.  If the user provides 100 vote lockouts, we allocate
100, as that exceeds the minimum.

However, we do still need to ensure that the native program never
attempts to add more items to a collection in the oversize case. This
is out of scope for this PR.
  • Loading branch information
riptl authored and ripatel-fd committed May 3, 2024
1 parent b45978b commit 8b176e0
Show file tree
Hide file tree
Showing 15 changed files with 471 additions and 316 deletions.
4 changes: 2 additions & 2 deletions src/flamenco/genesis/fd_genesis_create.c
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,8 @@ genesis_create( void * buf,
vs->node_pubkey = options->identity_pubkey;
vs->authorized_withdrawer = options->identity_pubkey;
vs->commission = 100;
vs->authorized_voters.pool = fd_vote_authorized_voters_pool_alloc ( fd_scratch_virtual() );
vs->authorized_voters.treap = fd_vote_authorized_voters_treap_alloc( fd_scratch_virtual() );
vs->authorized_voters.pool = fd_vote_authorized_voters_pool_alloc ( fd_scratch_virtual(), 1UL );
vs->authorized_voters.treap = fd_vote_authorized_voters_treap_alloc( fd_scratch_virtual(), 1UL );

fd_vote_authorized_voter_t * ele =
fd_vote_authorized_voters_pool_ele_acquire( vs->authorized_voters.pool );
Expand Down
29 changes: 15 additions & 14 deletions src/flamenco/runtime/context/fd_exec_epoch_ctx.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "fd_exec_epoch_ctx.h"
#include <assert.h>
#include "../sysvar/fd_sysvar_stake_history.h"

/* TODO remove this */
#define MAX_LG_SLOT_CNT 10UL
Expand All @@ -18,13 +19,13 @@ fd_exec_epoch_ctx_footprint_ext( fd_exec_epoch_ctx_layout_t * layout,
fd_memset( layout, 0, sizeof(fd_exec_epoch_ctx_layout_t) );
layout->vote_acct_max = vote_acct_max;

ulong stake_votes_sz = fd_vote_accounts_pair_t_map_footprint( vote_acct_max ); if( !stake_votes_sz ) return 0UL;
ulong stake_delegations_sz = fd_delegation_pair_t_map_footprint ( vote_acct_max ); if( !stake_delegations_sz ) return 0UL;
ulong stake_history_treap_sz = fd_stake_history_treap_footprint( FD_STAKE_HISTORY_MAX ); if( !stake_history_treap_sz ) FD_LOG_CRIT(( "invalid fd_stake_history_treap footprint" ));
ulong stake_history_pool_sz = fd_stake_history_pool_footprint ( FD_STAKE_HISTORY_MAX ); if( !stake_history_pool_sz ) FD_LOG_CRIT(( "invalid fd_stake_history_pool footprint" ));
ulong next_epoch_stakes_sz = fd_vote_accounts_pair_t_map_footprint( vote_acct_max ); if( !next_epoch_stakes_sz ) return 0UL;
ulong leaders_sz = fd_epoch_leaders_footprint( MAX_PUB_CNT, MAX_SLOTS_CNT ); if( !leaders_sz ) FD_LOG_CRIT(( "invalid fd_epoch_leaders footprint" ));
ulong bank_hash_cmp_sz = fd_bank_hash_cmp_footprint( MAX_LG_SLOT_CNT ); if( !bank_hash_cmp_sz ) FD_LOG_CRIT(( "invalid fd_bank_hash_cmp footprint" ));
ulong stake_votes_sz = fd_vote_accounts_pair_t_map_footprint( vote_acct_max ); if( !stake_votes_sz ) return 0UL;
ulong stake_delegations_sz = fd_delegation_pair_t_map_footprint ( vote_acct_max ); if( !stake_delegations_sz ) return 0UL;
ulong stake_history_treap_sz = fd_stake_history_treap_footprint( FD_SYSVAR_STAKE_HISTORY_CAP ); if( !stake_history_treap_sz ) FD_LOG_CRIT(( "invalid fd_stake_history_treap footprint" ));
ulong stake_history_pool_sz = fd_stake_history_pool_footprint ( FD_SYSVAR_STAKE_HISTORY_CAP ); if( !stake_history_pool_sz ) FD_LOG_CRIT(( "invalid fd_stake_history_pool footprint" ));
ulong next_epoch_stakes_sz = fd_vote_accounts_pair_t_map_footprint( vote_acct_max ); if( !next_epoch_stakes_sz ) return 0UL;
ulong leaders_sz = fd_epoch_leaders_footprint( MAX_PUB_CNT, MAX_SLOTS_CNT ); if( !leaders_sz ) FD_LOG_CRIT(( "invalid fd_epoch_leaders footprint" ));
ulong bank_hash_cmp_sz = fd_bank_hash_cmp_footprint( MAX_LG_SLOT_CNT ); if( !bank_hash_cmp_sz ) FD_LOG_CRIT(( "invalid fd_bank_hash_cmp footprint" ));

FD_SCRATCH_ALLOC_INIT( l, 0 );
FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_epoch_ctx_t), sizeof(fd_exec_epoch_ctx_t) );
Expand Down Expand Up @@ -81,14 +82,14 @@ fd_exec_epoch_ctx_new( void * mem,
//void * leaders_mem = (void *)( (ulong)mem + layout->leaders_off );
void * bank_hash_cmp_mem = (void *)( (ulong)mem + layout->bank_hash_cmp_off );

fd_vote_accounts_pair_t_map_new( stake_votes_mem, vote_acct_max );
fd_delegation_pair_t_map_new ( stake_delegations_mem, vote_acct_max );
fd_stake_history_treap_new ( stake_history_treap_mem, FD_STAKE_HISTORY_MAX );
fd_stake_history_pool_new ( stake_history_pool_mem, FD_STAKE_HISTORY_MAX );
fd_vote_accounts_pair_t_map_new( next_epoch_stakes_mem, vote_acct_max );
fd_vote_accounts_pair_t_map_new( stake_votes_mem, vote_acct_max );
fd_delegation_pair_t_map_new ( stake_delegations_mem, vote_acct_max );
fd_stake_history_treap_new ( stake_history_treap_mem, FD_SYSVAR_STAKE_HISTORY_CAP );
fd_stake_history_pool_new ( stake_history_pool_mem, FD_SYSVAR_STAKE_HISTORY_CAP );
fd_vote_accounts_pair_t_map_new( next_epoch_stakes_mem, vote_acct_max );
//TODO support separate epoch leaders new and init
//fd_epoch_leaders_new ( leaders_mem, MAX_PUB_CNT, MAX_SLOTS_CNT );
fd_bank_hash_cmp_new ( bank_hash_cmp_mem, MAX_LG_SLOT_CNT );
fd_bank_hash_cmp_new ( bank_hash_cmp_mem, MAX_LG_SLOT_CNT );

FD_COMPILER_MFENCE();
self->magic = FD_EXEC_EPOCH_CTX_MAGIC;
Expand Down Expand Up @@ -254,7 +255,7 @@ fd_exec_epoch_ctx_fixup_memory( fd_exec_epoch_ctx_t * epoch_ctx,

if( fd_stake_history_treap_ele_cnt( new_treap ) )
FD_LOG_ERR(( "epoch_ctx->stake_history not empty" ));
if( fd_stake_history_pool_max( new_pool ) != FD_STAKE_HISTORY_MAX )
if( fd_stake_history_pool_max( new_pool ) != FD_SYSVAR_STAKE_HISTORY_CAP )
FD_LOG_ERR(( "epoch_ctx->stake_history corrupt" ));

if( FD_LIKELY( old_treap ) ) { /* not initialized by genesis */
Expand Down
7 changes: 4 additions & 3 deletions src/flamenco/runtime/fd_runtime.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "sysvar/fd_sysvar_cache.h"
#include "sysvar/fd_sysvar_clock.h"
#include "sysvar/fd_sysvar_epoch_schedule.h"
#include "sysvar/fd_sysvar_recent_hashes.h"
#include "sysvar/fd_sysvar_stake_history.h"
#include "sysvar/fd_sysvar.h"
#include "../../ballet/base58/fd_base58.h"
Expand Down Expand Up @@ -89,7 +90,7 @@ fd_runtime_init_bank_from_genesis( fd_exec_slot_ctx_t * slot_ctx,
slot_ctx->slot_bank.block_height = 0UL;

fd_block_block_hash_entry_t *hashes = slot_ctx->slot_bank.recent_block_hashes.hashes =
deq_fd_block_block_hash_entry_t_alloc(slot_ctx->valloc);
deq_fd_block_block_hash_entry_t_alloc( slot_ctx->valloc, FD_SYSVAR_RECENT_HASHES_CAP );
fd_block_block_hash_entry_t *elem = deq_fd_block_block_hash_entry_t_push_head_nocopy(hashes);
fd_block_block_hash_entry_new(elem);
fd_memcpy(elem->blockhash.hash, genesis_hash, FD_SHA256_HASH_SZ);
Expand Down Expand Up @@ -1183,7 +1184,7 @@ fd_runtime_finalize_txns_tpool( fd_exec_slot_ctx_t * slot_ctx,
for ( deq_fd_block_block_hash_entry_t_iter_t iter = deq_fd_block_block_hash_entry_t_iter_init( hashes_deque );
!deq_fd_block_block_hash_entry_t_iter_done( hashes_deque, iter );
iter = deq_fd_block_block_hash_entry_t_iter_next( hashes_deque, iter ) ) {
/* If the block hash entry matches the transactions recent block hash, we skip thje hash */
/* If the block hash entry matches the transactions recent block hash, we skip the hash */
fd_block_block_hash_entry_t * entry = deq_fd_block_block_hash_entry_t_iter_ele( hashes_deque, iter );
if ( memcmp( entry->blockhash.hash, recent_blockhash->hash, sizeof(fd_hash_t) ) == 0 ) {
skip_hash = 1;
Expand Down Expand Up @@ -3796,7 +3797,7 @@ fd_runtime_replay( fd_runtime_ctx_t * state, fd_runtime_args_t * args ) {
prev_slot = slot;
}

if( state->tpool ) {
if( state->tpool ) {
fd_tpool_fini( state->tpool );
}

Expand Down
Loading

0 comments on commit 8b176e0

Please sign in to comment.