Skip to content

Commit

Permalink
Add CPI and instruction stack prechecks
Browse files Browse the repository at this point in the history
  • Loading branch information
mjain-jump committed Sep 18, 2024
1 parent f55c236 commit c002f61
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 55 deletions.
3 changes: 1 addition & 2 deletions contrib/test/instr-fixtures/bpf-loader-v3-programs.list
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
dump/test-vectors/instr/fixtures/bpf-loader-v3-programs/4c3874406b6bc014434a6b08cfd7ce957005c299.fix
dump/test-vectors/instr/fixtures/bpf-loader-v3-programs/crash-61560dca014f17632760e7915b271cfba4fdb121.fix
dump/test-vectors/instr/fixtures/bpf-loader-v3-programs/4c3874406b6bc014434a6b08cfd7ce957005c299.fix
dump/test-vectors/instr/fixtures/bpf-loader-v3-programs/crash-61560dca014f17632760e7915b271cfba4fdb121.fix
dump/test-vectors/instr/fixtures/bpf-loader-v3-programs/572bef775bfe039859f9ba7765452b8c0167548e_684111.fix
58 changes: 30 additions & 28 deletions src/flamenco/runtime/context/fd_exec_txn_ctx.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ typedef struct fd_exec_instr_trace_entry fd_exec_instr_trace_entry_t;

/* https://github.com/anza-xyz/agave/blob/0d34a1a160129c4293dac248e14231e9e773b4ce/program-runtime/src/compute_budget.rs#L139 */
#define FD_MAX_INSTRUCTION_TRACE_LENGTH (64UL)
/* https://github.com/firedancer-io/agave/blob/f70ab5598ccd86b216c3928e4397bf4a5b58d723/compute-budget/src/compute_budget.rs#L13 */
#define FD_MAX_INSTRUCTION_STACK_DEPTH (5UL)

