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 : }
|