Skip to content

Commit

Permalink
flamenco, fuzz: CPI execution mocking
Browse files Browse the repository at this point in the history
  • Loading branch information
ravyu-jump committed Aug 29, 2024
1 parent e4c9b40 commit db168ac
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 102 deletions.
43 changes: 43 additions & 0 deletions contrib/tool/dump_cpi.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
diff --git a/src/flamenco/vm/syscall/fd_vm_syscall_cpi_common.c b/src/flamenco/vm/syscall/fd_vm_syscall_cpi_common.c
index 336b002c2..0403a4909 100644
--- a/src/flamenco/vm/syscall/fd_vm_syscall_cpi_common.c
+++ b/src/flamenco/vm/syscall/fd_vm_syscall_cpi_common.c
@@ -594,6 +594,10 @@ VM_SYSCALL_CPI_ENTRYPOINT( void * _vm,

FD_VM_CU_UPDATE( vm, FD_VM_INVOKE_UNITS );

+ fd_exec_test_syscall_context_t sys_ctx = FD_EXEC_TEST_SYSCALL_CONTEXT_INIT_DEFAULT;
+ dump_vm_cpi_state(vm, STRINGIFY(FD_EXPAND_THEN_CONCAT2(sol_invoke_signed_, VM_SYSCALL_CPI_ABI)),
+ instruction_va, acct_infos_va, acct_info_cnt, signers_seeds_va, signers_seeds_cnt, &sys_ctx);
+
/* Pre-flight checks ************************************************/
int err = fd_vm_syscall_cpi_preflight_check( signers_seeds_cnt, acct_info_cnt, vm->instr_ctx->slot_ctx );
if( FD_UNLIKELY( err ) ) return err;
@@ -732,18 +736,27 @@ VM_SYSCALL_CPI_ENTRYPOINT( void * _vm,
}
}

+ sys_ctx.has_exec_effects = 1;
+ sys_ctx.exec_effects.modified_accounts_count = (pb_size_t) caller_accounts_to_update_len;
+ sys_ctx.exec_effects.modified_accounts = fd_scratch_alloc( 8UL, sizeof(fd_exec_test_acct_state_t) * caller_accounts_to_update_len );
+
/* Update the caller accounts with any changes made by the callee during CPI execution */
for( ulong i=0UL; i<caller_accounts_to_update_len; i++ ) {
/* https://github.com/firedancer-io/solana/blob/508f325e19c0fd8e16683ea047d7c1a85f127e74/programs/bpf_loader/src/syscalls/cpi.rs#L939-L943 */
/* We only want to update the writable accounts, because the non-writable
caller accounts can't be changed during a CPI execution. */
if( fd_instr_acc_is_writable_idx( vm->instr_ctx->instr, callee_account_keys[i] ) ) {
+ dump_acct_to_state( vm->instr_ctx->instr, callee_account_keys[i], &sys_ctx.exec_effects.modified_accounts[i] );
fd_pubkey_t const * callee = &vm->instr_ctx->instr->acct_pubkeys[callee_account_keys[i]];
err = VM_SYSCALL_CPI_UPDATE_CALLER_ACC_FUNC(vm, &acc_infos[caller_accounts_to_update[i]], (uchar)callee_account_keys[i], callee);
if( FD_UNLIKELY( err ) ) return err;
}
}

+ char filename[256];
+ gen_cpi_state_filename( &vm->instr_ctx->instr->program_id_pubkey, VM_SYSCALL_CPI_INSTR_DATA_LEN( cpi_instruction ), filename );
+ dump_pb_to_file( &sys_ctx, filename, FD_EXEC_TEST_SYSCALL_CONTEXT_FIELDS );
+
caller_lamports_h = 0UL;
caller_lamports_l = 0UL;
err = fd_instr_info_sum_account_lamports( vm->instr_ctx->instr, &caller_lamports_h, &caller_lamports_l );
20 changes: 20 additions & 0 deletions contrib/tool/dump_cpi_on_replay.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/bash
# This script is used to dump the a SyscallContext during CPIs executed on a ledger.
# Run this from the root of the firedancer repository
# Must have a "dump/vm_cpi_state" directory in the root of the firedancer repository

SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
set -e

# Apply the patch and build the project
git apply $SCRIPT_DIR/dump_cpi.patch
make -j bin lib

# Run the replay command
build/native/gcc/bin/fd_ledger --cmd replay --verify-acc-hash 1 --rocksdb dump/mainnet-265330432/rocksdb --index-max 5000000 --end-slot 265330433 --cluster-version 1190 --page-cnt 30 --funk-page-cnt 16 --snapshot dump/mainnet-265330432/snapshot-265330431-BMvcRhxNoRtkZ5KLEKhhXM6GiWdTgdkoGLMe86xY4rF.tar.zst --allocator wksp --tile-cpus 5-21

# Revert the patch and clean the project
git apply -R contrib/tool/dump_cpi.patch
make -j clean

set +e
71 changes: 64 additions & 7 deletions src/flamenco/runtime/tests/fd_exec_instr_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -1659,6 +1659,8 @@ fd_sbpf_program_load_test_run( FD_PARAM_UNUSED fd_exec_instr_test_runner_t * run
return actual_end - (ulong) output_buf;
}

static fd_exec_test_instr_effects_t const * cpi_exec_effects = NULL;

ulong
fd_exec_vm_syscall_test_run( fd_exec_instr_test_runner_t * runner,
void const * input_,
Expand All @@ -1674,9 +1676,10 @@ fd_exec_vm_syscall_test_run( fd_exec_instr_test_runner_t * runner,
const fd_exec_test_instr_context_t * input_instr_ctx = &input->instr_ctx;
fd_exec_instr_ctx_t ctx[1];
// Skip extra checks for non-CPI syscalls
bool skip_extra_checks = strncmp( (const char *)input->syscall_invocation.function_name.bytes, "sol_invoke_signed", 17 );
int skip_extra_checks = strncmp( (const char *)input->syscall_invocation.function_name.bytes, "sol_invoke_signed", 17 );
uint is_cpi = !skip_extra_checks;

if( !fd_exec_test_instr_context_create( runner, ctx, input_instr_ctx, alloc, skip_extra_checks ) )
if( !fd_exec_test_instr_context_create( runner, ctx, input_instr_ctx, alloc, !!skip_extra_checks ) )
goto error;
fd_valloc_t valloc = fd_scratch_virtual();

Expand All @@ -1702,6 +1705,9 @@ fd_exec_vm_syscall_test_run( fd_exec_instr_test_runner_t * runner,
if( !input->has_vm_ctx ) {
goto error;
}
if( input->has_exec_effects ){
cpi_exec_effects = &input->exec_effects;
}
uchar * rodata = input->vm_ctx.rodata ? input->vm_ctx.rodata->bytes : NULL;
ulong rodata_sz = input->vm_ctx.rodata ? input->vm_ctx.rodata->size : 0UL;

Expand All @@ -1713,7 +1719,7 @@ fd_exec_vm_syscall_test_run( fd_exec_instr_test_runner_t * runner,
input_regions_count = setup_vm_input_regions( input_regions, input->vm_ctx.input_data_regions, input->vm_ctx.input_data_regions_count );
}

if (input->vm_ctx.heap_max > FD_VM_HEAP_DEFAULT) {
if (input->vm_ctx.heap_max > FD_VM_HEAP_MAX) {
goto error;
}

Expand Down Expand Up @@ -1792,7 +1798,14 @@ fd_exec_vm_syscall_test_run( fd_exec_instr_test_runner_t * runner,
int syscall_err = syscall->func( vm, vm->reg[1], vm->reg[2], vm->reg[3], vm->reg[4], vm->reg[5], &vm->reg[0] );

/* Capture the effects */
effects->error = -syscall_err;

/* Ignore Lamport mismatches since Agave performs this check outside of the CPI */
if( is_cpi && syscall_err == FD_VM_CPI_ERR_LAMPORTS_MISMATCH ) {
syscall_err = 0;
} else {
syscall_err = -syscall_err;
}

effects->r0 = syscall_err ? 0 : vm->reg[0]; // Save only on success
effects->cu_avail = (ulong)vm->cu;

Expand Down Expand Up @@ -1839,12 +1852,14 @@ fd_exec_vm_syscall_test_run( fd_exec_instr_test_runner_t * runner,
/* Return the effects */
ulong actual_end = tmp_end + input_regions_size;
fd_exec_test_instr_context_destroy( runner, ctx, wksp, alloc );
cpi_exec_effects = NULL;

*output = effects;
return actual_end - (ulong)output_buf;

error:
fd_exec_test_instr_context_destroy( runner, ctx, wksp, alloc );
cpi_exec_effects = NULL;
return 0;
}

Expand All @@ -1854,8 +1869,50 @@ int
__wrap_fd_execute_instr( fd_exec_txn_ctx_t * txn_ctx,
fd_instr_info_t * instr_info )
{
(void)(txn_ctx);
(void)(instr_info);
FD_LOG_WARNING(( "fd_execute_instr is disabled" ));
static const pb_byte_t zero_blk[32] = {0};

if( cpi_exec_effects == NULL ) {
FD_LOG_WARNING(( "fd_execute_instr is disabled" ));
return FD_EXECUTOR_INSTR_SUCCESS;
}

// Iterate through instruction accounts
for( ushort i = 0UL; i < instr_info->acct_cnt; ++i ) {
uchar idx_in_txn = instr_info->acct_txn_idxs[i];
fd_pubkey_t * acct_pubkey = &instr_info->acct_pubkeys[i];

fd_borrowed_account_t * acct = NULL;
/* Find (first) account in cpi_exec_effects->modified_accounts that matches pubkey*/
for( uint j = 0UL; j < cpi_exec_effects->modified_accounts_count; ++j ) {
fd_exec_test_acct_state_t * acct_state = &cpi_exec_effects->modified_accounts[j];
if( memcmp( acct_state->address, acct_pubkey, sizeof(fd_pubkey_t) ) != 0 ) continue;

/* Fetch borrowed account */
int err = fd_txn_borrowed_account_modify_idx( txn_ctx,
idx_in_txn,
/* Do not reallocate if data is not going to be modified */
acct_state->data ? acct_state->data->size : 0UL,
&acct );
if( err ) break;

/* Update account state */
acct->meta->info.lamports = acct_state->lamports;
acct->meta->info.executable = acct_state->executable;
acct->meta->info.rent_epoch = acct_state->rent_epoch;

/* TODO: use lower level API (i.e., fd_borrowed_account_resize) to avoid memcpy here */
if( acct_state->data ){
fd_memcpy( acct->data, acct_state->data->bytes, acct_state->data->size );
acct->meta->dlen = acct_state->data->size;
}

/* Follow solfuzz-agave, which skips if pubkey is malformed */
if( memcmp( acct_state->owner, zero_blk, sizeof(fd_pubkey_t) ) != 0 ) {
fd_memcpy( acct->meta->info.owner, acct_state->owner, sizeof(fd_pubkey_t) );
}

break;
}
}
return FD_EXECUTOR_INSTR_SUCCESS;
}
12 changes: 9 additions & 3 deletions src/flamenco/runtime/tests/generated/vm.pb.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/flamenco/vm/fd_vm_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
#define FD_VM_CPI_ERR_INSTR_TOO_LARGE (-41) /* detected too many account infos meta */
#define FD_VM_CPI_ERR_INSTR_DATA_TOO_LARGE (-42) /* detected instruction data too large */
#define FD_VM_CPI_ERR_TOO_MANY_ACC_METAS (-43) /* detected too many account metas */
#define FD_VM_CPI_ERR_LAMPORTS_MISMATCH (-44) /* detected lamports mismatch */

FD_PROTOTYPES_BEGIN

Expand Down
Loading

0 comments on commit db168ac

Please sign in to comment.