LCOV - code coverage report
Current view: top level - flamenco/runtime/tests - fd_vm_test.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 274 0.0 %
Date: 2025-03-20 12:08:36 Functions: 0 5 0.0 %

          Line data    Source code
       1             : #include "fd_vm_test.h"
       2             : #include "fd_exec_instr_test.h"
       3             : #include "../fd_system_ids.h"
       4             : #include "generated/vm.pb.h"
       5             : #include "../program/fd_bpf_loader_serialization.h"
       6             : 
       7             : 
       8             : int
       9             : fd_vm_syscall_noop( void * _vm,
      10             :                     ulong arg0,
      11             :                     ulong arg1,
      12             :                     ulong arg2,
      13             :                     ulong arg3,
      14             :                     ulong arg4,
      15           0 :                     ulong* _ret){
      16             :   /* TODO: have input message determine CUs to deduct?
      17             :   fd_vm_t * vm = (fd_vm_t *) _vm;
      18             :   vm->cu = vm->cu - 5;
      19             :   */
      20             : 
      21           0 :   (void) _vm;
      22           0 :   (void) arg0;
      23           0 :   (void) arg1;
      24           0 :   (void) arg2;
      25           0 :   (void) arg3;
      26           0 :   (void) arg4;
      27           0 :   *_ret = 0;
      28           0 :   return 0;
      29           0 : }
      30             : 
      31             : void
      32             : fd_setup_vm_acc_region_metas( fd_vm_acc_region_meta_t * acc_regions_meta,
      33             :                               fd_vm_t *                 vm,
      34           0 :                               fd_exec_instr_ctx_t *     instr_ctx ) {
      35             :   /* cur_region is used to figure out what acc region index the account
      36             :      corresponds to. */
      37           0 :   uint cur_region = 0UL;
      38           0 :   for( ulong i=0UL; i<instr_ctx->instr->acct_cnt; i++ ) {
      39           0 :     cur_region++;
      40           0 :     fd_txn_account_t const * acc = instr_ctx->instr->accounts[i];
      41           0 :     acc_regions_meta[i].region_idx          = cur_region;
      42           0 :     acc_regions_meta[i].has_data_region     = acc->const_meta->dlen>0UL;
      43           0 :     acc_regions_meta[i].has_resizing_region = !vm->is_deprecated;
      44           0 :     if( acc->const_meta->dlen>0UL ) {
      45           0 :       cur_region++;
      46           0 :     }
      47           0 :     if( vm->is_deprecated ) {
      48           0 :       cur_region--;
      49           0 :     }
      50           0 :   }
      51           0 : }
      52             : 
      53             : ulong
      54             : fd_exec_vm_interp_test_run( fd_exec_instr_test_runner_t *         runner,
      55             :                             void const *                          input_,
      56             :                             void **                               output_,
      57             :                             void *                                output_buf,
      58           0 :                             ulong                                 output_bufsz ) {
      59           0 :   fd_exec_test_syscall_context_t const * input = fd_type_pun_const( input_ );
      60           0 :   fd_exec_test_syscall_effects_t      ** output = fd_type_pun( output_ );
      61             : 
      62             :   /* Create execution context */
      63           0 :   const fd_exec_test_instr_context_t * input_instr_ctx = &input->instr_ctx;
      64           0 :   fd_exec_instr_ctx_t instr_ctx[1];
      65           0 :   if( !fd_exec_test_instr_context_create( runner, instr_ctx, input_instr_ctx, true /* is_syscall avoids certain checks we don't want */ ) ) {
      66           0 :     fd_exec_test_instr_context_destroy( runner, instr_ctx );
      67           0 :     return 0UL;
      68           0 :   }
      69             : 
      70           0 :   if( !( input->has_vm_ctx ) ) {
      71           0 :     fd_exec_test_instr_context_destroy( runner, instr_ctx );
      72           0 :     return 0UL;
      73           0 :   }
      74             : 
      75           0 :   fd_spad_t * spad   = fd_exec_instr_test_runner_get_spad( runner );
      76           0 :   fd_valloc_t valloc = fd_spad_virtual( spad );
      77             : 
      78             :   /* Create effects */
      79           0 :   ulong output_end = (ulong) output_buf + output_bufsz;
      80           0 :   FD_SCRATCH_ALLOC_INIT( l, output_buf );
      81           0 :   fd_exec_test_syscall_effects_t * effects =
      82           0 :     FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_syscall_effects_t),
      83           0 :                                 sizeof (fd_exec_test_syscall_effects_t) );
      84           0 :   *effects = (fd_exec_test_syscall_effects_t) FD_EXEC_TEST_SYSCALL_EFFECTS_INIT_ZERO;
      85             : 
      86           0 :   if( FD_UNLIKELY( _l > output_end ) ) {
      87           0 :     fd_exec_test_instr_context_destroy( runner, instr_ctx );
      88           0 :     return 0UL;
      89           0 :   }
      90             : 
      91           0 : do{
      92             :   /* Setup regions */
      93           0 :   if ( !input->vm_ctx.rodata ) {
      94           0 :     break;
      95           0 :   }
      96           0 :   ulong   rodata_sz = input->vm_ctx.rodata->size;
      97           0 :   uchar * rodata = fd_spad_alloc_debug( spad, 8UL, rodata_sz );
      98           0 :   memcpy( rodata, input->vm_ctx.rodata->bytes, rodata_sz );
      99             : 
     100             :   /* Enable direct_mapping for SBPF version >= v1 */
     101           0 :   if( input->vm_ctx.sbpf_version >= FD_SBPF_V1 ) {
     102           0 :     ((fd_exec_txn_ctx_t *)(instr_ctx->txn_ctx))->features.bpf_account_data_direct_mapping = 0UL;
     103           0 :   }
     104             : 
     105             :   /* Setup input region */
     106           0 :   ulong                   input_sz                = 0UL;
     107           0 :   ulong                   pre_lens[256]           = {0};
     108           0 :   fd_vm_input_region_t    input_mem_regions[1000] = {0}; /* We can have a max of (3 * num accounts + 1) regions */
     109           0 :   fd_vm_acc_region_meta_t acc_region_metas[256]   = {0}; /* instr acc idx to idx */
     110           0 :   uint                    input_mem_regions_cnt   = 0U;
     111           0 :   int                     direct_mapping          = FD_FEATURE_ACTIVE( instr_ctx->txn_ctx->slot, instr_ctx->txn_ctx->features, bpf_account_data_direct_mapping );
     112             : 
     113           0 :   uchar * input_ptr      = NULL;
     114           0 :   uchar   program_id_idx = instr_ctx->instr->program_id;
     115           0 :   uchar   is_deprecated  = ( program_id_idx < instr_ctx->txn_ctx->accounts_cnt ) &&
     116           0 :                            ( !memcmp( instr_ctx->txn_ctx->accounts[program_id_idx].const_meta->info.owner, fd_solana_bpf_loader_deprecated_program_id.key, sizeof(fd_pubkey_t) ) );
     117             : 
     118             :   /* TODO: Check for an error code. Probably unlikely during fuzzing though */
     119           0 :   fd_bpf_loader_input_serialize_parameters( instr_ctx,
     120           0 :                                             &input_sz,
     121           0 :                                             pre_lens,
     122           0 :                                             input_mem_regions,
     123           0 :                                             &input_mem_regions_cnt,
     124           0 :                                             acc_region_metas,
     125           0 :                                             direct_mapping,
     126           0 :                                             is_deprecated,
     127           0 :                                             &input_ptr );
     128             : 
     129           0 :   if( input->vm_ctx.heap_max>FD_VM_HEAP_DEFAULT ) {
     130           0 :     break;
     131           0 :   }
     132             : 
     133             :   /* Setup calldests from call_whitelist.
     134             :      Alloc calldests with the expected size (1 bit per ix, rounded up to ulong) */
     135           0 :   ulong max_pc = (rodata_sz + 7) / 8;
     136           0 :   ulong calldests_sz = ((max_pc + 63) / 64) * 8;
     137           0 :   ulong * calldests = fd_valloc_malloc( valloc, fd_sbpf_calldests_align(), calldests_sz );
     138           0 :   memset( calldests, 0, calldests_sz );
     139           0 :   if( input->vm_ctx.call_whitelist && input->vm_ctx.call_whitelist->size > 0 ) {
     140           0 :     memcpy( calldests, input->vm_ctx.call_whitelist->bytes, input->vm_ctx.call_whitelist->size );
     141             :     /* Make sure bits over max_pc are all 0s. */
     142           0 :     ulong mask = (1UL << (max_pc % 64)) - 1UL;
     143           0 :     calldests[ max_pc / 64 ] &= mask;
     144           0 :   }
     145           0 :   ulong entry_pc = fd_ulong_min( input->vm_ctx.entry_pc, rodata_sz / 8UL - 1UL );
     146           0 :   if( input->vm_ctx.sbpf_version >= FD_SBPF_V3 ) {
     147             :     /* in v3 we have to enable the entrypoint */
     148           0 :     calldests[ entry_pc / 64UL ] |= ( 1UL << ( entry_pc % 64UL ) );
     149           0 :   }
     150             : 
     151             :   /* Setup syscalls. Have them all be no-ops */
     152           0 :   fd_sbpf_syscalls_t * syscalls = fd_sbpf_syscalls_new( fd_valloc_malloc( valloc, fd_sbpf_syscalls_align(), fd_sbpf_syscalls_footprint() ) );
     153           0 :   fd_vm_syscall_register_slot( syscalls,
     154           0 :                                instr_ctx->txn_ctx->slot,
     155           0 :                                &instr_ctx->txn_ctx->features,
     156           0 :                                0 );
     157             : 
     158           0 :   for( ulong i=0; i< fd_sbpf_syscalls_slot_cnt(); i++ ){
     159           0 :     fd_sbpf_syscalls_t * syscall = fd_sbpf_syscalls_query( syscalls, syscalls[i].key, NULL );
     160           0 :     if ( !syscall ) {
     161           0 :       continue;
     162           0 :     }
     163           0 :     syscall->func = fd_vm_syscall_noop;
     164           0 :   }
     165             : 
     166             :   /* Setup trace */
     167           0 :   const uint DUMP_TRACE = 0; // Set to 1 to dump trace to stdout
     168           0 :   uint tracing_enabled = input->vm_ctx.tracing_enabled;
     169           0 :   fd_vm_trace_t * trace = NULL;
     170           0 :   ulong event_max = 1UL<<20;
     171           0 :   ulong event_data_max = 2048UL;
     172             : 
     173           0 :   if (!!tracing_enabled) {
     174           0 :     trace = fd_vm_trace_new( fd_valloc_malloc( valloc, fd_vm_trace_align(), fd_vm_trace_footprint( event_max, event_data_max ) ), event_max, event_data_max );
     175           0 :   }
     176             : 
     177             :   /* Setup vm */
     178           0 :   fd_vm_t * vm = fd_vm_join( fd_vm_new( fd_valloc_malloc( valloc, fd_vm_align(), fd_vm_footprint() ) ) );
     179           0 :   FD_TEST( vm );
     180             : 
     181           0 :   fd_vm_init(
     182           0 :     vm,
     183           0 :     instr_ctx,
     184           0 :     input->vm_ctx.heap_max,
     185           0 :     input->has_instr_ctx ? input->instr_ctx.cu_avail : 0,
     186           0 :     rodata,
     187           0 :     rodata_sz,
     188           0 :     (ulong *) rodata, /* text*, same as rodata */
     189           0 :     rodata_sz / 8, /* text_cnt */
     190           0 :     0, /* text_off */
     191           0 :     rodata_sz, /* text_sz */
     192           0 :     entry_pc,
     193           0 :     calldests,
     194           0 :     input->vm_ctx.sbpf_version,
     195           0 :     syscalls,
     196           0 :     trace, /* trace */
     197           0 :     NULL, /* sha */
     198           0 :     input_mem_regions,
     199           0 :     input_mem_regions_cnt,
     200           0 :     acc_region_metas, /* vm_acc_region_meta*/
     201           0 :     is_deprecated, /* is deprecated */
     202           0 :     direct_mapping /* direct mapping */
     203           0 :   );
     204             : 
     205             :   /* Setup registers.
     206             :      r1, r10, r11 are initialized by EbpfVm::new (r10) or EbpfVm::execute_program (r1, r11),
     207             :      or equivalently by fd_vm_init and fd_vm_setup_state_for_execution.
     208             :      Modifying them will most like break execution.
     209             :      In syscalls we allow override them (especially r1) because that simulates the fact
     210             :      that a program partially executed before reaching the syscall.
     211             :      Here we want to test what happens when the program starts from the beginning. */
     212           0 :   vm->reg[0]  = input->vm_ctx.r0;
     213             :   // vm->reg[1]  = input->vm_ctx.r1; // do not override
     214           0 :   vm->reg[2]  = input->vm_ctx.r2;
     215           0 :   vm->reg[3]  = input->vm_ctx.r3;
     216           0 :   vm->reg[4]  = input->vm_ctx.r4;
     217           0 :   vm->reg[5]  = input->vm_ctx.r5;
     218           0 :   vm->reg[6]  = input->vm_ctx.r6;
     219           0 :   vm->reg[7]  = input->vm_ctx.r7;
     220           0 :   vm->reg[8]  = input->vm_ctx.r8;
     221           0 :   vm->reg[9]  = input->vm_ctx.r9;
     222             :   // vm->reg[10]  = input->vm_ctx.r10; // do not override
     223             :   // vm->reg[11]  = input->vm_ctx.r11; // do not override
     224             : 
     225             :   // Validate the vm
     226           0 :   if( fd_vm_validate( vm ) != FD_VM_SUCCESS ) {
     227             :     // custom error, avoid -1 because we use it for "unknown error" in solfuzz-agave
     228           0 :     effects->error = -2;
     229           0 :     break;
     230           0 :   }
     231             : 
     232           0 :   if( input->syscall_invocation.stack_prefix ) {
     233           0 :     uchar * stack    = input->syscall_invocation.stack_prefix->bytes;
     234           0 :     ulong   stack_sz = fd_ulong_min(input->syscall_invocation.stack_prefix->size, FD_VM_STACK_MAX);
     235           0 :     fd_memcpy( vm->stack, stack, stack_sz );
     236           0 :   }
     237             : 
     238           0 :   if( input->syscall_invocation.heap_prefix ) {
     239           0 :     uchar * heap    = input->syscall_invocation.heap_prefix->bytes;
     240           0 :     ulong   heap_sz = fd_ulong_min(input->syscall_invocation.heap_prefix->size, FD_VM_HEAP_MAX);
     241           0 :     fd_memcpy( vm->heap, heap, heap_sz );
     242           0 :   }
     243             : 
     244             :   /* Run vm */
     245           0 :   int exec_res = 0;
     246           0 :   if (!!tracing_enabled) {
     247           0 :     exec_res = fd_vm_exec_trace( vm );
     248           0 :     if( DUMP_TRACE ) fd_vm_trace_printf( trace, syscalls );
     249           0 :     fd_vm_trace_delete( fd_vm_trace_leave( trace ) );
     250           0 :   } else {
     251           0 :     exec_res = fd_vm_exec_notrace( vm );
     252           0 :   }
     253             : 
     254             :   /* Agave does not have a SIGCALL error, and instead throws SIGILL */
     255           0 :   if( exec_res == FD_VM_ERR_SIGCALL ) exec_res = FD_VM_ERR_SIGILL;
     256           0 :   effects->error = -1 * exec_res;
     257             : 
     258             :   /* Capture outputs */
     259           0 :   effects->cu_avail    = vm->cu;
     260           0 :   effects->frame_count = vm->frame_cnt;
     261           0 :   /* Only capture registers if no error */;
     262           0 :   effects->r0          = exec_res ? 0 : vm->reg[0];
     263           0 :   effects->r1          = exec_res ? 0 : vm->reg[1];
     264           0 :   effects->r2          = exec_res ? 0 : vm->reg[2];
     265           0 :   effects->r3          = exec_res ? 0 : vm->reg[3];
     266           0 :   effects->r4          = exec_res ? 0 : vm->reg[4];
     267           0 :   effects->r5          = exec_res ? 0 : vm->reg[5];
     268           0 :   effects->r6          = exec_res ? 0 : vm->reg[6];
     269           0 :   effects->r7          = exec_res ? 0 : vm->reg[7];
     270           0 :   effects->r8          = exec_res ? 0 : vm->reg[8];
     271           0 :   effects->r9          = exec_res ? 0 : vm->reg[9];
     272           0 :   effects->r10         = exec_res ? 0 : vm->reg[10];
     273             : 
     274             :   /* skip logs since syscalls are stubbed */
     275             : 
     276             :   /* CU error is difficult to properly compare as there may have been
     277             :      valid writes to the memory regions prior to capturing the error. And
     278             :      the pc might be well past (by an arbitrary amount) the instruction
     279             :      where the CU error occurred. */
     280           0 :   if( exec_res == FD_VM_ERR_SIGCOST ) break;
     281             : 
     282           0 :   effects->pc = vm->pc;
     283             : 
     284           0 :   if( vm->heap_max > 0 ) {
     285           0 :     effects->heap       = FD_SCRATCH_ALLOC_APPEND(
     286           0 :       l, alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE( vm->heap_max ) );
     287           0 :     effects->heap->size = (uint)vm->heap_max;
     288           0 :     fd_memcpy( effects->heap->bytes, vm->heap, vm->heap_max );
     289           0 :   }
     290             : 
     291             :   /* Compress stack by removing right-most 0s.
     292             :      This reduces the total size of effects/fixtures when stack is not used,
     293             :      otherwise each would waste 256kB. */
     294           0 :   int rtrim_sz;
     295           0 :   for( rtrim_sz=FD_VM_STACK_MAX-1; rtrim_sz>=0; rtrim_sz-- ) {
     296           0 :     if( vm->stack[rtrim_sz] != 0 ) break;
     297           0 :   }
     298           0 :   if( rtrim_sz > 0 || (vm->stack[0] != 0) ) {
     299           0 :     effects->stack       = FD_SCRATCH_ALLOC_APPEND(
     300           0 :       l, alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE( FD_VM_STACK_MAX ) );
     301           0 :     effects->stack->size = (uint)rtrim_sz+1;
     302           0 :     fd_memcpy( effects->stack->bytes, vm->stack, (ulong)rtrim_sz+1 );
     303           0 :   }
     304             : 
     305           0 :   effects->rodata       = FD_SCRATCH_ALLOC_APPEND(
     306           0 :     l, alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE( rodata_sz ) );
     307           0 :   effects->rodata->size = (uint)rodata_sz;
     308           0 :   fd_memcpy( effects->rodata->bytes, rodata, rodata_sz );
     309             : 
     310             :   /* Capture input data regions */
     311           0 :   ulong tmp_end = FD_SCRATCH_ALLOC_FINI(l, 1UL);
     312           0 :   ulong input_data_regions_size = load_from_vm_input_regions( vm->input_mem_regions,
     313           0 :                                                               vm->input_mem_regions_cnt,
     314           0 :                                                               &effects->input_data_regions,
     315           0 :                                                               &effects->input_data_regions_count,
     316           0 :                                                               (void *) tmp_end,
     317           0 :                                                               fd_ulong_sat_sub( output_end, tmp_end) );
     318           0 :   FD_SCRATCH_ALLOC_APPEND( l, 1UL, input_data_regions_size );
     319             : 
     320           0 : } while(0);
     321             : 
     322           0 :   ulong actual_end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
     323           0 :   *output = effects;
     324           0 :   fd_exec_test_instr_context_destroy( runner, instr_ctx );
     325           0 :   return actual_end - (ulong)output_buf;
     326           0 : }
     327             : 
     328             : 
     329             : uint
     330             : fd_setup_vm_input_regions( fd_vm_input_region_t *                   input,
     331             :                            fd_exec_test_input_data_region_t const * test_input,
     332             :                            ulong                                    test_input_count,
     333           0 :                            fd_spad_t *                              spad ) {
     334           0 :   ulong offset = 0UL;
     335           0 :   uint input_idx = 0UL;
     336           0 :   for( ulong i=0; i<test_input_count; i++ ) {
     337           0 :     fd_exec_test_input_data_region_t const * region = &test_input[i];
     338           0 :     pb_bytes_array_t * array = region->content;
     339           0 :     if( !array ) {
     340           0 :       continue; /* skip empty regions https://github.com/anza-xyz/agave/blob/3072c1a72b2edbfa470ca869f1ea891dfb6517f2/programs/bpf_loader/src/serialization.rs#L136 */
     341           0 :     }
     342             : 
     343           0 :     uchar * haddr = fd_spad_alloc_debug( spad, 8UL, array->size );
     344           0 :     fd_memcpy( haddr, array->bytes, array->size );
     345           0 :     input[input_idx].vaddr_offset     = offset;
     346           0 :     input[input_idx].haddr            = (ulong)haddr;
     347           0 :     input[input_idx].region_sz        = array->size;
     348           0 :     input[input_idx].is_writable      = region->is_writable;
     349             : 
     350           0 :     input_idx++;
     351           0 :     offset += array->size;
     352           0 :   }
     353           0 :   return input_idx; /* return the number of populated regions */
     354           0 : }
     355             : 
     356             : 
     357             : ulong
     358             : load_from_vm_input_regions( fd_vm_input_region_t const *        input,
     359             :                             uint                                input_count,
     360             :                             fd_exec_test_input_data_region_t ** output,
     361             :                             pb_size_t *                         output_count,
     362             :                             void *                              output_buf,
     363           0 :                             ulong                               output_bufsz ) {
     364             :   /* pre-flight checks on output buffer size*/
     365           0 :   ulong input_regions_total_sz = 0;
     366           0 :   for( ulong i=0; i<input_count; i++ ) {
     367           0 :     input_regions_total_sz += input[i].region_sz;
     368           0 :   }
     369             : 
     370           0 :   if( FD_UNLIKELY(   input_regions_total_sz == 0
     371           0 :                   || output_bufsz < input_regions_total_sz ) ) {
     372           0 :     *output = NULL;
     373           0 :     *output_count = 0;
     374           0 :     return 0;
     375           0 :   }
     376             : 
     377           0 :   FD_SCRATCH_ALLOC_INIT( l, output_buf );
     378           0 :   *output = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_input_data_region_t),
     379           0 :                                       input_count * sizeof (fd_exec_test_input_data_region_t) );
     380           0 :   FD_TEST( *output );
     381           0 :   *output_count = input_count;
     382             : 
     383           0 :   for( ulong i=0; i<input_count; i++ ) {
     384           0 :     fd_vm_input_region_t const * vm_region = &input[i];
     385           0 :     fd_exec_test_input_data_region_t * out_region = &(*output)[i];
     386           0 :     out_region->is_writable = vm_region->is_writable;
     387           0 :     out_region->offset = vm_region->vaddr_offset;
     388             : 
     389           0 :     out_region->content = FD_SCRATCH_ALLOC_APPEND( l, alignof(pb_bytes_array_t),
     390           0 :                                                PB_BYTES_ARRAY_T_ALLOCSIZE(vm_region->region_sz) );
     391           0 :     FD_TEST( out_region->content );
     392           0 :     out_region->content->size = vm_region->region_sz;
     393           0 :     fd_memcpy( out_region->content->bytes, (void *)vm_region->haddr, vm_region->region_sz );
     394           0 :   }
     395             : 
     396           0 :   ulong end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
     397           0 :   return end - (ulong)output_buf; /* return the number of bytes written */
     398           0 : }

Generated by: LCOV version 1.14