Skip to content

Commit

Permalink
flamenco, fuzz: CPI syscall fuzzing support
Browse files Browse the repository at this point in the history
  • Loading branch information
ravyu-jump committed Aug 5, 2024
1 parent 65c003b commit 39cecc5
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 25 deletions.
14 changes: 7 additions & 7 deletions src/flamenco/runtime/fd_executor.c
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ dump_sorted_features( const fd_features_t * features, fd_exec_test_feature_set_t
}

static void
dump_account_state( fd_borrowed_account_t * borrowed_account,
dump_account_state( fd_borrowed_account_t const * borrowed_account,
fd_exec_test_acct_state_t * output_account ) {
// Address
fd_memcpy(output_account->address, borrowed_account->pubkey, sizeof(fd_pubkey_t));
Expand All @@ -515,10 +515,10 @@ dump_account_state( fd_borrowed_account_t * borrowed_account,
output_account->has_seed_addr = false;
}

static void
create_instr_context_protobuf_from_instructions( fd_exec_test_instr_context_t * instr_context,
fd_exec_txn_ctx_t *txn_ctx,
fd_instr_info_t *instr ) {
void
fd_create_instr_context_protobuf_from_instructions( fd_exec_test_instr_context_t * instr_context,
fd_exec_txn_ctx_t const *txn_ctx,
fd_instr_info_t const *instr ) {
/*
NOTE: Calling this function requires the caller to have a scratch frame ready (see dump_instr_to_protobuf)
*/
Expand Down Expand Up @@ -546,7 +546,7 @@ create_instr_context_protobuf_from_instructions( fd_exec_test_instr_context_t *
instr_context->accounts = fd_scratch_alloc(alignof(fd_exec_test_acct_state_t), (instr_context->accounts_count + num_sysvar_entries + txn_ctx->executable_cnt) * sizeof(fd_exec_test_acct_state_t));
for( ulong i = 0; i < txn_ctx->accounts_cnt; i++ ) {
// Copy account information over
fd_borrowed_account_t * borrowed_account = &txn_ctx->borrowed_accounts[i];
fd_borrowed_account_t const * borrowed_account = &txn_ctx->borrowed_accounts[i];
fd_exec_test_acct_state_t * output_account = &instr_context->accounts[i];
dump_account_state( borrowed_account, output_account );
}
Expand Down Expand Up @@ -670,7 +670,7 @@ dump_instr_to_protobuf( fd_exec_txn_ctx_t *txn_ctx,
}

fd_exec_test_instr_context_t instr_context = FD_EXEC_TEST_INSTR_CONTEXT_INIT_DEFAULT;
create_instr_context_protobuf_from_instructions( &instr_context, txn_ctx, instr );
fd_create_instr_context_protobuf_from_instructions( &instr_context, txn_ctx, instr );

/* Output to file */
ulong out_buf_size = 100 * 1024 * 1024;
Expand Down
10 changes: 10 additions & 0 deletions src/flamenco/runtime/fd_executor.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@

FD_PROTOTYPES_BEGIN

/* Create an InstrContext protobuf struct from a given
transaction context and instr info.
NOTE: Calling this function requires the caller to have a scratch
frame ready and pushed (see dump_instr_to_protobuf) */
void
fd_create_instr_context_protobuf_from_instructions( fd_exec_test_instr_context_t * instr_context,
fd_exec_txn_ctx_t const * txn_ctx,
fd_instr_info_t const * instr );

/* fd_exec_instr_fn_t processes an instruction. Returns an error code
in FD_EXECUTOR_INSTR_{ERR_{...},SUCCESS}. */

Expand Down
12 changes: 9 additions & 3 deletions src/flamenco/runtime/tests/fd_exec_instr_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ _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_VM_HEAP_SIZE;
txn_ctx->heap_size = fd_ulong_max( txn_ctx->heap_size, FD_VM_HEAP_SIZE ); /* FIXME: bound this to FD_VM_HEAP_MAX?*/

/* Set up epoch context */
fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( epoch_ctx );
Expand Down Expand Up @@ -509,6 +509,9 @@ _instr_context_create( fd_exec_instr_test_runner_t * runner,
}
info->acct_cnt = (uchar)test_ctx->instr_accounts_count;

// FIXME: Specifically for CPI syscalls, flag guard this?
fd_instr_info_sum_account_lamports( info, &info->starting_lamports_h, &info->starting_lamports_l );

/* This function is used to create context both for instructions and for syscalls,
however some of the remaining checks are only relevant for program instructions. */
if( !is_syscall ) {
Expand Down Expand Up @@ -1627,7 +1630,10 @@ fd_exec_vm_syscall_test_run( fd_exec_instr_test_runner_t * runner,
/* Create execution context */
const fd_exec_test_instr_context_t * input_instr_ctx = &input->instr_ctx;
fd_exec_instr_ctx_t ctx[1];
if( !_instr_context_create( runner, ctx, input_instr_ctx, alloc, true ) )
// Skip extra checks for non-CPI syscalls
bool skip_extra_checks = strncmp( (const char *)input->syscall_invocation.function_name.bytes, "sol_invoke_signed", 17 );

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

Expand Down Expand Up @@ -1762,7 +1768,7 @@ fd_exec_vm_syscall_test_run( fd_exec_instr_test_runner_t * runner,

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

effects->heap = FD_SCRATCH_ALLOC_APPEND(
Expand Down
38 changes: 29 additions & 9 deletions src/flamenco/runtime/tests/fd_exec_sol_compat.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ typedef struct {
ulong supported_feature_cnt;
} sol_compat_features_t;

typedef struct {
uint16_t validator_type;
} sol_compat_metadata_t;

static sol_compat_metadata_t metadata = { .validator_type = 1 /* Firedancer */ };
static sol_compat_features_t features;
static uchar * smem;
static const ulong smax = 1UL<<30;
Expand Down Expand Up @@ -93,6 +98,16 @@ sol_compat_check_wksp_usage( void ) {
}
}

sol_compat_features_t const *
sol_compat_get_features_v1( void ) {
return &features;
}

sol_compat_metadata_t const *
sol_compat_get_metadata_v1( void ) {
return &metadata;
}

fd_exec_instr_test_runner_t *
sol_compat_setup_scratch_and_runner( void * fmem ) {
// Setup scratch
Expand Down Expand Up @@ -152,8 +167,7 @@ sol_compat_execute_wrapper( fd_exec_instr_test_runner_t * runner,
void * input,
void ** output,
exec_test_run_fn_t * exec_test_run_fn ) {
// fd_scratch_push();
do {
FD_SCRATCH_SCOPE_BEGIN {
ulong out_bufsz = 100000000; /* 100 MB */
void * out0 = fd_scratch_prepare( 1UL );
assert( out_bufsz < fd_scratch_free() );
Expand All @@ -163,8 +177,7 @@ sol_compat_execute_wrapper( fd_exec_instr_test_runner_t * runner,
*output = NULL;
break;
}
} while(0);
// fd_scratch_pop();
} FD_SCRATCH_SCOPE_END;
}

/*
Expand Down Expand Up @@ -484,10 +497,6 @@ sol_compat_elf_loader_v1( uchar * out,
return ok;
}

sol_compat_features_t const *
sol_compat_get_features_v1( void ) {
return &features;
}

int
sol_compat_vm_syscall_execute_v1( uchar * out,
Expand Down Expand Up @@ -530,7 +539,7 @@ int
sol_compat_vm_validate_v1( uchar * out,
ulong * out_sz,
uchar const * in,
ulong in_sz) {
ulong in_sz ) {
// Setup
ulong fmem[ 64 ];
fd_exec_instr_test_runner_t * runner = sol_compat_setup_scratch_and_runner( fmem );
Expand Down Expand Up @@ -562,3 +571,14 @@ sol_compat_vm_validate_v1( uchar * out,

return ok;
}

/* We still need a separate entrypoint since other harnesses (namely sfuzz-agave)
do something other than wrap their vm_syscall equivalent */
int
sol_compat_vm_cpi_syscall_v1( uchar * out,
ulong * out_sz,
uchar const * in,
ulong in_sz ) {
/* Just a wrapper to vm_syscall_execute_v1 */
return sol_compat_vm_syscall_execute_v1( out, out_sz, in, in_sz );
}
10 changes: 10 additions & 0 deletions src/flamenco/vm/fd_vm_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,16 @@
#define FD_VM_SH_OVERFLOW (-37) /* detected a shift overflow, equivalent to VeriferError::ShiftWithOverflow */
#define FD_VM_TEXT_SZ_UNALIGNED (-38) /* detected a text section that is not a multiple of 8 */

/* Error codes related to CPI syscall.
FIXME: Should this be in fd_executor_err.h instead?
Or a separate file for syscall errors? */

#define FD_VM_CPI_ERR_TOO_MANY_SIGNERS (-39) /* detected too many signers */
#define FD_VM_CPI_ERR_TOO_MANY_ACC_INFOS (-40) /* detected too many account infos */
#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 */

FD_PROTOTYPES_BEGIN

/* fd_vm_strerror converts an FD_VM_SUCCESS / FD_VM_ERR_* code into
Expand Down
137 changes: 131 additions & 6 deletions src/flamenco/vm/syscall/fd_vm_syscall_cpi.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,130 @@
#include "../../runtime/fd_account.h"
#include "../../runtime/fd_executor.h"
#include "../../runtime/fd_account_old.h" /* FIXME: remove this and update to use new APIs */
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
#include <errno.h>
#include "../../nanopb/pb_encode.h"
#include "../../runtime/tests/generated/vm.pb.h"
#include "../../runtime/tests/fd_exec_instr_test.h"

#define STRINGIFY(x) TOSTRING(x)
#define TOSTRING(x) #x

/* Captures the state of the VM (including the instruction context).
Meant to be invoked at the start of the VM_SYSCALL_CPI_ENTRYPOINT like so:
```
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);
```
Assumes that a `vm_cp_state` directory exists in the current working directory. Generates a
unique dump for combination of (tile_id, caller_pubkey, instr_sz). */

static FD_FN_UNUSED void
dump_vm_cpi_state(fd_vm_t *vm,
char const * fn_name,
ulong instruction_va,
ulong acct_infos_va,
ulong acct_info_cnt,
ulong signers_seeds_va,
ulong signers_seeds_cnt ) {
char filename[100];
fd_instr_info_t const *instr = vm->instr_ctx->instr;
sprintf(filename, "vm_cpi_state/%lu_%lu%lu_%lu.sysctx", fd_tile_id(), instr->program_id_pubkey.ul[0], instr->program_id_pubkey.ul[1], instr->data_sz);

// Check if file exists
if( access (filename, F_OK) != -1 ) {
return;
}

fd_exec_test_syscall_context_t sys_ctx = FD_EXEC_TEST_SYSCALL_CONTEXT_INIT_ZERO;
sys_ctx.has_instr_ctx = true;
sys_ctx.has_vm_ctx = true;
sys_ctx.has_syscall_invocation = true;

// Copy function name
sys_ctx.syscall_invocation.function_name.size = fd_uint_min( (uint) strlen(fn_name), sizeof(sys_ctx.syscall_invocation.function_name.bytes) );
fd_memcpy( sys_ctx.syscall_invocation.function_name.bytes,
fn_name,
sys_ctx.syscall_invocation.function_name.size );

// VM Ctx integral fields
sys_ctx.vm_ctx.r1 = instruction_va;
sys_ctx.vm_ctx.r2 = acct_infos_va;
sys_ctx.vm_ctx.r3 = acct_info_cnt;
sys_ctx.vm_ctx.r4 = signers_seeds_va;
sys_ctx.vm_ctx.r5 = signers_seeds_cnt;

sys_ctx.vm_ctx.rodata_text_section_length = vm->text_sz;
sys_ctx.vm_ctx.rodata_text_section_offset = vm->text_off;

sys_ctx.vm_ctx.heap_max = vm->heap_max; /* should be equiv. to txn_ctx->heap_sz */

FD_SCRATCH_SCOPE_BEGIN{
sys_ctx.vm_ctx.rodata = fd_scratch_alloc( 8UL, PB_BYTES_ARRAY_T_ALLOCSIZE(vm->rodata_sz) );
sys_ctx.vm_ctx.rodata->size = (pb_size_t) vm->rodata_sz;
fd_memcpy( sys_ctx.vm_ctx.rodata->bytes, vm->rodata, vm->rodata_sz );

pb_size_t stack_sz = (pb_size_t) ( (vm->frame_cnt + 1)*FD_VM_STACK_GUARD_SZ*2 );
sys_ctx.syscall_invocation.stack_prefix = fd_scratch_alloc( 8UL, PB_BYTES_ARRAY_T_ALLOCSIZE(stack_sz) );
sys_ctx.syscall_invocation.stack_prefix->size = stack_sz;
fd_memcpy( sys_ctx.syscall_invocation.stack_prefix->bytes, vm->stack, stack_sz );

sys_ctx.syscall_invocation.heap_prefix = fd_scratch_alloc( 8UL, PB_BYTES_ARRAY_T_ALLOCSIZE(vm->heap_max) );
sys_ctx.syscall_invocation.heap_prefix->size = (pb_size_t) vm->instr_ctx->txn_ctx->heap_size;
fd_memcpy( sys_ctx.syscall_invocation.heap_prefix->bytes, vm->heap, vm->instr_ctx->txn_ctx->heap_size );

sys_ctx.vm_ctx.input_data_regions_count = vm->input_mem_regions_cnt;
sys_ctx.vm_ctx.input_data_regions = fd_scratch_alloc( 8UL, sizeof(fd_exec_test_input_data_region_t) * vm->input_mem_regions_cnt );
for( ulong i=0UL; i<vm->input_mem_regions_cnt; i++ ) {
sys_ctx.vm_ctx.input_data_regions[i].content = fd_scratch_alloc( 8UL, PB_BYTES_ARRAY_T_ALLOCSIZE(vm->input_mem_regions[i].region_sz) );
sys_ctx.vm_ctx.input_data_regions[i].content->size = (pb_size_t) vm->input_mem_regions[i].region_sz;
fd_memcpy( sys_ctx.vm_ctx.input_data_regions[i].content->bytes, (uchar *) vm->input_mem_regions[i].haddr, vm->input_mem_regions[i].region_sz );
sys_ctx.vm_ctx.input_data_regions[i].offset = vm->input_mem_regions[i].vaddr_offset;
sys_ctx.vm_ctx.input_data_regions[i].is_writable = vm->input_mem_regions[i].is_writable;
}

fd_create_instr_context_protobuf_from_instructions( &sys_ctx.instr_ctx,
vm->instr_ctx->txn_ctx,
vm->instr_ctx->instr );

// Serialize the protobuf to file (using mmap)
size_t pb_alloc_size = 100 * 1024 * 1024; // 100MB (largest so far is 19MB)
FILE *f = fopen(filename, "wb+");
if( ftruncate(fileno(f), (off_t) pb_alloc_size) != 0 ) {
FD_LOG_WARNING(("Failed to resize file %s", filename));
fclose(f);
return;
}

uchar *pb_alloc = mmap( NULL,
pb_alloc_size,
PROT_READ | PROT_WRITE,
MAP_SHARED,
fileno(f),
0 /* offset */);
if( pb_alloc == MAP_FAILED ) {
FD_LOG_WARNING(( "Failed to mmap file %d", errno ));
fclose(f);
return;
}

pb_ostream_t stream = pb_ostream_from_buffer(pb_alloc, pb_alloc_size);
if( !pb_encode( &stream, FD_EXEC_TEST_SYSCALL_CONTEXT_FIELDS, &sys_ctx ) ) {
FD_LOG_WARNING(( "Failed to encode instruction context" ));
}
// resize file to actual size
if( ftruncate( fileno(f), (off_t) stream.bytes_written ) != 0 ) {
FD_LOG_WARNING(( "Failed to resize file %s", filename ));
}

fclose(f);

} FD_SCRATCH_SCOPE_END;
}

/* FIXME: ALGO EFFICIENCY */
static inline int
Expand Down Expand Up @@ -266,23 +390,24 @@ fd_vm_syscall_cpi_preflight_check( ulong signers_seeds_cnt,
if( FD_UNLIKELY( signers_seeds_cnt > FD_CPI_MAX_SIGNER_CNT ) ) {
// TODO: return SyscallError::TooManySigners
FD_LOG_WARNING(("TODO: return too many signers" ));
return FD_VM_ERR_INVAL;
return FD_VM_CPI_ERR_TOO_MANY_SIGNERS;
}

/* https://github.com/solana-labs/solana/blob/eb35a5ac1e7b6abe81947e22417f34508f89f091/programs/bpf_loader/src/syscalls/cpi.rs#L996-L997 */
if( FD_FEATURE_ACTIVE( slot_ctx, loosen_cpi_size_restriction ) ) {
if( FD_UNLIKELY( acct_info_cnt > FD_CPI_MAX_ACCOUNT_INFOS ) ) {
// TODO: return SyscallError::MaxInstructionAccountInfosExceeded
FD_LOG_WARNING(( "TODO: return max instruction account infos exceeded" ));
return FD_VM_ERR_INVAL;
return FD_VM_CPI_ERR_TOO_MANY_ACC_INFOS;
}
} else {
ulong adjusted_len = fd_ulong_sat_mul( acct_info_cnt, sizeof( fd_pubkey_t ) );
if ( FD_UNLIKELY( adjusted_len > FD_VM_MAX_CPI_INSTRUCTION_SIZE ) ) {
/* Cap the number of account_infos a caller can pass to approximate
maximum that accounts that could be passed in an instruction
TODO: return SyscallError::TooManyAccounts */
return FD_VM_ERR_INVAL;
FD_LOG_WARNING(( "TODO: return max instruction account infos exceeded" ));
return FD_VM_CPI_ERR_TOO_MANY_ACC_INFOS;
}
}

Expand All @@ -302,20 +427,20 @@ fd_vm_syscall_cpi_check_instruction( fd_vm_t const * vm,
if( FD_UNLIKELY( data_sz > FD_CPI_MAX_INSTRUCTION_DATA_LEN ) ) {
FD_LOG_WARNING(( "cpi: data too long (%#lx)", data_sz ));
// SyscallError::MaxInstructionDataLenExceeded
return FD_VM_ERR_INVAL;
return FD_VM_CPI_ERR_INSTR_DATA_TOO_LARGE;
}
if( FD_UNLIKELY( acct_cnt > FD_CPI_MAX_INSTRUCTION_ACCOUNTS ) ) {
FD_LOG_WARNING(( "cpi: too many accounts (%#lx)", acct_cnt ));
// SyscallError::MaxInstructionAccountsExceeded
return FD_VM_ERR_INVAL;
return FD_VM_CPI_ERR_TOO_MANY_ACC_METAS;
}
} else {
// https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/programs/bpf_loader/src/syscalls/cpi.rs#L1114
ulong tot_sz = fd_ulong_sat_add( fd_ulong_sat_mul( FD_VM_RUST_ACCOUNT_META_SIZE, acct_cnt ), data_sz );
if ( FD_UNLIKELY( tot_sz > FD_VM_MAX_CPI_INSTRUCTION_SIZE ) ) {
FD_LOG_WARNING(( "cpi: instruction too long (%#lx)", tot_sz ));
// SyscallError::InstructionTooLarge
return FD_VM_ERR_INVAL;
return FD_VM_CPI_ERR_INSTR_TOO_LARGE;
}
}

Expand Down

0 comments on commit 39cecc5

Please sign in to comment.