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