LCOV - code coverage report
Current view: top level - flamenco/runtime/tests - fd_vm_harness.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 230 273 84.2 %
Date: 2026-03-19 18:19:27 Functions: 3 3 100.0 %

          Line data    Source code
       1             : #include "fd_instr_harness.h"
       2             : #include "../fd_executor.h"
       3             : #include "../fd_runtime.h"
       4             : #include "../fd_system_ids.h"
       5             : #include "../../log_collector/fd_log_collector.h"
       6             : #include "../program/fd_bpf_loader_serialization.h"
       7             : #include "../../../ballet/sbpf/fd_sbpf_loader.h"
       8             : #include "../../vm/fd_vm.h"
       9             : #include "../../vm/test_vm_util.h"
      10             : #include "generated/vm.pb.h"
      11             : #include "../fd_bank.h"
      12             : 
      13             : static fd_sbpf_syscalls_t *
      14             : fd_solfuzz_vm_syscall_lookup_func( fd_sbpf_syscalls_t * syscalls,
      15             :                                    const char *         syscall_name,
      16        4759 :                                    size_t               len) {
      17        4759 :   ulong i;
      18             : 
      19        4759 :   if (!syscall_name) return NULL;
      20             : 
      21      168172 :   for (i = 0; i < fd_sbpf_syscalls_slot_cnt(); ++i) {
      22      168168 :     if (!fd_sbpf_syscalls_key_inval(syscalls[i].key) && syscalls[i].name && strlen(syscalls[i].name) == len) {
      23        7322 :       if (!memcmp(syscalls[i].name, syscall_name, len)) {
      24        4755 :         return syscalls + i;
      25        4755 :       }
      26        7322 :     }
      27      168168 :   }
      28             : 
      29           4 :   return NULL;
      30        4759 : }
      31             : 
      32             : static ulong
      33             : fd_solfuzz_vm_load_from_input_regions( fd_vm_input_region_t const *        input,
      34             :                                        uint                                input_count,
      35             :                                        fd_exec_test_input_data_region_t ** output,
      36             :                                        pb_size_t *                         output_count,
      37             :                                        void *                              output_buf,
      38        4763 :                                        ulong                               output_bufsz ) {
      39             :   /* pre-flight checks on output buffer size*/
      40        4763 :   ulong input_regions_total_sz = 0;
      41        9527 :   for( ulong i=0; i<input_count; i++ ) {
      42        4764 :     input_regions_total_sz += input[i].region_sz;
      43        4764 :   }
      44             : 
      45        4763 :   if( FD_UNLIKELY(   input_regions_total_sz == 0
      46        4763 :                   || output_bufsz < input_regions_total_sz ) ) {
      47           0 :     *output = NULL;
      48           0 :     *output_count = 0;
      49           0 :     return 0;
      50           0 :   }
      51             : 
      52        4763 :   FD_SCRATCH_ALLOC_INIT( l, output_buf );
      53        4763 :   *output = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_input_data_region_t),
      54        4763 :                                       input_count * sizeof (fd_exec_test_input_data_region_t) );
      55        4763 :   FD_TEST( *output );
      56        4763 :   *output_count = input_count;
      57             : 
      58        9530 :   for( ulong i=0; i<input_count; i++ ) {
      59        4767 :     fd_vm_input_region_t const * vm_region = &input[i];
      60        4767 :     fd_exec_test_input_data_region_t * out_region = &(*output)[i];
      61        4767 :     out_region->is_writable = vm_region->is_writable;
      62        4767 :     out_region->offset = vm_region->vaddr_offset;
      63             : 
      64        4767 :     if( vm_region->region_sz > 0 ) {
      65        4767 :       out_region->content = FD_SCRATCH_ALLOC_APPEND( l, alignof(pb_bytes_array_t),
      66        4767 :                                                  PB_BYTES_ARRAY_T_ALLOCSIZE(vm_region->region_sz) );
      67        4767 :       FD_TEST( out_region->content );
      68        4767 :       out_region->content->size = vm_region->region_sz;
      69        4767 :       fd_memcpy( out_region->content->bytes, (void *)vm_region->haddr, vm_region->region_sz );
      70        4767 :     } else {
      71           0 :       out_region->content = NULL;
      72           0 :     }
      73        4767 :   }
      74             : 
      75        4763 :   ulong end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
      76        4763 :   return end - (ulong)output_buf; /* return the number of bytes written */
      77        4763 : }
      78             : 
      79             : 
      80             : ulong
      81             : fd_solfuzz_pb_syscall_run( fd_solfuzz_runner_t * runner,
      82             :                            void const *          input_,
      83             :                            void **               output_,
      84             :                            void *                output_buf,
      85        4715 :                            ulong                 output_bufsz ) {
      86        4715 :   fd_exec_test_syscall_context_t const * input =  fd_type_pun_const( input_ );
      87        4715 :   fd_exec_test_syscall_effects_t **      output = fd_type_pun( output_ );
      88             : 
      89             :   /* Create execution context */
      90        4715 :   const fd_exec_test_instr_context_t * input_instr_ctx = &input->instr_ctx;
      91        4715 :   fd_exec_instr_ctx_t ctx[1];
      92        4715 :   fd_solfuzz_pb_instr_ctx_create( runner, ctx, input_instr_ctx );
      93             : 
      94        4715 :   ctx->txn_out->err.exec_err = 0;
      95        4715 :   ctx->txn_out->err.exec_err_kind = FD_EXECUTOR_ERR_KIND_NONE;
      96        4715 :   ctx->bank = runner->bank;
      97             : 
      98             :   /* Capture outputs */
      99        4715 :   ulong output_end = (ulong)output_buf + output_bufsz;
     100        4715 :   FD_SCRATCH_ALLOC_INIT( l, output_buf );
     101        4715 :   fd_exec_test_syscall_effects_t * effects =
     102        4715 :     FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_syscall_effects_t),
     103        4715 :                                 sizeof (fd_exec_test_syscall_effects_t) );
     104        4715 :   if( FD_UNLIKELY( _l > output_end ) ) {
     105           0 :     goto error;
     106           0 :   }
     107             : 
     108        4715 :   if( input->vm_ctx.return_data.program_id && input->vm_ctx.return_data.program_id->size == sizeof(fd_pubkey_t) ) {
     109        1275 :     fd_memcpy( ctx->txn_out->details.return_data.program_id.uc, input->vm_ctx.return_data.program_id->bytes, sizeof(fd_pubkey_t) );
     110        1275 :   }
     111             : 
     112        4715 :   if( input->vm_ctx.return_data.data && input->vm_ctx.return_data.data->size>0U ) {
     113        1276 :     ctx->txn_out->details.return_data.len = input->vm_ctx.return_data.data->size;
     114        1276 :     fd_memcpy( ctx->txn_out->details.return_data.data, input->vm_ctx.return_data.data->bytes, ctx->txn_out->details.return_data.len );
     115        1276 :   }
     116             : 
     117        4715 :   *effects = (fd_exec_test_syscall_effects_t) FD_EXEC_TEST_SYSCALL_EFFECTS_INIT_ZERO;
     118             : 
     119             :   /* Set up the VM instance */
     120        4715 :   fd_spad_t * spad = runner->spad;
     121        4715 :   fd_sha256_t _sha[1];
     122        4715 :   fd_sha256_t * sha = fd_sha256_join( fd_sha256_new( _sha ) );
     123        4715 :   fd_sbpf_syscalls_t * syscalls = fd_sbpf_syscalls_new( fd_spad_alloc_check( spad, fd_sbpf_syscalls_align(), fd_sbpf_syscalls_footprint() ) );
     124        4715 :   fd_vm_syscall_register_all( syscalls, 0 );
     125             : 
     126             :   /* Pull out the memory regions */
     127        4715 :   if( !input->has_vm_ctx ) {
     128           0 :     goto error;
     129           0 :   }
     130             : 
     131        4715 :   ulong rodata_sz = input->vm_ctx.rodata ? input->vm_ctx.rodata->size : 0UL;
     132        4715 :   uchar * rodata = fd_spad_alloc_check( spad, 8UL, rodata_sz );
     133        4715 :   if ( input->vm_ctx.rodata != NULL ) {
     134        1280 :     fd_memcpy( rodata, input->vm_ctx.rodata->bytes, rodata_sz );
     135        1280 :   }
     136             : 
     137        4715 :   if( input->vm_ctx.heap_max > FD_VM_HEAP_MAX ) {
     138           0 :     goto error;
     139           0 :   }
     140             : 
     141        4715 :   fd_vm_t * vm = fd_vm_join( fd_vm_new( fd_spad_alloc_check( spad, fd_vm_align(), fd_vm_footprint() ) ) );
     142        4715 :   if ( !vm ) {
     143           0 :     goto error;
     144           0 :   }
     145             : 
     146             :   /* If the program ID account owner is the v1 BPF loader, then alignment is disabled (controlled by
     147             :      the `is_deprecated` flag) */
     148             : 
     149        4715 :   ulong                   input_sz                               = 0UL;
     150        4715 :   ulong                   pre_lens[256]                          = {0};
     151        4715 :   fd_vm_input_region_t    input_mem_regions[1000]                = {0}; /* We can have a max of (3 * num accounts + 1) regions */
     152        4715 :   fd_vm_acc_region_meta_t acc_region_metas[256]                  = {0}; /* instr acc idx to idx */
     153        4715 :   uint                    input_mem_regions_cnt                  = 0U;
     154        4715 :   int                     direct_mapping                         = FD_FEATURE_ACTIVE_BANK( ctx->bank, account_data_direct_mapping );
     155        4715 :   int                     stricter_abi_and_runtime_constraints   = FD_FEATURE_ACTIVE_BANK( ctx->bank, stricter_abi_and_runtime_constraints );
     156             : 
     157        4715 :   uchar               program_id_idx = ctx->instr->program_id;
     158        4715 :   fd_account_meta_t * program_acc    = ctx->txn_out->accounts.account[program_id_idx].meta;
     159        4715 :   uchar               is_deprecated  = ( program_id_idx < ctx->txn_out->accounts.cnt ) &&
     160        4761 :                                       ( !memcmp( program_acc->owner, fd_solana_bpf_loader_deprecated_program_id.key, sizeof(fd_pubkey_t) ) );
     161             : 
     162             :   /* Push the instruction onto the stack. This may also modify the sysvar instructions account, if its present. */
     163        4715 :   int stack_push_err = fd_instr_stack_push( ctx->runtime, ctx->txn_in, ctx->txn_out, (fd_instr_info_t *)ctx->instr );
     164        4715 :   if( FD_UNLIKELY( stack_push_err ) ) {
     165           0 :       FD_LOG_WARNING(( "instr stack push err" ));
     166           0 :       goto error;
     167           0 :   }
     168             : 
     169        4715 :   ulong instr_data_offset = 0UL;
     170        4715 :   int err = fd_bpf_loader_input_serialize_parameters( ctx,
     171        4715 :                                                       pre_lens,
     172        4715 :                                                       input_mem_regions,
     173        4715 :                                                       &input_mem_regions_cnt,
     174        4715 :                                                       acc_region_metas,
     175        4715 :                                                       stricter_abi_and_runtime_constraints,
     176        4715 :                                                       direct_mapping,
     177        4715 :                                                       is_deprecated,
     178        4715 :                                                       &instr_data_offset,
     179        4715 :                                                       &input_sz );
     180        4715 :   if( FD_UNLIKELY( err ) ) {
     181           0 :     FD_LOG_WARNING(( "bpf loader input serialize parameters err" ));
     182           0 :     goto error;
     183           0 :   }
     184             : 
     185        4715 :   fd_vm_init( vm,
     186        4715 :               ctx,
     187        4715 :               input->vm_ctx.heap_max,
     188        4715 :               ctx->txn_out->details.compute_budget.compute_meter,
     189        4715 :               rodata,
     190        4715 :               rodata_sz,
     191        4715 :               NULL, // TODO
     192        4715 :               0, // TODO
     193        4715 :               0, // TODO
     194        4715 :               0, // TODO, text_sz
     195        4715 :               0, // TODO
     196        4715 :               NULL, // TODO
     197        4715 :               TEST_VM_DEFAULT_SBPF_VERSION,
     198        4715 :               syscalls,
     199        4715 :               NULL, // TODO
     200        4715 :               sha,
     201        4715 :               input_mem_regions,
     202        4715 :               input_mem_regions_cnt,
     203        4715 :               acc_region_metas,
     204        4715 :               is_deprecated,
     205        4715 :               direct_mapping,
     206        4715 :               stricter_abi_and_runtime_constraints,
     207        4715 :               0 /* dump_syscall_to_pb */,
     208        4715 :               0UL /* r2 is set by the fuzzer below */ );
     209             : 
     210             :   // Override some execution state values from the syscall fuzzer input
     211             :   // This is so we can test if the syscall mutates any of these erroneously
     212        4715 :   vm->reg[0] = input->vm_ctx.r0;
     213        4715 :   vm->reg[1] = input->vm_ctx.r1;
     214        4715 :   vm->reg[2] = input->vm_ctx.r2;
     215        4715 :   vm->reg[3] = input->vm_ctx.r3;
     216        4715 :   vm->reg[4] = input->vm_ctx.r4;
     217        4715 :   vm->reg[5] = input->vm_ctx.r5;
     218        4715 :   vm->reg[6] = input->vm_ctx.r6;
     219        4715 :   vm->reg[7] = input->vm_ctx.r7;
     220        4715 :   vm->reg[8] = input->vm_ctx.r8;
     221        4715 :   vm->reg[9] = input->vm_ctx.r9;
     222        4715 :   vm->reg[10] = input->vm_ctx.r10;
     223        4715 :   vm->reg[11] = input->vm_ctx.r11;
     224             : 
     225             :   // Override initial part of the heap, if specified the syscall fuzzer input
     226        4740 :   if( input->syscall_invocation.heap_prefix ) {
     227        4740 :     fd_memcpy( vm->heap, input->syscall_invocation.heap_prefix->bytes,
     228        4740 :                fd_ulong_min(input->syscall_invocation.heap_prefix->size, vm->heap_max) );
     229        4740 :   }
     230             : 
     231             :   // Override initial part of the stack, if specified the syscall fuzzer input
     232        4715 :   if( input->syscall_invocation.stack_prefix ) {
     233        1279 :     fd_memcpy( vm->stack, input->syscall_invocation.stack_prefix->bytes,
     234        1279 :                fd_ulong_min(input->syscall_invocation.stack_prefix->size, FD_VM_STACK_MAX) );
     235        1279 :   }
     236             : 
     237             :   // Look up the syscall to execute
     238        4715 :   char * syscall_name = (char *)input->syscall_invocation.function_name.bytes;
     239        4715 :   fd_sbpf_syscalls_t const * syscall = fd_solfuzz_vm_syscall_lookup_func(syscalls, syscall_name, input->syscall_invocation.function_name.size);
     240        4715 :   if( !syscall ) {
     241           0 :     goto error;
     242           0 :   }
     243             : 
     244             :   /* There's an instr ctx struct embedded in the txn ctx instr stack. */
     245        4715 :   fd_exec_instr_ctx_t * instr_ctx = &ctx->runtime->instr.stack[ ctx->runtime->instr.stack_sz - 1 ];
     246        4715 :   *instr_ctx = (fd_exec_instr_ctx_t) {
     247        4715 :     .instr   = ctx->instr,
     248        4715 :     .txn_out = ctx->txn_out,
     249        4715 :     .runtime = ctx->runtime,
     250        4715 :   };
     251             : 
     252             :   /* Actually invoke the syscall */
     253        4715 :   int syscall_err = syscall->func( vm, vm->reg[1], vm->reg[2], vm->reg[3], vm->reg[4], vm->reg[5], &vm->reg[0] );
     254        4715 :   int stack_pop_err = fd_instr_stack_pop( ctx->runtime, ctx->txn_out, ctx->instr );
     255        4715 :   if( FD_UNLIKELY( stack_pop_err ) ) {
     256           0 :       FD_LOG_WARNING(( "instr stack pop err" ));
     257           0 :       goto error;
     258           0 :   }
     259        4715 :   if( syscall_err ) {
     260        1140 :     fd_log_collector_program_failure( vm->instr_ctx );
     261        1140 :   }
     262             : 
     263             :   /* Capture the effects */
     264        4715 :   int exec_err = vm->instr_ctx->txn_out->err.exec_err;
     265        4715 :   effects->error = 0;
     266        4715 :   if( syscall_err ) {
     267        1139 :     if( exec_err==0 ) {
     268           0 :       FD_LOG_WARNING(( "TODO: syscall returns error, but exec_err not set. this is probably missing a log." ));
     269           0 :       effects->error = -1;
     270        1139 :     } else {
     271        1139 :       effects->error = (exec_err <= 0) ? -exec_err : -1;
     272             : 
     273             :       /* Map error kind, equivalent to:
     274             :           effects->error_kind = (fd_exec_test_err_kind_t)(vm->instr_ctx->txn_ctx->err.exec_err_kind); */
     275        1139 :       switch (vm->instr_ctx->txn_out->err.exec_err_kind) {
     276         417 :         case FD_EXECUTOR_ERR_KIND_EBPF:
     277         417 :           effects->error_kind = FD_EXEC_TEST_ERR_KIND_EBPF;
     278         417 :           break;
     279         153 :         case FD_EXECUTOR_ERR_KIND_SYSCALL:
     280         153 :           effects->error_kind = FD_EXEC_TEST_ERR_KIND_SYSCALL;
     281         153 :           break;
     282         569 :         case FD_EXECUTOR_ERR_KIND_INSTR:
     283         569 :           effects->error_kind = FD_EXEC_TEST_ERR_KIND_INSTRUCTION;
     284         569 :           break;
     285           0 :         default:
     286           0 :           effects->error_kind = FD_EXEC_TEST_ERR_KIND_UNSPECIFIED;
     287           0 :           break;
     288        1139 :       }
     289        1139 :     }
     290        1139 :   }
     291        4715 :   effects->r0 = syscall_err ? 0 : vm->reg[0]; // Save only on success
     292        4715 :   effects->cu_avail = (ulong)vm->cu;
     293             : 
     294        4746 :   if( vm->heap_max ) {
     295        4746 :     effects->heap = FD_SCRATCH_ALLOC_APPEND(
     296        4746 :       l, alignof(uint), PB_BYTES_ARRAY_T_ALLOCSIZE( vm->heap_max ) );
     297        4746 :     if( FD_UNLIKELY( _l > output_end ) ) {
     298           0 :       goto error;
     299           0 :     }
     300        4746 :     effects->heap->size = (uint)vm->heap_max;
     301        4746 :     fd_memcpy( effects->heap->bytes, vm->heap, vm->heap_max );
     302 >1844*10^16 :   } else {
     303 >1844*10^16 :     effects->heap = NULL;
     304 >1844*10^16 :   }
     305             : 
     306        4715 :   effects->stack = FD_SCRATCH_ALLOC_APPEND(
     307        4715 :     l, alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE( FD_VM_STACK_MAX ) );
     308        4715 :     if( FD_UNLIKELY( _l > output_end ) ) {
     309           0 :       goto error;
     310           0 :     }
     311        4715 :   effects->stack->size = (uint)FD_VM_STACK_MAX;
     312        4715 :   fd_memcpy( effects->stack->bytes, vm->stack, FD_VM_STACK_MAX );
     313             : 
     314        4715 :   if( vm->rodata_sz ) {
     315        1280 :     effects->rodata = FD_SCRATCH_ALLOC_APPEND(
     316        1280 :       l, alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE( rodata_sz ) );
     317        1280 :     if( FD_UNLIKELY( _l > output_end ) ) {
     318           0 :       goto error;
     319           0 :     }
     320        1280 :     effects->rodata->size = (uint)rodata_sz;
     321        1280 :     fd_memcpy( effects->rodata->bytes, vm->rodata, rodata_sz );
     322        3435 :   } else {
     323        3435 :     effects->rodata = NULL;
     324        3435 :   }
     325             : 
     326        4715 :   effects->frame_count = vm->frame_cnt;
     327             : 
     328        4715 :   fd_log_collector_t * log = vm->instr_ctx->runtime->log.log_collector;
     329             :   /* Only collect log on valid errors (i.e., != -1). Follows
     330             :      https://github.com/firedancer-io/solfuzz-agave/blob/99758d3c4f3a342d56e2906936458d82326ae9a8/src/utils/err_map.rs#L148 */
     331        4770 :   if( effects->error != -1 && log->buf_sz ) {
     332        1163 :     effects->log = FD_SCRATCH_ALLOC_APPEND(
     333        1163 :       l, alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE( log->buf_sz ) );
     334        1163 :     if( FD_UNLIKELY( _l > output_end ) ) {
     335           0 :       goto error;
     336           0 :     }
     337        1163 :     effects->log->size = (uint)fd_log_collector_debug_sprintf( log, (char *)effects->log->bytes, 0 );
     338        3552 :   } else {
     339        3552 :     effects->log = NULL;
     340        3552 :   }
     341             : 
     342             :   /* Capture input regions */
     343        4715 :   ulong tmp_end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
     344        4715 :   ulong input_regions_size = fd_solfuzz_vm_load_from_input_regions(
     345        4715 :       vm->input_mem_regions,
     346        4715 :       vm->input_mem_regions_cnt,
     347        4715 :       &effects->input_data_regions,
     348        4715 :       &effects->input_data_regions_count,
     349        4715 :       (void *)tmp_end,
     350        4715 :       fd_ulong_sat_sub( output_end, tmp_end )
     351        4715 :   );
     352             : 
     353        4765 :   if( !!vm->input_mem_regions_cnt && !effects->input_data_regions ) {
     354           0 :     goto error;
     355           0 :   }
     356             : 
     357             :   /* Return the effects */
     358        4715 :   ulong actual_end = tmp_end + input_regions_size;
     359        4715 :   fd_solfuzz_pb_instr_ctx_destroy( runner, ctx );
     360             : 
     361        4715 :   *output = effects;
     362        4715 :   return actual_end - (ulong)output_buf;
     363             : 
     364           0 : error:
     365           0 :   fd_solfuzz_pb_instr_ctx_destroy( runner, ctx );
     366           0 :   return 0;
     367        4715 : }

Generated by: LCOV version 1.14