Line data Source code
1 : #include "fd_vm_test.h"
2 : #include "../context/fd_exec_epoch_ctx.h"
3 : #include "../context/fd_exec_slot_ctx.h"
4 : #include "../context/fd_exec_txn_ctx.h"
5 : #include "../../vm/test_vm_util.h"
6 : #include "fd_exec_instr_test.h"
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 : ulong
32 : fd_exec_vm_validate_test_run( fd_exec_instr_test_runner_t * runner,
33 : void const * input_,
34 : void ** output_,
35 : void * output_buf,
36 858 : ulong output_bufsz ) {
37 858 : (void) runner;
38 858 : fd_exec_test_full_vm_context_t const * input = fd_type_pun_const( input_ );
39 858 : fd_exec_test_validate_vm_effects_t ** output = fd_type_pun( output_ );
40 :
41 858 : if( FD_UNLIKELY( !input->has_vm_ctx ) ) {
42 0 : return 0UL;
43 0 : }
44 :
45 858 : fd_exec_instr_ctx_t * ctx = test_vm_minimal_exec_instr_ctx( fd_libc_alloc_virtual() );
46 :
47 858 : FD_TEST( output_bufsz >= sizeof(fd_exec_test_validate_vm_effects_t) );
48 :
49 : /* Capture outputs */
50 858 : FD_SCRATCH_ALLOC_INIT( l, output_buf );
51 :
52 858 : fd_exec_test_validate_vm_effects_t * effects =
53 858 : FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_validate_vm_effects_t),
54 858 : sizeof (fd_exec_test_validate_vm_effects_t) );
55 858 : FD_SCRATCH_ALLOC_FINI( l, 1UL );
56 :
57 858 : fd_valloc_t valloc = fd_scratch_virtual();
58 858 : do{
59 858 : fd_exec_test_vm_context_t const * vm_ctx = &input->vm_ctx;
60 :
61 : /* Follows prost/solfuzz-agave behavior for empty bytes field */
62 858 : uchar * rodata = NULL;
63 858 : ulong rodata_sz = 0UL;
64 858 : if( FD_LIKELY( vm_ctx->rodata ) ) {
65 765 : rodata = vm_ctx->rodata->bytes;
66 765 : rodata_sz = vm_ctx->rodata->size;
67 765 : }
68 :
69 858 : ulong * text = (ulong *) (rodata + vm_ctx->rodata_text_section_offset);
70 858 : ulong text_cnt = vm_ctx->rodata_text_section_length / 8UL;
71 :
72 858 : fd_vm_t * vm = fd_vm_join( fd_vm_new( fd_valloc_malloc( valloc, fd_vm_align(), fd_vm_footprint() ) ) );
73 858 : FD_TEST( vm );
74 :
75 858 : fd_vm_init(
76 858 : vm,
77 858 : ctx,
78 858 : 0, /* heap_max */
79 858 : 0, /* cu_avail */
80 858 : rodata,
81 858 : rodata_sz,
82 858 : text,
83 858 : text_cnt,
84 858 : vm_ctx->rodata_text_section_offset,
85 858 : vm_ctx->rodata_text_section_length,
86 858 : 0, /* entry_pc, not used in validate at the moment */
87 858 : NULL, /* calldests */
88 858 : NULL, /* syscalls */
89 858 : NULL, /* trace */
90 858 : NULL, /* sha */
91 858 : NULL, /* mem regions */
92 858 : 0, /* mem regions count */
93 858 : NULL, /* mem regions accs */
94 858 : 0, /* is deprecated */
95 : FD_FEATURE_ACTIVE( ctx->slot_ctx, bpf_account_data_direct_mapping ) /* direct mapping */
96 858 : );
97 858 : effects->result = fd_vm_validate( vm );
98 :
99 858 : fd_valloc_free( valloc, fd_vm_delete( fd_vm_leave( vm ) ) );
100 :
101 858 : } while(0);
102 :
103 :
104 : /* Run vm validate and capture result */
105 :
106 858 : effects->success = (effects->result == FD_VM_SUCCESS);
107 858 : *output = effects;
108 :
109 858 : test_vm_exec_instr_ctx_delete( ctx );
110 858 : return sizeof (fd_exec_test_validate_vm_effects_t);
111 858 : }
112 :
113 : void
114 : setup_vm_acc_region_metas( fd_vm_acc_region_meta_t * acc_regions_meta,
115 : fd_vm_t * vm,
116 11217 : fd_exec_instr_ctx_t * instr_ctx ) {
117 : /* cur_region is used to figure out what acc region index the account
118 : corresponds to. */
119 11217 : uint cur_region = 0UL;
120 12456 : for( ulong i=0UL; i<instr_ctx->instr->acct_cnt; i++ ) {
121 1239 : cur_region++;
122 1239 : fd_borrowed_account_t const * acc = instr_ctx->instr->borrowed_accounts[i];
123 1239 : acc_regions_meta[i].region_idx = cur_region;
124 1239 : acc_regions_meta[i].has_data_region = acc->const_meta->dlen>0UL;
125 1239 : acc_regions_meta[i].has_resizing_region = !vm->is_deprecated;
126 1239 : if( acc->const_meta->dlen>0UL ) {
127 501 : cur_region++;
128 501 : }
129 1239 : if( vm->is_deprecated ) {
130 6 : cur_region--;
131 6 : }
132 1239 : }
133 11217 : }
134 :
135 : ulong
136 : fd_exec_vm_interp_test_run( fd_exec_instr_test_runner_t * runner,
137 : fd_exec_test_syscall_context_t const *input,
138 : fd_exec_test_syscall_effects_t **output,
139 : void * output_buf,
140 6 : ulong output_bufsz ) {
141 6 : fd_wksp_t * wksp = fd_wksp_attach( "wksp" );
142 6 : fd_alloc_t * alloc = fd_alloc_join( fd_alloc_new( fd_wksp_alloc_laddr( wksp, fd_alloc_align(), fd_alloc_footprint(), 2 ), 2 ), 0 );
143 :
144 : /* Create execution context */
145 6 : const fd_exec_test_instr_context_t * input_instr_ctx = &input->instr_ctx;
146 6 : fd_exec_instr_ctx_t instr_ctx[1];
147 6 : if( !fd_exec_test_instr_context_create( runner, instr_ctx, input_instr_ctx, alloc, true /* is_syscall avoids certain checks we don't want */ ) ) {
148 0 : fd_exec_test_instr_context_destroy( runner, instr_ctx, wksp, alloc );
149 0 : return 0UL;
150 0 : }
151 :
152 6 : if( !( input->has_vm_ctx && input->has_syscall_invocation ) ) {
153 0 : fd_exec_test_instr_context_destroy( runner, instr_ctx, wksp, alloc );
154 0 : return 0UL;
155 0 : }
156 :
157 6 : fd_valloc_t valloc = fd_scratch_virtual();
158 :
159 : /* Create effects */
160 6 : ulong output_end = (ulong) output_buf + output_bufsz;
161 6 : FD_SCRATCH_ALLOC_INIT( l, output_buf );
162 6 : fd_exec_test_syscall_effects_t * effects =
163 6 : FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_syscall_effects_t),
164 6 : sizeof (fd_exec_test_syscall_effects_t) );
165 6 : *effects = (fd_exec_test_syscall_effects_t) FD_EXEC_TEST_SYSCALL_EFFECTS_INIT_ZERO;
166 :
167 6 : if( FD_UNLIKELY( _l > output_end ) ) {
168 0 : fd_exec_test_instr_context_destroy( runner, instr_ctx, wksp, alloc );
169 0 : return 0UL;
170 0 : }
171 :
172 6 : do{
173 : /* Setup regions */
174 6 : if ( !input->vm_ctx.rodata ) {
175 0 : break;
176 0 : }
177 6 : uchar * rodata = input->vm_ctx.rodata->bytes;
178 6 : ulong rodata_sz = input->vm_ctx.rodata->size;
179 :
180 : /* Load input data regions */
181 6 : fd_vm_input_region_t * input_regions = fd_valloc_malloc( valloc, alignof(fd_vm_input_region_t), sizeof(fd_vm_input_region_t) * input->vm_ctx.input_data_regions_count );
182 6 : uint input_regions_cnt = setup_vm_input_regions( input_regions, input->vm_ctx.input_data_regions, input->vm_ctx.input_data_regions_count, valloc );
183 :
184 6 : if (input->vm_ctx.heap_max > FD_VM_HEAP_DEFAULT) {
185 0 : break;
186 0 : }
187 :
188 : /* Setup calldests from call_whitelist */
189 6 : ulong * calldests = (ulong *) input->vm_ctx.call_whitelist->bytes;
190 :
191 : /* Setup syscalls. Have them all be no-ops */
192 6 : fd_sbpf_syscalls_t * syscalls = fd_sbpf_syscalls_new( fd_valloc_malloc( valloc, fd_sbpf_syscalls_align(), fd_sbpf_syscalls_footprint() ) );
193 6 : fd_vm_syscall_register_all( syscalls, 0 );
194 :
195 774 : for( ulong i=0; i< fd_sbpf_syscalls_slot_cnt(); i++ ){
196 768 : fd_sbpf_syscalls_t * syscall = fd_sbpf_syscalls_query( syscalls, syscalls[i].key, NULL );
197 768 : if ( !syscall ) {
198 558 : continue;
199 558 : }
200 210 : syscall->func = fd_vm_syscall_noop;
201 210 : }
202 :
203 : /* Setup trace */
204 6 : const uint DUMP_TRACE = 0; // Set to 1 to dump trace to stdout
205 6 : uint tracing_enabled = input->vm_ctx.tracing_enabled;
206 6 : fd_vm_trace_t * trace = NULL;
207 6 : ulong event_max = 1UL<<20;
208 6 : ulong event_data_max = 2048UL;
209 :
210 6 : if (!!tracing_enabled) {
211 3 : 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 );
212 3 : }
213 :
214 : /* Setup vm */
215 6 : fd_vm_t * vm = fd_vm_join( fd_vm_new( fd_valloc_malloc( valloc, fd_vm_align(), fd_vm_footprint() ) ) );
216 6 : FD_TEST( vm );
217 :
218 : /* Override some execution state values from the interp fuzzer input
219 : This is so we can test if the interp (or vm setup) mutates any of
220 : these erroneously */
221 6 : vm->reg[0] = input->vm_ctx.r0;
222 6 : vm->reg[1] = input->vm_ctx.r1;
223 6 : vm->reg[2] = input->vm_ctx.r2;
224 6 : vm->reg[3] = input->vm_ctx.r3;
225 6 : vm->reg[4] = input->vm_ctx.r4;
226 6 : vm->reg[5] = input->vm_ctx.r5;
227 6 : vm->reg[6] = input->vm_ctx.r6;
228 6 : vm->reg[7] = input->vm_ctx.r7;
229 6 : vm->reg[8] = input->vm_ctx.r8;
230 6 : vm->reg[9] = input->vm_ctx.r9;
231 6 : vm->reg[10] = input->vm_ctx.r10;
232 6 : vm->reg[11] = input->vm_ctx.r11;
233 :
234 6 : fd_vm_init(
235 6 : vm,
236 6 : instr_ctx,
237 6 : FD_VM_HEAP_MAX,
238 6 : input->has_instr_ctx ? input->instr_ctx.cu_avail : 0,
239 6 : rodata,
240 6 : rodata_sz,
241 6 : (ulong *) rodata, /* text*, same as rodata */
242 6 : rodata_sz / 8, /* text_cnt */
243 6 : 0, /* text_off */
244 6 : rodata_sz, /* text_sz */
245 6 : input->vm_ctx.entry_pc,
246 6 : calldests,
247 6 : syscalls,
248 6 : trace, /* trace */
249 6 : NULL, /* sha */
250 6 : input_regions,
251 6 : input_regions_cnt,
252 6 : NULL, /* vm_acc_region_meta*/
253 6 : 0, /* is deprecated */
254 : FD_FEATURE_ACTIVE( instr_ctx->slot_ctx, bpf_account_data_direct_mapping ) /* direct mapping */
255 6 : );
256 :
257 : // Propagate the acc_regions_meta to the vm
258 6 : vm->acc_region_metas = fd_valloc_malloc( valloc, alignof(fd_vm_acc_region_meta_t), sizeof(fd_vm_acc_region_meta_t) * input->vm_ctx.input_data_regions_count );
259 6 : setup_vm_acc_region_metas( vm->acc_region_metas, vm, vm->instr_ctx );
260 :
261 : // Validate the vm
262 6 : if ( fd_vm_validate( vm ) != FD_VM_SUCCESS ) {
263 0 : effects->error = -1;
264 0 : break;
265 0 : }
266 :
267 6 : if( input->syscall_invocation.stack_prefix ) {
268 6 : uchar * stack = input->syscall_invocation.stack_prefix->bytes;
269 6 : ulong stack_sz = fd_ulong_min(input->syscall_invocation.stack_prefix->size, FD_VM_STACK_MAX);
270 6 : fd_memcpy( vm->stack, stack, stack_sz );
271 6 : }
272 :
273 6 : if( input->syscall_invocation.heap_prefix ) {
274 3 : uchar * heap = input->syscall_invocation.heap_prefix->bytes;
275 3 : ulong heap_sz = fd_ulong_min(input->syscall_invocation.heap_prefix->size, FD_VM_HEAP_MAX);
276 3 : fd_memcpy( vm->heap, heap, heap_sz );
277 3 : }
278 :
279 : /* Run vm */
280 6 : int exec_res = 0;
281 6 : if (!!tracing_enabled) {
282 3 : exec_res = fd_vm_exec_trace( vm );
283 3 : if( DUMP_TRACE ) fd_vm_trace_printf( trace, syscalls );
284 3 : fd_vm_trace_delete( fd_vm_trace_leave( trace ) );
285 3 : } else {
286 3 : exec_res = fd_vm_exec_notrace( vm );
287 3 : }
288 6 : effects->error = -1 * exec_res;
289 :
290 : /* Capture outputs */
291 6 : effects->cu_avail = vm->cu;
292 6 : effects->frame_count = vm->frame_cnt;
293 6 : effects->r0 = exec_res ? 0 : vm->reg[0]; /* Only capture r0 if no error */
294 :
295 : /* skip logs since syscalls are stubbed */
296 :
297 : /* CU error is difficult to properly compare as there may have been
298 : valid writes to the memory regions prior to capturing the error. And
299 : the pc might be well past (by an arbitrary amount) the instruction
300 : where the CU error occurred. */
301 6 : if( exec_res == FD_VM_ERR_SIGCOST ) break;
302 :
303 3 : effects->pc = vm->pc;
304 :
305 3 : effects->heap = FD_SCRATCH_ALLOC_APPEND(
306 3 : l, alignof(uchar), PB_BYTES_ARRAY_T_ALLOCSIZE( vm->heap_max ) );
307 0 : effects->heap->size = (uint)vm->heap_max;
308 3 : fd_memcpy( effects->heap->bytes, vm->heap, vm->heap_max );
309 :
310 3 : effects->stack = FD_SCRATCH_ALLOC_APPEND(
311 3 : l, alignof(uchar), PB_BYTES_ARRAY_T_ALLOCSIZE( FD_VM_STACK_MAX ) );
312 3 : effects->stack->size = (uint)FD_VM_STACK_MAX;
313 3 : fd_memcpy( effects->stack->bytes, vm->stack, FD_VM_STACK_MAX );
314 :
315 3 : effects->rodata = FD_SCRATCH_ALLOC_APPEND(
316 3 : l, alignof(uchar), PB_BYTES_ARRAY_T_ALLOCSIZE( rodata_sz ) );
317 0 : effects->rodata->size = (uint)rodata_sz;
318 3 : fd_memcpy( effects->rodata->bytes, rodata, rodata_sz );
319 :
320 : /* Capture input data regions */
321 3 : ulong tmp_end = FD_SCRATCH_ALLOC_FINI(l, 1UL);
322 3 : ulong input_data_regions_size = load_from_vm_input_regions( vm->input_mem_regions,
323 3 : vm->input_mem_regions_cnt,
324 3 : &effects->input_data_regions,
325 3 : &effects->input_data_regions_count,
326 3 : (void *) tmp_end,
327 3 : fd_ulong_sat_sub( output_end, tmp_end) );
328 3 : FD_SCRATCH_ALLOC_APPEND( l, 1UL, input_data_regions_size );
329 :
330 :
331 3 : } while(0);
332 :
333 6 : ulong actual_end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
334 6 : *output = effects;
335 6 : fd_exec_test_instr_context_destroy( runner, instr_ctx, wksp, alloc );
336 6 : return actual_end - (ulong)output_buf;
337 6 : }
338 :
339 :
340 : uint
341 : setup_vm_input_regions( fd_vm_input_region_t * input,
342 : fd_exec_test_input_data_region_t const * test_input,
343 : ulong test_input_count,
344 720 : fd_valloc_t valloc ) {
345 720 : ulong offset = 0UL;
346 720 : uint input_idx = 0UL;
347 2982 : for( ulong i=0; i<test_input_count; i++ ) {
348 2262 : fd_exec_test_input_data_region_t const * region = &test_input[i];
349 2262 : pb_bytes_array_t * array = region->content;
350 2262 : if( !array ) {
351 63 : continue; /* skip empty regions https://github.com/anza-xyz/agave/blob/3072c1a72b2edbfa470ca869f1ea891dfb6517f2/programs/bpf_loader/src/serialization.rs#L136 */
352 63 : }
353 :
354 2199 : uchar * haddr = fd_valloc_malloc( valloc, 8UL, array->size );
355 2199 : fd_memcpy( haddr, array->bytes, array->size );
356 2199 : input[input_idx].vaddr_offset = offset;
357 2199 : input[input_idx].haddr = (ulong)haddr;
358 2199 : input[input_idx].region_sz = array->size;
359 2199 : input[input_idx].is_writable = region->is_writable;
360 :
361 2199 : input_idx++;
362 2199 : offset += array->size;
363 2199 : }
364 720 : return input_idx; /* return the number of populated regions */
365 720 : }
366 :
367 :
368 : ulong
369 : load_from_vm_input_regions( fd_vm_input_region_t const * input,
370 : uint input_count,
371 : fd_exec_test_input_data_region_t ** output,
372 : pb_size_t * output_count,
373 : void * output_buf,
374 11214 : ulong output_bufsz ) {
375 : /* pre-flight checks on output buffer size*/
376 11214 : ulong input_regions_total_sz = 0;
377 13410 : for( ulong i=0; i<input_count; i++ ) {
378 2196 : input_regions_total_sz += input[i].region_sz;
379 2196 : }
380 :
381 11214 : if( FD_UNLIKELY( input_regions_total_sz == 0
382 11214 : || output_bufsz < input_regions_total_sz ) ) {
383 10497 : *output = NULL;
384 10497 : *output_count = 0;
385 10497 : return 0;
386 10497 : }
387 :
388 717 : FD_SCRATCH_ALLOC_INIT( l, output_buf );
389 717 : *output = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_input_data_region_t),
390 717 : input_count * sizeof (fd_exec_test_input_data_region_t) );
391 717 : FD_TEST( *output );
392 717 : *output_count = input_count;
393 :
394 2913 : for( ulong i=0; i<input_count; i++ ) {
395 2196 : fd_vm_input_region_t const * vm_region = &input[i];
396 2196 : fd_exec_test_input_data_region_t * out_region = &(*output)[i];
397 2196 : out_region->is_writable = vm_region->is_writable;
398 2196 : out_region->offset = vm_region->vaddr_offset;
399 :
400 2196 : out_region->content = FD_SCRATCH_ALLOC_APPEND( l, alignof(pb_bytes_array_t),
401 2196 : PB_BYTES_ARRAY_T_ALLOCSIZE(vm_region->region_sz) );
402 2196 : FD_TEST( out_region->content );
403 2196 : out_region->content->size = vm_region->region_sz;
404 2196 : fd_memcpy( out_region->content->bytes, (void *)vm_region->haddr, vm_region->region_sz );
405 2196 : }
406 :
407 717 : ulong end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
408 717 : return end - (ulong)output_buf; /* return the number of bytes written */
409 717 : }
|