struct __attribute__((aligned(8UL))) fd_exec_txn_ctx {
ulong magic; /* ==FD_EXEC_TXN_CTX_MAGIC */
Expand All @@ -63,36 +65,36 @@ struct __attribute__((aligned(8UL))) fd_exec_txn_ctx {
fd_valloc_t valloc;

ulong paid_fees;
ulong compute_unit_limit; /* Compute unit limit for this transaction. */
ulong compute_unit_price; /* Compute unit price for this transaction. */
ulong compute_meter; /* Remaining compute units */
ulong heap_size; /* Heap size for VMs for this transaction. */
ulong loaded_accounts_data_size_limit; /* Loaded accounts data size limit for this transaction. */
uint prioritization_fee_type; /* The type of prioritization fee to use. */
fd_txn_t const * txn_descriptor; /* Descriptor of the transaction. */
fd_rawtxn_b_t _txn_raw[1]; /* Raw bytes of the transaction. */
uint custom_err; /* When a custom error is returned, this is where the numeric value gets stashed */
uchar instr_stack_sz; /* Current depth of the instruction execution stack. */
fd_exec_instr_ctx_t instr_stack[6]; /* Instruction execution stack. */
ulong compute_unit_limit; /* Compute unit limit for this transaction. */
ulong compute_unit_price; /* Compute unit price for this transaction. */
ulong compute_meter; /* Remaining compute units */
ulong heap_size; /* Heap size for VMs for this transaction. */
ulong loaded_accounts_data_size_limit; /* Loaded accounts data size limit for this transaction. */
uint prioritization_fee_type; /* The type of prioritization fee to use. */
fd_txn_t const * txn_descriptor; /* Descriptor of the transaction. */
fd_rawtxn_b_t _txn_raw[1]; /* Raw bytes of the transaction. */
uint custom_err; /* When a custom error is returned, this is where the numeric value gets stashed */
uchar instr_stack_sz; /* Current depth of the instruction execution stack. */
fd_exec_instr_ctx_t instr_stack[FD_MAX_INSTRUCTION_STACK_DEPTH]; /* Instruction execution stack. */
fd_exec_instr_ctx_t * failed_instr;
int instr_err_idx;
ulong accounts_cnt; /* Number of account pubkeys accessed by this transaction. */
fd_pubkey_t accounts[128]; /* Array of account pubkeys accessed by this transaction. */
ulong executable_cnt; /* Number of BPF upgradeable loader accounts. */
fd_borrowed_account_t executable_accounts[128]; /* Array of BPF upgradeable loader program data accounts */
fd_borrowed_account_t borrowed_accounts[128]; /* Array of borrowed accounts accessed by this transaction. */
uchar nonce_accounts[128]; /* Nonce accounts in the txn to be saved */
uint num_instructions; /* Counter for number of instructions in txn */
fd_txn_return_data_t return_data; /* Data returned from `return_data` syscalls */
fd_vote_account_cache_t * vote_accounts_map; /* Cache of bank's deserialized vote accounts to support fork choice */
fd_vote_account_cache_entry_t * vote_accounts_pool; /* Memory pool for deserialized vote account cache */
ulong accounts_resize_delta; /* Transaction level tracking for account resizing */
fd_hash_t blake_txn_msg_hash; /* Hash of raw transaction message used by the status cache */
ulong execution_fee; /* Execution fee paid by the fee payer in the transaction */
ulong priority_fee; /* Priority fee paid by the fee payer in the transaction */

uchar dirty_vote_acc : 1; /* 1 if this transaction maybe modified a vote account */
uchar dirty_stake_acc : 1; /* 1 if this transaction maybe modified a stake account */
ulong accounts_cnt; /* Number of account pubkeys accessed by this transaction. */
fd_pubkey_t accounts[128]; /* Array of account pubkeys accessed by this transaction. */
ulong executable_cnt; /* Number of BPF upgradeable loader accounts. */
fd_borrowed_account_t executable_accounts[128]; /* Array of BPF upgradeable loader program data accounts */
fd_borrowed_account_t borrowed_accounts[128]; /* Array of borrowed accounts accessed by this transaction. */
uchar nonce_accounts[128]; /* Nonce accounts in the txn to be saved */
uint num_instructions; /* Counter for number of instructions in txn */
fd_txn_return_data_t return_data; /* Data returned from `return_data` syscalls */
fd_vote_account_cache_t * vote_accounts_map; /* Cache of bank's deserialized vote accounts to support fork choice */
fd_vote_account_cache_entry_t * vote_accounts_pool; /* Memory pool for deserialized vote account cache */
ulong accounts_resize_delta; /* Transaction level tracking for account resizing */
fd_hash_t blake_txn_msg_hash; /* Hash of raw transaction message used by the status cache */
ulong execution_fee; /* Execution fee paid by the fee payer in the transaction */
ulong priority_fee; /* Priority fee paid by the fee payer in the transaction */

uchar dirty_vote_acc : 1; /* 1 if this transaction maybe modified a vote account */
uchar dirty_stake_acc : 1; /* 1 if this transaction maybe modified a stake account */

fd_capture_ctx_t * capture_ctx;

Expand Down
84 changes: 60 additions & 24 deletions src/flamenco/runtime/fd_executor.c
Original file line number Diff line number Diff line change
Expand Up @@ -909,31 +909,68 @@ dump_instr_to_protobuf( fd_exec_txn_ctx_t *txn_ctx,
} FD_SCRATCH_SCOPE_END;
}

/* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L319-L357 */
int
fd_txn_ctx_push( fd_exec_txn_ctx_t * txn_ctx ) {
/* Earlier checks in the permalink are redundant since Agave maintains instr stack and trace accounts separately
https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L347-L351 */
if( txn_ctx->instr_trace_length>=FD_MAX_INSTRUCTION_TRACE_LENGTH ) {
return FD_EXECUTOR_INSTR_ERR_MAX_INSN_TRACE_LENS_EXCEEDED;
}
txn_ctx->instr_trace_length++;

/* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/sdk/src/transaction_context.rs#L352-L356 */
if( txn_ctx->instr_stack_sz>=FD_MAX_INSTRUCTION_STACK_DEPTH ) {
return FD_EXECUTOR_INSTR_ERR_CALL_DEPTH;
}
txn_ctx->instr_stack_sz++;

return FD_EXECUTOR_INSTR_SUCCESS;
}

/* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/program-runtime/src/invoke_context.rs#L246-L290 */
int
fd_instr_stack_push( fd_exec_txn_ctx_t * txn_ctx,
fd_instr_info_t const * instr ) {
/* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/program-runtime/src/invoke_context.rs#L256-L286 */
if( txn_ctx->instr_stack_sz ) {
/* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/program-runtime/src/invoke_context.rs#L261-L285 */
uchar contains = 0;
uchar is_last = 0;
for( uchar level=0; level<txn_ctx->instr_stack_sz; level++ ) {
fd_exec_instr_ctx_t * instr_ctx = &txn_ctx->instr_stack[level];
if( !memcmp( instr->program_id_pubkey.uc, instr_ctx->instr->program_id_pubkey.uc, sizeof(fd_pubkey_t) ) ) {
if( level == txn_ctx->instr_stack_sz-1 ) {
is_last = 1;
}
contains = 1;
}
}
/* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/program-runtime/src/invoke_context.rs#L282-L285 */
if( FD_UNLIKELY( contains && !is_last ) ) {
return FD_EXECUTOR_INSTR_ERR_REENTRANCY_NOT_ALLOWED;
}
}
/* https://github.com/anza-xyz/agave/blob/c4b42ab045860d7b13b3912eafb30e6d2f4e593f/program-runtime/src/invoke_context.rs#L289 */
return fd_txn_ctx_push( txn_ctx );
}

int
fd_execute_instr( fd_exec_txn_ctx_t * txn_ctx,
fd_instr_info_t * instr ) {
FD_SCRATCH_SCOPE_BEGIN {
ulong max_num_instructions = FD_FEATURE_ACTIVE( txn_ctx->slot_ctx, limit_max_instruction_trace_length ) ? FD_MAX_INSTRUCTION_TRACE_LENGTH : ULONG_MAX;
if( txn_ctx->num_instructions >= max_num_instructions ) {
return FD_EXECUTOR_INSTR_ERR_MAX_INSN_TRACE_LENS_EXCEEDED;
}
txn_ctx->num_instructions++;
fd_pubkey_t const * txn_accs = txn_ctx->accounts;

ulong starting_lamports_h = 0;
ulong starting_lamports_l = 0;
int err = fd_instr_info_sum_account_lamports( instr, &starting_lamports_h, &starting_lamports_l );
if( err ) {
return err;
}
instr->starting_lamports_h = starting_lamports_h;
instr->starting_lamports_l = starting_lamports_l;

fd_exec_instr_ctx_t * parent = NULL;
if( txn_ctx->instr_stack_sz )
parent = &txn_ctx->instr_stack[ txn_ctx->instr_stack_sz - 1 ];

fd_exec_instr_ctx_t * ctx = &txn_ctx->instr_stack[ txn_ctx->instr_stack_sz++ ];
int err = fd_instr_stack_push( txn_ctx, instr );
if( FD_UNLIKELY( err ) ) {
return err;
}

fd_exec_instr_ctx_t * ctx = &txn_ctx->instr_stack[ txn_ctx->instr_stack_sz - 1 ];
*ctx = (fd_exec_instr_ctx_t) {
.instr = instr,
.txn_ctx = txn_ctx,
Expand All @@ -948,21 +985,20 @@ fd_execute_instr( fd_exec_txn_ctx_t * txn_ctx,
.child_cnt = 0U,
};

/* Add the instruction to the trace */
txn_ctx->instr_trace[ txn_ctx->instr_trace_length++ ] = (fd_exec_instr_trace_entry_t) {
txn_ctx->instr_trace[ txn_ctx->instr_trace_length - 1 ] = (fd_exec_instr_trace_entry_t) {
.instr_info = instr,
.stack_height = txn_ctx->instr_stack_sz,
};

// defense in depth
if( instr->program_id >= txn_ctx->txn_descriptor->acct_addr_cnt + txn_ctx->txn_descriptor->addr_table_adtl_cnt ) {
FD_LOG_WARNING(( "INVALID PROGRAM ID, RUNTIME BUG!!!" ));
int exec_result = FD_EXECUTOR_INSTR_ERR_NOT_ENOUGH_ACC_KEYS;
ulong starting_lamports_h = 0;
ulong starting_lamports_l = 0;
err = fd_instr_info_sum_account_lamports( instr, &starting_lamports_h, &starting_lamports_l );
if( FD_UNLIKELY( err ) ) {
txn_ctx->instr_stack_sz--;

FD_LOG_WARNING(( "instruction executed unsuccessfully: error code %d", exec_result ));
return exec_result;
return err;
}
instr->starting_lamports_h = starting_lamports_h;
instr->starting_lamports_l = starting_lamports_l;

fd_exec_instr_fn_t native_prog_fn = fd_executor_lookup_native_program( &txn_ctx->borrowed_accounts[ instr->program_id ] );
fd_pubkey_t const * program_id = &txn_accs[ instr->program_id ];
Expand Down
2 changes: 1 addition & 1 deletion src/flamenco/runtime/tests/fd_exec_instr_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ fd_exec_test_instr_context_create( fd_exec_instr_test_runner_t * runner,

/* Initial variables */
txn_ctx->loaded_accounts_data_size_limit = FD_VM_LOADED_ACCOUNTS_DATA_SIZE_LIMIT;
txn_ctx->heap_size = fd_ulong_max( txn_ctx->heap_size, FD_VM_HEAP_SIZE ); /* FIXME: bound this to FD_VM_HEAP_MAX?*/
txn_ctx->heap_size = FD_VM_HEAP_SIZE;

/* Set up epoch context */
fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( epoch_ctx );
Expand Down

0 comments on commit c002f61

Please sign in to comment.