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 int
14 : fd_solfuzz_vm_syscall_noop( void * _vm,
15 : ulong arg0,
16 : ulong arg1,
17 : ulong arg2,
18 : ulong arg3,
19 : ulong arg4,
20 0 : ulong* _ret){
21 : /* TODO: have input message determine CUs to deduct?
22 : fd_vm_t * vm = (fd_vm_t *) _vm;
23 : vm->cu = vm->cu - 5;
24 : */
25 :
26 0 : (void) _vm;
27 0 : (void) arg0;
28 0 : (void) arg1;
29 0 : (void) arg2;
30 0 : (void) arg3;
31 0 : (void) arg4;
32 0 : *_ret = 0;
33 0 : return 0;
34 0 : }
35 :
36 : static fd_sbpf_syscalls_t *
37 : fd_solfuzz_vm_syscall_lookup_func( fd_sbpf_syscalls_t * syscalls,
38 : const char * syscall_name,
39 0 : size_t len) {
40 0 : ulong i;
41 :
42 0 : if (!syscall_name) return NULL;
43 :
44 0 : for (i = 0; i < fd_sbpf_syscalls_slot_cnt(); ++i) {
45 0 : if (!fd_sbpf_syscalls_key_inval(syscalls[i].key) && syscalls[i].name && strlen(syscalls[i].name) == len) {
46 0 : if (!memcmp(syscalls[i].name, syscall_name, len)) {
47 0 : return syscalls + i;
48 0 : }
49 0 : }
50 0 : }
51 :
52 0 : return NULL;
53 0 : }
54 :
55 : static ulong
56 : fd_solfuzz_vm_load_from_input_regions( fd_vm_input_region_t const * input,
57 : uint input_count,
58 : fd_exec_test_input_data_region_t ** output,
59 : pb_size_t * output_count,
60 : void * output_buf,
61 0 : ulong output_bufsz ) {
62 : /* pre-flight checks on output buffer size*/
63 0 : ulong input_regions_total_sz = 0;
64 0 : for( ulong i=0; i<input_count; i++ ) {
65 0 : input_regions_total_sz += input[i].region_sz;
66 0 : }
67 :
68 0 : if( FD_UNLIKELY( input_regions_total_sz == 0
69 0 : || output_bufsz < input_regions_total_sz ) ) {
70 0 : *output = NULL;
71 0 : *output_count = 0;
72 0 : return 0;
73 0 : }
74 :
75 0 : FD_SCRATCH_ALLOC_INIT( l, output_buf );
76 0 : *output = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_input_data_region_t),
77 0 : input_count * sizeof (fd_exec_test_input_data_region_t) );
78 0 : FD_TEST( *output );
79 0 : *output_count = input_count;
80 :
81 0 : for( ulong i=0; i<input_count; i++ ) {
82 0 : fd_vm_input_region_t const * vm_region = &input[i];
83 0 : fd_exec_test_input_data_region_t * out_region = &(*output)[i];
84 0 : out_region->is_writable = vm_region->is_writable;
85 0 : out_region->offset = vm_region->vaddr_offset;
86 :
87 0 : if( vm_region->region_sz > 0 ) {
88 0 : out_region->content = FD_SCRATCH_ALLOC_APPEND( l, alignof(pb_bytes_array_t),
89 0 : PB_BYTES_ARRAY_T_ALLOCSIZE(vm_region->region_sz) );
90 0 : FD_TEST( out_region->content );
91 0 : out_region->content->size = vm_region->region_sz;
92 0 : fd_memcpy( out_region->content->bytes, (void *)vm_region->haddr, vm_region->region_sz );
93 0 : } else {
94 0 : out_region->content = NULL;
95 0 : }
96 0 : }
97 :
98 0 : ulong end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
99 0 : return end - (ulong)output_buf; /* return the number of bytes written */
100 0 : }
101 :
102 :
103 : ulong
104 : fd_solfuzz_pb_vm_interp_run( fd_solfuzz_runner_t * runner,
105 : void const * input_,
106 : void ** output_,
107 : void * output_buf,
108 0 : ulong output_bufsz ) {
109 0 : fd_exec_test_syscall_context_t const * input = fd_type_pun_const( input_ );
110 0 : fd_exec_test_syscall_effects_t ** output = fd_type_pun( output_ );
111 :
112 : /* Create execution context */
113 0 : const fd_exec_test_instr_context_t * input_instr_ctx = &input->instr_ctx;
114 0 : fd_exec_instr_ctx_t instr_ctx[1];
115 0 : if( !fd_solfuzz_pb_instr_ctx_create( runner, instr_ctx, input_instr_ctx, true /* is_syscall avoids certain checks we don't want */ ) ) {
116 0 : fd_solfuzz_pb_instr_ctx_destroy( runner, instr_ctx );
117 0 : return 0UL;
118 0 : }
119 :
120 0 : if( !( input->has_vm_ctx ) ) {
121 0 : fd_solfuzz_pb_instr_ctx_destroy( runner, instr_ctx );
122 0 : return 0UL;
123 0 : }
124 :
125 0 : fd_spad_t * spad = runner->spad;
126 0 : instr_ctx->bank = runner->bank;
127 :
128 : /* Create effects */
129 0 : ulong output_end = (ulong) output_buf + output_bufsz;
130 0 : FD_SCRATCH_ALLOC_INIT( l, output_buf );
131 0 : fd_exec_test_syscall_effects_t * effects =
132 0 : FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_syscall_effects_t),
133 0 : sizeof (fd_exec_test_syscall_effects_t) );
134 0 : *effects = (fd_exec_test_syscall_effects_t) FD_EXEC_TEST_SYSCALL_EFFECTS_INIT_ZERO;
135 :
136 0 : if( FD_UNLIKELY( _l > output_end ) ) {
137 0 : fd_solfuzz_pb_instr_ctx_destroy( runner, instr_ctx );
138 0 : return 0UL;
139 0 : }
140 :
141 0 : do{
142 : /* Setup regions */
143 0 : if ( !input->vm_ctx.rodata ) {
144 0 : break;
145 0 : }
146 0 : ulong rodata_sz = input->vm_ctx.rodata->size;
147 0 : uchar * rodata = fd_spad_alloc_check( spad, 8UL, rodata_sz );
148 0 : memcpy( rodata, input->vm_ctx.rodata->bytes, rodata_sz );
149 :
150 : /* Setup input region */
151 0 : ulong input_sz = 0UL;
152 0 : ulong pre_lens[256] = {0};
153 0 : fd_vm_input_region_t input_mem_regions[1000] = {0}; /* We can have a max of (3 * num accounts + 1) regions */
154 0 : fd_vm_acc_region_meta_t acc_region_metas[256] = {0}; /* instr acc idx to idx */
155 0 : uint input_mem_regions_cnt = 0UL;
156 0 : int direct_mapping = FD_FEATURE_ACTIVE_BANK( instr_ctx->bank, account_data_direct_mapping );
157 0 : int stricter_abi_and_runtime_constraints = FD_FEATURE_ACTIVE_BANK( instr_ctx->bank, stricter_abi_and_runtime_constraints );
158 :
159 0 : uchar * input_ptr = NULL;
160 0 : uchar program_id_idx = instr_ctx->instr->program_id;
161 0 : fd_account_meta_t const * program_acc = instr_ctx->txn_out->accounts.metas[program_id_idx];
162 0 : uchar is_deprecated = ( program_id_idx < instr_ctx->txn_out->accounts.cnt ) &&
163 0 : ( !memcmp( program_acc->owner, fd_solana_bpf_loader_deprecated_program_id.key, sizeof(fd_pubkey_t) ) );
164 :
165 : /* Push the instruction onto the stack. This may also modify the sysvar instructions account, if its present. */
166 0 : int stack_push_err = fd_instr_stack_push( instr_ctx->runtime, instr_ctx->txn_in, instr_ctx->txn_out, (fd_instr_info_t *)instr_ctx->instr );
167 0 : if( FD_UNLIKELY( stack_push_err ) ) {
168 0 : FD_LOG_WARNING(( "instr stack push err" ));
169 0 : fd_solfuzz_pb_instr_ctx_destroy( runner, instr_ctx );
170 0 : return 0;
171 0 : }
172 :
173 : /* Serialize accounts into input memory region. */
174 0 : ulong instr_data_offset = 0UL;
175 0 : int err = fd_bpf_loader_input_serialize_parameters( instr_ctx,
176 0 : &input_sz,
177 0 : pre_lens,
178 0 : input_mem_regions,
179 0 : &input_mem_regions_cnt,
180 0 : acc_region_metas,
181 0 : stricter_abi_and_runtime_constraints,
182 0 : direct_mapping,
183 0 : is_deprecated,
184 0 : &instr_data_offset,
185 0 : &input_ptr );
186 0 : if( FD_UNLIKELY( err ) ) {
187 0 : fd_solfuzz_pb_instr_ctx_destroy( runner, instr_ctx );
188 0 : return 0;
189 0 : }
190 :
191 0 : if( input->vm_ctx.heap_max>FD_VM_HEAP_DEFAULT ) {
192 0 : break;
193 0 : }
194 :
195 : /* Setup calldests from call_whitelist.
196 : Alloc calldests with the expected size (1 bit per ix, rounded up to ulong) */
197 0 : ulong max_pc = (rodata_sz + 7) / 8;
198 0 : ulong calldests_footprint = fd_sbpf_calldests_footprint( max_pc );
199 0 : void * calldests_mem = fd_spad_alloc_check( spad, fd_sbpf_calldests_align(), calldests_footprint );
200 0 : ulong * calldests = fd_sbpf_calldests_join( fd_sbpf_calldests_new( calldests_mem, max_pc ) );
201 0 : if( input->vm_ctx.call_whitelist && input->vm_ctx.call_whitelist->size > 0 ) {
202 0 : memcpy( calldests, input->vm_ctx.call_whitelist->bytes, input->vm_ctx.call_whitelist->size );
203 : /* Make sure bits over max_pc are all 0s. */
204 0 : ulong mask = (1UL << (max_pc % 64)) - 1UL;
205 0 : if ( max_pc % 64 != 0) {
206 0 : calldests[ max_pc / 64 ] &= mask;
207 0 : }
208 0 : }
209 0 : ulong entry_pc = fd_ulong_min( input->vm_ctx.entry_pc, rodata_sz / 8UL - 1UL );
210 0 : if( input->vm_ctx.sbpf_version >= FD_SBPF_V3 ) {
211 : /* in v3 we have to enable the entrypoint */
212 0 : calldests[ entry_pc / 64UL ] |= ( 1UL << ( entry_pc % 64UL ) );
213 0 : }
214 :
215 : /* Setup syscalls. Have them all be no-ops */
216 0 : fd_sbpf_syscalls_t * syscalls = fd_sbpf_syscalls_new( fd_spad_alloc_check( spad, fd_sbpf_syscalls_align(), fd_sbpf_syscalls_footprint() ) );
217 0 : fd_vm_syscall_register_slot( syscalls,
218 0 : fd_bank_slot_get( instr_ctx->bank ),
219 0 : fd_bank_features_query( instr_ctx->bank ),
220 0 : 0 );
221 :
222 0 : for( ulong i=0; i< fd_sbpf_syscalls_slot_cnt(); i++ ){
223 0 : if( !fd_sbpf_syscalls_key_inval( syscalls[i].key ) ) {
224 0 : syscalls[i].func = fd_solfuzz_vm_syscall_noop;
225 0 : }
226 0 : }
227 :
228 : /* Setup trace */
229 0 : const int enable_vm_tracing = runner->enable_vm_tracing;
230 0 : fd_vm_trace_t * trace = NULL;
231 :
232 0 : if ( FD_UNLIKELY( enable_vm_tracing ) ) {
233 0 : trace = fd_vm_trace_new( fd_spad_alloc_check( spad, fd_vm_trace_align(), fd_vm_trace_footprint( FD_RUNTIME_VM_TRACE_EVENT_MAX, FD_RUNTIME_VM_TRACE_EVENT_DATA_MAX ) ), FD_RUNTIME_VM_TRACE_EVENT_MAX, FD_RUNTIME_VM_TRACE_EVENT_DATA_MAX );
234 0 : }
235 :
236 : /* Setup vm */
237 0 : fd_vm_t * vm = fd_vm_join( fd_vm_new( fd_spad_alloc_check( spad, fd_vm_align(), fd_vm_footprint() ) ) );
238 0 : FD_TEST( vm );
239 :
240 0 : fd_vm_init(
241 0 : vm,
242 0 : instr_ctx,
243 0 : input->vm_ctx.heap_max,
244 0 : input->has_instr_ctx ? input->instr_ctx.cu_avail : 0,
245 0 : rodata,
246 0 : rodata_sz,
247 0 : (ulong *) rodata, /* text*, same as rodata */
248 0 : rodata_sz / 8, /* text_cnt */
249 0 : 0, /* text_off */
250 0 : rodata_sz, /* text_sz */
251 0 : entry_pc,
252 0 : calldests,
253 0 : input->vm_ctx.sbpf_version,
254 0 : syscalls,
255 0 : trace, /* trace */
256 0 : NULL, /* sha */
257 0 : input_mem_regions,
258 0 : input_mem_regions_cnt,
259 0 : acc_region_metas, /* vm_acc_region_meta*/
260 0 : is_deprecated, /* is deprecated */
261 0 : direct_mapping, /* direct mapping */
262 0 : stricter_abi_and_runtime_constraints, /* stricter_abi_and_runtime_constraints */
263 0 : 0 /* dump_syscall_to_pb */,
264 0 : 0UL /* r2 is set by the fuzzer below */
265 0 : );
266 :
267 : /* Setup registers.
268 : r1, r10, r11 are initialized by EbpfVm::new (r10) or EbpfVm::execute_program (r1, r11),
269 : or equivalently by fd_vm_init.
270 : Modifying them will most like break execution.
271 : In syscalls we allow override them (especially r1) because that simulates the fact
272 : that a program partially executed before reaching the syscall.
273 : Here we want to test what happens when the program starts from the beginning. */
274 0 : vm->reg[0] = input->vm_ctx.r0;
275 : // vm->reg[1] = input->vm_ctx.r1; // do not override
276 0 : vm->reg[2] = input->vm_ctx.r2;
277 0 : vm->reg[3] = input->vm_ctx.r3;
278 0 : vm->reg[4] = input->vm_ctx.r4;
279 0 : vm->reg[5] = input->vm_ctx.r5;
280 0 : vm->reg[6] = input->vm_ctx.r6;
281 0 : vm->reg[7] = input->vm_ctx.r7;
282 0 : vm->reg[8] = input->vm_ctx.r8;
283 0 : vm->reg[9] = input->vm_ctx.r9;
284 : // vm->reg[10] = input->vm_ctx.r10; // do not override
285 : // vm->reg[11] = input->vm_ctx.r11; // do not override
286 :
287 : // Validate the vm
288 0 : if( fd_vm_validate( vm ) != FD_VM_SUCCESS ) {
289 : // custom error, avoid -1 because we use it for "unknown error" in solfuzz-agave
290 0 : effects->error = -2;
291 0 : break;
292 0 : }
293 :
294 0 : if( input->syscall_invocation.stack_prefix ) {
295 0 : uchar * stack = input->syscall_invocation.stack_prefix->bytes;
296 0 : ulong stack_sz = fd_ulong_min(input->syscall_invocation.stack_prefix->size, FD_VM_STACK_MAX);
297 0 : fd_memcpy( vm->stack, stack, stack_sz );
298 0 : }
299 :
300 0 : if( input->syscall_invocation.heap_prefix ) {
301 0 : uchar * heap = input->syscall_invocation.heap_prefix->bytes;
302 0 : ulong heap_sz = fd_ulong_min(input->syscall_invocation.heap_prefix->size, FD_VM_HEAP_MAX);
303 0 : fd_memcpy( vm->heap, heap, heap_sz );
304 0 : }
305 :
306 : /* Run vm */
307 0 : int exec_res = 0;
308 0 : if ( FD_UNLIKELY( enable_vm_tracing ) ) {
309 0 : exec_res = fd_vm_exec_trace( vm );
310 0 : if( enable_vm_tracing ) fd_vm_trace_printf( trace, syscalls );
311 0 : fd_vm_trace_delete( fd_vm_trace_leave( trace ) );
312 0 : } else {
313 0 : exec_res = fd_vm_exec_notrace( vm );
314 0 : }
315 :
316 0 : effects->error = -exec_res;
317 :
318 : /* We do not compare VM state on CU errors since CU accounting
319 : is non-conformant with the Agave JIT/Interpreter.
320 : CU consumption is not precisely defined when VM faults */
321 0 : if( FD_UNLIKELY( exec_res==FD_VM_ERR_EBPF_EXCEEDED_MAX_INSTRUCTIONS ) )
322 0 : break;
323 :
324 : /* Capture remaining outputs */
325 0 : effects->cu_avail = vm->cu;
326 0 : effects->frame_count = vm->frame_cnt;
327 : /* Only capture registers if no error */
328 0 : effects->r0 = exec_res ? 0 : vm->reg[0];
329 0 : effects->r1 = exec_res ? 0 : vm->reg[1];
330 0 : effects->r2 = exec_res ? 0 : vm->reg[2];
331 0 : effects->r3 = exec_res ? 0 : vm->reg[3];
332 0 : effects->r4 = exec_res ? 0 : vm->reg[4];
333 0 : effects->r5 = exec_res ? 0 : vm->reg[5];
334 0 : effects->r6 = exec_res ? 0 : vm->reg[6];
335 0 : effects->r7 = exec_res ? 0 : vm->reg[7];
336 0 : effects->r8 = exec_res ? 0 : vm->reg[8];
337 0 : effects->r9 = exec_res ? 0 : vm->reg[9];
338 0 : effects->r10 = exec_res ? 0 : vm->reg[10];
339 :
340 : /* skip logs since syscalls are stubbed */
341 :
342 0 : effects->pc = vm->pc;
343 :
344 0 : if( vm->heap_max > 0 ) {
345 0 : effects->heap = FD_SCRATCH_ALLOC_APPEND(
346 0 : l, alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE( vm->heap_max ) );
347 0 : effects->heap->size = (uint)vm->heap_max;
348 0 : fd_memcpy( effects->heap->bytes, vm->heap, vm->heap_max );
349 0 : }
350 :
351 : /* Compress stack by removing right-most 0s.
352 : This reduces the total size of effects/fixtures when stack is not used,
353 : otherwise each would waste 256kB. */
354 0 : int rtrim_sz;
355 0 : for( rtrim_sz=FD_VM_STACK_MAX-1; rtrim_sz>=0; rtrim_sz-- ) {
356 0 : if( vm->stack[rtrim_sz] != 0 ) break;
357 0 : }
358 0 : if( rtrim_sz > 0 || (vm->stack[0] != 0) ) {
359 0 : effects->stack = FD_SCRATCH_ALLOC_APPEND(
360 0 : l, alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE( FD_VM_STACK_MAX ) );
361 0 : effects->stack->size = (uint)rtrim_sz+1;
362 0 : fd_memcpy( effects->stack->bytes, vm->stack, (ulong)rtrim_sz+1 );
363 0 : }
364 :
365 0 : effects->rodata = FD_SCRATCH_ALLOC_APPEND(
366 0 : l, alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE( rodata_sz ) );
367 0 : effects->rodata->size = (uint)rodata_sz;
368 0 : fd_memcpy( effects->rodata->bytes, rodata, rodata_sz );
369 :
370 : /* Capture input data regions */
371 0 : ulong tmp_end = FD_SCRATCH_ALLOC_FINI(l, 1UL);
372 0 : ulong input_data_regions_size = fd_solfuzz_vm_load_from_input_regions(
373 0 : vm->input_mem_regions,
374 0 : vm->input_mem_regions_cnt,
375 0 : &effects->input_data_regions,
376 0 : &effects->input_data_regions_count,
377 0 : (void *) tmp_end,
378 0 : fd_ulong_sat_sub( output_end, tmp_end )
379 0 : );
380 0 : FD_SCRATCH_ALLOC_APPEND( l, 1UL, input_data_regions_size );
381 :
382 0 : } while(0);
383 :
384 0 : ulong actual_end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
385 0 : *output = effects;
386 0 : fd_solfuzz_pb_instr_ctx_destroy( runner, instr_ctx );
387 0 : return actual_end - (ulong)output_buf;
388 0 : }
389 :
390 : ulong
391 : fd_solfuzz_pb_syscall_run( fd_solfuzz_runner_t * runner,
392 : void const * input_,
393 : void ** output_,
394 : void * output_buf,
395 0 : ulong output_bufsz ) {
396 0 : fd_exec_test_syscall_context_t const * input = fd_type_pun_const( input_ );
397 0 : fd_exec_test_syscall_effects_t ** output = fd_type_pun( output_ );
398 :
399 : /* Create execution context */
400 0 : const fd_exec_test_instr_context_t * input_instr_ctx = &input->instr_ctx;
401 0 : fd_exec_instr_ctx_t ctx[1];
402 : // Skip extra checks for non-CPI syscalls
403 0 : int is_cpi = !strncmp( (const char *)input->syscall_invocation.function_name.bytes, "sol_invoke_signed", 17 );
404 0 : int skip_extra_checks = !is_cpi;
405 :
406 0 : if( !fd_solfuzz_pb_instr_ctx_create( runner, ctx, input_instr_ctx, skip_extra_checks ) )
407 0 : goto error;
408 :
409 0 : ctx->txn_out->err.exec_err = 0;
410 0 : ctx->txn_out->err.exec_err_kind = FD_EXECUTOR_ERR_KIND_NONE;
411 0 : ctx->bank = runner->bank;
412 :
413 : /* Capture outputs */
414 0 : ulong output_end = (ulong)output_buf + output_bufsz;
415 0 : FD_SCRATCH_ALLOC_INIT( l, output_buf );
416 0 : fd_exec_test_syscall_effects_t * effects =
417 0 : FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_syscall_effects_t),
418 0 : sizeof (fd_exec_test_syscall_effects_t) );
419 0 : if( FD_UNLIKELY( _l > output_end ) ) {
420 0 : goto error;
421 0 : }
422 :
423 0 : if( input->vm_ctx.return_data.program_id && input->vm_ctx.return_data.program_id->size == sizeof(fd_pubkey_t) ) {
424 0 : fd_memcpy( ctx->txn_out->details.return_data.program_id.uc, input->vm_ctx.return_data.program_id->bytes, sizeof(fd_pubkey_t) );
425 0 : }
426 :
427 0 : if( input->vm_ctx.return_data.data && input->vm_ctx.return_data.data->size>0U ) {
428 0 : ctx->txn_out->details.return_data.len = input->vm_ctx.return_data.data->size;
429 0 : fd_memcpy( ctx->txn_out->details.return_data.data, input->vm_ctx.return_data.data->bytes, ctx->txn_out->details.return_data.len );
430 0 : }
431 :
432 0 : *effects = (fd_exec_test_syscall_effects_t) FD_EXEC_TEST_SYSCALL_EFFECTS_INIT_ZERO;
433 :
434 : /* Set up the VM instance */
435 0 : fd_spad_t * spad = runner->spad;
436 0 : fd_sha256_t _sha[1];
437 0 : fd_sha256_t * sha = fd_sha256_join( fd_sha256_new( _sha ) );
438 0 : fd_sbpf_syscalls_t * syscalls = fd_sbpf_syscalls_new( fd_spad_alloc_check( spad, fd_sbpf_syscalls_align(), fd_sbpf_syscalls_footprint() ) );
439 0 : fd_vm_syscall_register_all( syscalls, 0 );
440 :
441 : /* Pull out the memory regions */
442 0 : if( !input->has_vm_ctx ) {
443 0 : goto error;
444 0 : }
445 :
446 0 : ulong rodata_sz = input->vm_ctx.rodata ? input->vm_ctx.rodata->size : 0UL;
447 0 : uchar * rodata = fd_spad_alloc_check( spad, 8UL, rodata_sz );
448 0 : if ( input->vm_ctx.rodata != NULL ) {
449 0 : fd_memcpy( rodata, input->vm_ctx.rodata->bytes, rodata_sz );
450 0 : }
451 :
452 0 : if( input->vm_ctx.heap_max > FD_VM_HEAP_MAX ) {
453 0 : goto error;
454 0 : }
455 :
456 0 : fd_vm_t * vm = fd_vm_join( fd_vm_new( fd_spad_alloc_check( spad, fd_vm_align(), fd_vm_footprint() ) ) );
457 0 : if ( !vm ) {
458 0 : goto error;
459 0 : }
460 :
461 : /* If the program ID account owner is the v1 BPF loader, then alignment is disabled (controlled by
462 : the `is_deprecated` flag) */
463 :
464 0 : ulong input_sz = 0UL;
465 0 : ulong pre_lens[256] = {0};
466 0 : fd_vm_input_region_t input_mem_regions[1000] = {0}; /* We can have a max of (3 * num accounts + 1) regions */
467 0 : fd_vm_acc_region_meta_t acc_region_metas[256] = {0}; /* instr acc idx to idx */
468 0 : uint input_mem_regions_cnt = 0U;
469 0 : int direct_mapping = FD_FEATURE_ACTIVE_BANK( ctx->bank, account_data_direct_mapping );
470 0 : int stricter_abi_and_runtime_constraints = FD_FEATURE_ACTIVE_BANK( ctx->bank, stricter_abi_and_runtime_constraints );
471 :
472 0 : uchar * input_ptr = NULL;
473 0 : uchar program_id_idx = ctx->instr->program_id;
474 0 : fd_account_meta_t * program_acc = ctx->txn_out->accounts.metas[program_id_idx];
475 0 : uchar is_deprecated = ( program_id_idx < ctx->txn_out->accounts.cnt ) &&
476 0 : ( !memcmp( program_acc->owner, fd_solana_bpf_loader_deprecated_program_id.key, sizeof(fd_pubkey_t) ) );
477 :
478 : /* Push the instruction onto the stack. This may also modify the sysvar instructions account, if its present. */
479 0 : int stack_push_err = fd_instr_stack_push( ctx->runtime, ctx->txn_in, ctx->txn_out, (fd_instr_info_t *)ctx->instr );
480 0 : if( FD_UNLIKELY( stack_push_err ) ) {
481 0 : FD_LOG_WARNING(( "instr stack push err" ));
482 0 : goto error;
483 0 : }
484 :
485 : /* Serialize accounts into input memory region. */
486 0 : ulong instr_data_offset = 0UL;
487 0 : int err = fd_bpf_loader_input_serialize_parameters( ctx,
488 0 : &input_sz,
489 0 : pre_lens,
490 0 : input_mem_regions,
491 0 : &input_mem_regions_cnt,
492 0 : acc_region_metas,
493 0 : stricter_abi_and_runtime_constraints,
494 0 : direct_mapping,
495 0 : is_deprecated,
496 0 : &instr_data_offset,
497 0 : &input_ptr );
498 0 : if( FD_UNLIKELY( err ) ) {
499 0 : FD_LOG_WARNING(( "bpf loader input serialize parameters err" ));
500 0 : goto error;
501 0 : }
502 :
503 0 : fd_vm_init( vm,
504 0 : ctx,
505 0 : input->vm_ctx.heap_max,
506 0 : ctx->txn_out->details.compute_budget.compute_meter,
507 0 : rodata,
508 0 : rodata_sz,
509 0 : NULL, // TODO
510 0 : 0, // TODO
511 0 : 0, // TODO
512 0 : 0, // TODO, text_sz
513 0 : 0, // TODO
514 0 : NULL, // TODO
515 0 : TEST_VM_DEFAULT_SBPF_VERSION,
516 0 : syscalls,
517 0 : NULL, // TODO
518 0 : sha,
519 0 : input_mem_regions,
520 0 : input_mem_regions_cnt,
521 0 : acc_region_metas,
522 0 : is_deprecated,
523 0 : direct_mapping,
524 0 : stricter_abi_and_runtime_constraints,
525 0 : 0 /* dump_syscall_to_pb */,
526 0 : 0UL /* r2 is set by the fuzzer below */ );
527 :
528 : // Override some execution state values from the syscall fuzzer input
529 : // This is so we can test if the syscall mutates any of these erroneously
530 0 : vm->reg[0] = input->vm_ctx.r0;
531 0 : vm->reg[1] = input->vm_ctx.r1;
532 0 : vm->reg[2] = input->vm_ctx.r2;
533 0 : vm->reg[3] = input->vm_ctx.r3;
534 0 : vm->reg[4] = input->vm_ctx.r4;
535 0 : vm->reg[5] = input->vm_ctx.r5;
536 0 : vm->reg[6] = input->vm_ctx.r6;
537 0 : vm->reg[7] = input->vm_ctx.r7;
538 0 : vm->reg[8] = input->vm_ctx.r8;
539 0 : vm->reg[9] = input->vm_ctx.r9;
540 0 : vm->reg[10] = input->vm_ctx.r10;
541 0 : vm->reg[11] = input->vm_ctx.r11;
542 :
543 : // Override initial part of the heap, if specified the syscall fuzzer input
544 0 : if( input->syscall_invocation.heap_prefix ) {
545 0 : fd_memcpy( vm->heap, input->syscall_invocation.heap_prefix->bytes,
546 0 : fd_ulong_min(input->syscall_invocation.heap_prefix->size, vm->heap_max) );
547 0 : }
548 :
549 : // Override initial part of the stack, if specified the syscall fuzzer input
550 0 : if( input->syscall_invocation.stack_prefix ) {
551 0 : fd_memcpy( vm->stack, input->syscall_invocation.stack_prefix->bytes,
552 0 : fd_ulong_min(input->syscall_invocation.stack_prefix->size, FD_VM_STACK_MAX) );
553 0 : }
554 :
555 : // Look up the syscall to execute
556 0 : char * syscall_name = (char *)input->syscall_invocation.function_name.bytes;
557 0 : fd_sbpf_syscalls_t const * syscall = fd_solfuzz_vm_syscall_lookup_func(syscalls, syscall_name, input->syscall_invocation.function_name.size);
558 0 : if( !syscall ) {
559 0 : goto error;
560 0 : }
561 :
562 : /* There's an instr ctx struct embedded in the txn ctx instr stack. */
563 0 : fd_exec_instr_ctx_t * instr_ctx = &ctx->runtime->instr.stack[ ctx->runtime->instr.stack_sz - 1 ];
564 0 : *instr_ctx = (fd_exec_instr_ctx_t) {
565 0 : .instr = ctx->instr,
566 0 : .txn_out = ctx->txn_out,
567 0 : .runtime = ctx->runtime,
568 0 : };
569 :
570 : /* Actually invoke the syscall */
571 0 : int syscall_err = syscall->func( vm, vm->reg[1], vm->reg[2], vm->reg[3], vm->reg[4], vm->reg[5], &vm->reg[0] );
572 0 : int stack_pop_err = fd_instr_stack_pop( ctx->runtime, ctx->txn_out, ctx->instr );
573 0 : if( FD_UNLIKELY( stack_pop_err ) ) {
574 0 : FD_LOG_WARNING(( "instr stack pop err" ));
575 0 : goto error;
576 0 : }
577 0 : if( syscall_err ) {
578 0 : fd_log_collector_program_failure( vm->instr_ctx );
579 0 : }
580 :
581 : /* Capture the effects */
582 0 : int exec_err = vm->instr_ctx->txn_out->err.exec_err;
583 0 : effects->error = 0;
584 0 : if( syscall_err ) {
585 0 : if( exec_err==0 ) {
586 0 : FD_LOG_WARNING(( "TODO: syscall returns error, but exec_err not set. this is probably missing a log." ));
587 0 : effects->error = -1;
588 0 : } else {
589 0 : effects->error = (exec_err <= 0) ? -exec_err : -1;
590 :
591 : /* Map error kind, equivalent to:
592 : effects->error_kind = (fd_exec_test_err_kind_t)(vm->instr_ctx->txn_ctx->err.exec_err_kind); */
593 0 : switch (vm->instr_ctx->txn_out->err.exec_err_kind) {
594 0 : case FD_EXECUTOR_ERR_KIND_EBPF:
595 0 : effects->error_kind = FD_EXEC_TEST_ERR_KIND_EBPF;
596 0 : break;
597 0 : case FD_EXECUTOR_ERR_KIND_SYSCALL:
598 0 : effects->error_kind = FD_EXEC_TEST_ERR_KIND_SYSCALL;
599 0 : break;
600 0 : case FD_EXECUTOR_ERR_KIND_INSTR:
601 0 : effects->error_kind = FD_EXEC_TEST_ERR_KIND_INSTRUCTION;
602 0 : break;
603 0 : default:
604 0 : effects->error_kind = FD_EXEC_TEST_ERR_KIND_UNSPECIFIED;
605 0 : break;
606 0 : }
607 0 : }
608 0 : }
609 0 : effects->r0 = syscall_err ? 0 : vm->reg[0]; // Save only on success
610 0 : effects->cu_avail = (ulong)vm->cu;
611 :
612 0 : if( vm->heap_max ) {
613 0 : effects->heap = FD_SCRATCH_ALLOC_APPEND(
614 0 : l, alignof(uint), PB_BYTES_ARRAY_T_ALLOCSIZE( vm->heap_max ) );
615 0 : if( FD_UNLIKELY( _l > output_end ) ) {
616 0 : goto error;
617 0 : }
618 0 : effects->heap->size = (uint)vm->heap_max;
619 0 : fd_memcpy( effects->heap->bytes, vm->heap, vm->heap_max );
620 0 : } else {
621 0 : effects->heap = NULL;
622 0 : }
623 :
624 0 : effects->stack = FD_SCRATCH_ALLOC_APPEND(
625 0 : l, alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE( FD_VM_STACK_MAX ) );
626 0 : if( FD_UNLIKELY( _l > output_end ) ) {
627 0 : goto error;
628 0 : }
629 0 : effects->stack->size = (uint)FD_VM_STACK_MAX;
630 0 : fd_memcpy( effects->stack->bytes, vm->stack, FD_VM_STACK_MAX );
631 :
632 0 : if( vm->rodata_sz ) {
633 0 : effects->rodata = FD_SCRATCH_ALLOC_APPEND(
634 0 : l, alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE( rodata_sz ) );
635 0 : if( FD_UNLIKELY( _l > output_end ) ) {
636 0 : goto error;
637 0 : }
638 0 : effects->rodata->size = (uint)rodata_sz;
639 0 : fd_memcpy( effects->rodata->bytes, vm->rodata, rodata_sz );
640 0 : } else {
641 0 : effects->rodata = NULL;
642 0 : }
643 :
644 0 : effects->frame_count = vm->frame_cnt;
645 :
646 0 : fd_log_collector_t * log = vm->instr_ctx->runtime->log.log_collector;
647 : /* Only collect log on valid errors (i.e., != -1). Follows
648 : https://github.com/firedancer-io/solfuzz-agave/blob/99758d3c4f3a342d56e2906936458d82326ae9a8/src/utils/err_map.rs#L148 */
649 0 : if( effects->error != -1 && log->buf_sz ) {
650 0 : effects->log = FD_SCRATCH_ALLOC_APPEND(
651 0 : l, alignof(pb_bytes_array_t), PB_BYTES_ARRAY_T_ALLOCSIZE( log->buf_sz ) );
652 0 : if( FD_UNLIKELY( _l > output_end ) ) {
653 0 : goto error;
654 0 : }
655 0 : effects->log->size = (uint)fd_log_collector_debug_sprintf( log, (char *)effects->log->bytes, 0 );
656 0 : } else {
657 0 : effects->log = NULL;
658 0 : }
659 :
660 : /* Capture input regions */
661 0 : effects->inputdata = NULL; /* Deprecated, using input_data_regions instead */
662 0 : ulong tmp_end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
663 0 : ulong input_regions_size = fd_solfuzz_vm_load_from_input_regions(
664 0 : vm->input_mem_regions,
665 0 : vm->input_mem_regions_cnt,
666 0 : &effects->input_data_regions,
667 0 : &effects->input_data_regions_count,
668 0 : (void *)tmp_end,
669 0 : fd_ulong_sat_sub( output_end, tmp_end )
670 0 : );
671 :
672 0 : if( !!vm->input_mem_regions_cnt && !effects->input_data_regions ) {
673 0 : goto error;
674 0 : }
675 :
676 : /* Return the effects */
677 0 : ulong actual_end = tmp_end + input_regions_size;
678 0 : fd_solfuzz_pb_instr_ctx_destroy( runner, ctx );
679 :
680 0 : *output = effects;
681 0 : return actual_end - (ulong)output_buf;
682 :
683 0 : error:
684 0 : fd_solfuzz_pb_instr_ctx_destroy( runner, ctx );
685 0 : return 0;
686 0 : }
|