From 3bd363440feeba1e8f9ac4225ae1846cc6209651 Mon Sep 17 00:00:00 2001 From: Ravyu Sivakumaran Date: Wed, 8 May 2024 20:33:15 +0000 Subject: [PATCH] Fuzz target for ELF loader For use with solfuzz remove hardcoded syscall hashes initial working calldests capture switch to accepting ELFLoaderCtx protobuf messages switch to full rodata comparison address PR comments clean up comments cleanup protobuf add extra checks and comments to elf loader workaround for elf ctx binary misalignment add fixtures runner restructure to return incomplete effects on loader failure instead of 0 add logs to fixture runner comments fix elf loader test conditions add elf loader fixture tests to CI move alignment check to program->rodata port over rodata section overlap check log with FAIL for script to pickup failure in test_elf_loader.c remove memcpy in fuzz harness define rodata alignment in constant add elf_sz field deploy checks flag --- contrib/test/run_test_vectors.sh | 1 + src/ballet/sbpf/fd_sbpf_loader.c | 34 ++++- src/ballet/sbpf/fd_sbpf_loader.h | 11 +- src/ballet/sbpf/test_sbpf_loader.c | 2 +- .../program/fd_bpf_loader_v2_program.c | 2 +- .../program/fd_bpf_loader_v3_program.c | 2 +- .../runtime/program/fd_bpf_program_util.c | 2 +- src/flamenco/runtime/tests/Local.mk | 1 + .../runtime/tests/fd_exec_instr_test.c | 116 +++++++++++++++++ .../runtime/tests/fd_exec_instr_test.h | 15 +++ .../runtime/tests/fd_exec_sol_compat.c | 49 +++++++ src/flamenco/runtime/tests/fd_exec_test.pb.c | 12 ++ src/flamenco/runtime/tests/fd_exec_test.pb.h | 112 ++++++++++++++++ src/flamenco/runtime/tests/fd_exec_test.proto | 35 +++++ src/flamenco/runtime/tests/test_elf_loader.c | 123 ++++++++++++++++++ src/flamenco/vm/fd_vm_context.c | 1 + 16 files changed, 506 insertions(+), 12 deletions(-) create mode 100644 src/flamenco/runtime/tests/test_elf_loader.c diff --git a/contrib/test/run_test_vectors.sh b/contrib/test/run_test_vectors.sh index 56010bafba..90f86b67f4 100755 --- a/contrib/test/run_test_vectors.sh +++ b/contrib/test/run_test_vectors.sh @@ -27,6 +27,7 @@ else fi find dump/test-vectors/instr/fixtures -type f -name '*.fix' -exec ./$OBJDIR/unit-test/test_exec_instr --log-path $LOG_PATH/test_exec_instr --log-level-stderr 4 {} + +find dump/test-vectors/elf_loader/fixtures -type f -name '*.fix' -exec ./$OBJDIR/unit-test/test_elf_loader --log-path $LOG_PATH/test_elf_loader --log-level-stderr 4 {} + total_tests=`find dump/test-vectors/instr/fixtures -type f -name '*.fix' | wc -l` failed=`grep -wR FAIL $LOG_PATH | wc -l` diff --git a/src/ballet/sbpf/fd_sbpf_loader.c b/src/ballet/sbpf/fd_sbpf_loader.c index cced325f37..947a367bd1 100644 --- a/src/ballet/sbpf/fd_sbpf_loader.c +++ b/src/ballet/sbpf/fd_sbpf_loader.c @@ -93,7 +93,13 @@ fd_sbpf_check_ehdr( fd_elf64_ehdr const * ehdr, /* Validate ELF magic */ REQUIRE( ( fd_uint_load_4( ehdr->e_ident )==0x464c457fU ) - /* Validate file type/target identification */ + /* Validate file type/target identification + Solana/Agave performs header checks across two places: + - Elf64::parse https://github.com/solana-labs/rbpf/blob/v0.8.0/src/elf_parser/mod.rs#L108 + - Executable::validate https://github.com/solana-labs/rbpf/blob/v0.8.0/src/elf.rs#L518 + These two sections are executed in close proximity, with no modifications to the header in between. + We can therefore consolidate the checks in one place. + */ & ( ehdr->e_ident[ FD_ELF_EI_CLASS ]==FD_ELF_CLASS_64 ) & ( ehdr->e_ident[ FD_ELF_EI_DATA ]==FD_ELF_DATA_LE ) & ( ehdr->e_ident[ FD_ELF_EI_VERSION ]==1 ) @@ -255,6 +261,10 @@ fd_sbpf_load_shdrs( fd_sbpf_elf_info_t * info, ulong const pht_cnt = elf->ehdr.e_phnum; ulong const pht_offend = pht_offset + (pht_cnt*sizeof(fd_elf64_phdr)); + /* Overlap checks */ + REQUIRE( (sht_offset>=eh_offend ) | (sht_offend<=eh_offset ) ); /* overlaps ELF file header */ + REQUIRE( (sht_offset>=pht_offend) | (sht_offend<=pht_offset) ); /* overlaps program header table */ + /* Require SHT_STRTAB for section name table */ REQUIRE( elf->ehdr.e_shstrndx < sht_cnt ); /* out of bounds */ @@ -295,14 +305,17 @@ fd_sbpf_load_shdrs( fd_sbpf_elf_info_t * info, /* check that physical range has no overflow and is within bounds */ REQUIRE( sh_offend >= sh_offset ); - REQUIRE( sh_offend <= elf_sz ); + REQUIRE( sh_offend <= elf_sz ); // https://github.com/solana-labs/rbpf/blob/v0.8.0/src/elf_parser/mod.rs#L180 if( sh_type!=FD_ELF_SHT_NOBITS ) { /* Overlap checks */ REQUIRE( (sh_offset>=eh_offend ) | (sh_offend<=eh_offset ) ); /* overlaps ELF file header */ REQUIRE( (sh_offset>=pht_offend) | (sh_offend<=pht_offset) ); /* overlaps program header table */ REQUIRE( (sh_offset>=sht_offend) | (sh_offend<=sht_offset) ); /* overlaps section header table */ - /* Ordering and overlap check */ + + /* Ordering and overlap check + https://github.com/solana-labs/rbpf/blob/v0.8.0/src/elf_parser/mod.rs#L177 + */ REQUIRE( sh_offset >= min_sh_offset ); min_sh_offset = sh_offend; } @@ -387,14 +400,15 @@ fd_sbpf_load_shdrs( fd_sbpf_elf_info_t * info, /* Expand range to fit section */ segment_end = fd_ulong_max( segment_end, sh_virtual_end ); - /* Coherence check sum of section sizes (used to detect overlap) */ + /* Coherence check sum of section sizes */ REQUIRE( tot_section_sz + sh_actual_size >= tot_section_sz ); /* overflow check */ tot_section_sz += sh_actual_size; } } - /* More coherence checks ... these should never fail */ - REQUIRE( segment_end <=elf_sz ); + /* More coherence checks to conform with agave */ + REQUIRE( segment_end <=elf_sz ); // https://github.com/solana-labs/rbpf/blob/v0.8.0/src/elf.rs#L782 + /* Check that the rodata segment is within bounds https://github.com/solana-labs/rbpf/blob/v0.8.0/src/elf.rs#L725 */ @@ -423,6 +437,8 @@ fd_sbpf_load_shdrs( fd_sbpf_elf_info_t * info, ulong entry_off = fd_ulong_sat_sub( elf->ehdr.e_entry, shdr_text->sh_addr ); ulong entry_pc = entry_off / 8UL; + + /* Follows https://github.com/solana-labs/rbpf/blob/v0.8.0/src/elf.rs#L443 */ REQUIRE( fd_ulong_is_aligned( entry_off, 8UL ) ); REQUIRE( entry_pc < ( info->rodata_sz / 8UL ) ); info->entry_pc = (uint)entry_pc; @@ -545,6 +561,12 @@ fd_sbpf_program_new( void * prog_mem, return NULL; } + /* https://github.com/solana-labs/rbpf/blob/v0.8.0/src/elf_parser/mod.rs#L99 */ + if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong) rodata, FD_SBPF_PROG_RODATA_ALIGN ) ) ){ + FD_LOG_WARNING(( "rodata is not 8-byte aligned" )); + return NULL; + } + /* Initialize program struct */ FD_SCRATCH_ALLOC_INIT( laddr, prog_mem ); diff --git a/src/ballet/sbpf/fd_sbpf_loader.h b/src/ballet/sbpf/fd_sbpf_loader.h index 2ea72d016e..7772786aa9 100644 --- a/src/ballet/sbpf/fd_sbpf_loader.h +++ b/src/ballet/sbpf/fd_sbpf_loader.h @@ -17,6 +17,7 @@ /* FIXME make error types more specific */ #define FD_SBPF_ERR_INVALID_ELF (1) +#define FD_SBPF_PROG_RODATA_ALIGN 8UL /* Program struct *****************************************************/ @@ -157,7 +158,9 @@ fd_sbpf_program_footprint( fd_sbpf_elf_info_t const * info ); elf_info may be deallocated on return. rodata is the read-only segment buffer that the program is configured - against and must be valid for the lifetime of the program object. */ + against and must be valid for the lifetime of the program object. It + should also meet the alignment requirements of the program object. + */ fd_sbpf_program_t * fd_sbpf_program_new( void * prog_mem, @@ -190,7 +193,11 @@ fd_sbpf_program_new( void * prog_mem, reject_broken_elfs: elf_deploy_checks For documentation on these config params, see: - https://github.com/solana-labs/rbpf/blob/v0.3.0/src/vm.rs#L198 */ + https://github.com/solana-labs/rbpf/blob/v0.3.0/src/vm.rs#L198 + + Solana/Agave equivalent: + https://github.com/solana-labs/rbpf/blob/v0.8.0/src/elf.rs#L361 + */ int fd_sbpf_program_load( fd_sbpf_program_t * prog, diff --git a/src/ballet/sbpf/test_sbpf_loader.c b/src/ballet/sbpf/test_sbpf_loader.c index 4237f3edc4..26f34c06b5 100644 --- a/src/ballet/sbpf/test_sbpf_loader.c +++ b/src/ballet/sbpf/test_sbpf_loader.c @@ -41,7 +41,7 @@ void test_duplicate_entrypoint_entry( void ) { fd_sbpf_elf_peek( &info, duplicate_entrypoint_entry_elf, duplicate_entrypoint_entry_elf_sz, true ); - void* rodata = fd_valloc_malloc( valloc, 8UL, info.rodata_footprint ); + void* rodata = fd_valloc_malloc( valloc, FD_SBPF_PROG_RODATA_ALIGN, info.rodata_footprint ); FD_TEST( rodata ); diff --git a/src/flamenco/runtime/program/fd_bpf_loader_v2_program.c b/src/flamenco/runtime/program/fd_bpf_loader_v2_program.c index dcebd29a8e..aad3681506 100644 --- a/src/flamenco/runtime/program/fd_bpf_loader_v2_program.c +++ b/src/flamenco/runtime/program/fd_bpf_loader_v2_program.c @@ -65,7 +65,7 @@ fd_bpf_loader_v2_user_execute( fd_exec_instr_ctx_t ctx ) { /* Allocate rodata segment */ - void * rodata = fd_valloc_malloc( ctx.valloc, 32UL, elf_info.rodata_footprint ); + void * rodata = fd_valloc_malloc( ctx.valloc, FD_SBPF_PROG_RODATA_ALIGN, elf_info.rodata_footprint ); FD_TEST( rodata ); /* Allocate program buffer */ diff --git a/src/flamenco/runtime/program/fd_bpf_loader_v3_program.c b/src/flamenco/runtime/program/fd_bpf_loader_v3_program.c index 920062d23d..ddb5d9da2d 100644 --- a/src/flamenco/runtime/program/fd_bpf_loader_v3_program.c +++ b/src/flamenco/runtime/program/fd_bpf_loader_v3_program.c @@ -151,7 +151,7 @@ deploy_program( fd_exec_instr_ctx_t * instr_ctx, } /* Allocate rodata segment */ - void * rodata = fd_scratch_alloc( 32UL, elf_info->rodata_footprint ); + void * rodata = fd_scratch_alloc( FD_SBPF_PROG_RODATA_ALIGN, elf_info->rodata_footprint ); if( FD_UNLIKELY( !rodata ) ) { return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; } diff --git a/src/flamenco/runtime/program/fd_bpf_program_util.c b/src/flamenco/runtime/program/fd_bpf_program_util.c index c3ec2ed405..e596ce7e3b 100644 --- a/src/flamenco/runtime/program/fd_bpf_program_util.c +++ b/src/flamenco/runtime/program/fd_bpf_program_util.c @@ -35,7 +35,7 @@ fd_sbpf_validated_program_rodata( fd_sbpf_validated_program_t * prog ) { l = FD_LAYOUT_APPEND( l, alignof(fd_sbpf_validated_program_t), sizeof(fd_sbpf_validated_program_t) ); assert( l==offsetof(fd_sbpf_validated_program_t, calldests) ); l = FD_LAYOUT_APPEND( l, fd_sbpf_calldests_align(), fd_sbpf_calldests_footprint(prog->rodata_sz/8UL) ); - l = FD_LAYOUT_FINI( l, 8UL ); + l = FD_LAYOUT_FINI( l, FD_SBPF_PROG_RODATA_ALIGN ); return (uchar *)fd_type_pun(prog) + l; } diff --git a/src/flamenco/runtime/tests/Local.mk b/src/flamenco/runtime/tests/Local.mk index ac22ea6f2b..20ac202f70 100644 --- a/src/flamenco/runtime/tests/Local.mk +++ b/src/flamenco/runtime/tests/Local.mk @@ -7,6 +7,7 @@ $(call add-hdrs,fd_exec_instr_test.h) $(call add-objs,fd_exec_instr_test,fd_flamenco) $(call make-unit-test,test_exec_instr,test_exec_instr,fd_flamenco fd_funk fd_ballet fd_util,$(SECP256K1_LIBS)) +$(call make-unit-test,test_elf_loader,test_elf_loader,fd_flamenco fd_funk fd_ballet fd_util,$(SECP256K1_LIBS)) $(call make-shared,libfd_exec_sol_compat.so,fd_exec_sol_compat,fd_flamenco fd_funk fd_ballet fd_util,$(SECP256K1_LIBS)) endif endif diff --git a/src/flamenco/runtime/tests/fd_exec_instr_test.c b/src/flamenco/runtime/tests/fd_exec_instr_test.c index b920806adc..adcebfe1ec 100644 --- a/src/flamenco/runtime/tests/fd_exec_instr_test.c +++ b/src/flamenco/runtime/tests/fd_exec_instr_test.c @@ -13,6 +13,9 @@ #include "../sysvar/fd_sysvar_recent_hashes.h" #include "../../../funk/fd_funk.h" #include "../../../util/bits/fd_float.h" +#include "../../../ballet/sbpf/fd_sbpf_loader.h" +#include "../../../ballet/elf/fd_elf.h" +#include "../../vm/fd_vm_syscalls.h" #include #include "../sysvar/fd_sysvar_cache.h" @@ -884,3 +887,116 @@ fd_exec_instr_test_run( fd_exec_instr_test_runner_t * runner, *output = effects; return actual_end - (ulong)output_buf; } + + +ulong +fd_sbpf_program_load_test_run( fd_exec_test_elf_loader_ctx_t const * input, + fd_exec_test_elf_loader_effects_t ** output, + void * output_buf, + ulong output_bufsz ){ + fd_sbpf_elf_info_t info; + fd_valloc_t valloc = fd_scratch_virtual(); + + if ( FD_UNLIKELY( !input->has_elf || !input->elf.data ) ){ + return 0UL; + } + + ulong elf_sz = input->elf_sz; + void const * _bin; + + /* elf_sz will be passed as arguments to elf loader functions. + pb decoder allocates memory for elf.data based on its actual size, + not elf_sz !. + If elf_sz is larger than the size of actual elf data, this may result + in out-of-bounds accesses which will upset ASAN (however intentional). + So in this case we just copy the data into a memory region of elf_sz bytes + + ! The decoupling of elf_sz and the actual binary size is intentional to test + underflow/overflow behavior */ + if ( elf_sz > input->elf.data->size ){ + void * tmp = fd_valloc_malloc( valloc, 1UL, elf_sz ); + if ( FD_UNLIKELY( !tmp ) ){ + return 0UL; + } + fd_memcpy( tmp, input->elf.data->bytes, input->elf.data->size ); + _bin = tmp; + } else { + _bin = input->elf.data->bytes; + } + + // Allocate space for captured effects + ulong output_end = (ulong)output_buf + output_bufsz; + FD_SCRATCH_ALLOC_INIT( l, output_buf ); + + fd_exec_test_elf_loader_effects_t * elf_effects = + FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_elf_loader_effects_t), + sizeof (fd_exec_test_elf_loader_effects_t) ); + if( FD_UNLIKELY( _l > output_end ) ) { + /* return 0 on fuzz-specific failures */ + return 0UL; + } + fd_memset( elf_effects, 0, sizeof(fd_exec_test_elf_loader_effects_t) ); + + /* wrap the loader code in do-while(0) block so that we can exit + immediately if execution fails at any point */ + + do{ + + if( FD_UNLIKELY( !fd_sbpf_elf_peek( &info, _bin, elf_sz, input->deploy_checks ) ) ) { + /* return incomplete effects on execution failures */ + break; + } + + void* rodata = fd_valloc_malloc( valloc, FD_SBPF_PROG_RODATA_ALIGN, info.rodata_footprint ); + FD_TEST( rodata ); + + fd_sbpf_program_t * prog = fd_sbpf_program_new( fd_valloc_malloc( valloc, fd_sbpf_program_align(), fd_sbpf_program_footprint( &info ) ), &info, rodata ); + FD_TEST( prog ); + + fd_sbpf_syscalls_t * syscalls = fd_sbpf_syscalls_new( fd_valloc_malloc( valloc, fd_sbpf_syscalls_align(), fd_sbpf_syscalls_footprint() )); + FD_TEST( syscalls ); + + fd_vm_syscall_register_all( syscalls ); + + int res = fd_sbpf_program_load( prog, _bin, elf_sz, syscalls, input->deploy_checks ); + if( FD_UNLIKELY( res ) ) { + break; + } + + fd_memset( elf_effects, 0, sizeof(fd_exec_test_elf_loader_effects_t) ); + elf_effects->rodata_sz = prog->rodata_sz; + + // Load rodata section + elf_effects->rodata = FD_SCRATCH_ALLOC_APPEND(l, 8UL, PB_BYTES_ARRAY_T_ALLOCSIZE( prog->rodata_sz )); + if( FD_UNLIKELY( _l > output_end ) ) { + return 0UL; + } + elf_effects->rodata->size = (pb_size_t) prog->rodata_sz; + fd_memcpy( &(elf_effects->rodata->bytes), prog->rodata, prog->rodata_sz ); + + elf_effects->text_cnt = prog->text_cnt; + elf_effects->text_off = prog->text_off; + + elf_effects->entry_pc = prog->entry_pc; + + + pb_size_t calldests_sz = (pb_size_t) fd_sbpf_calldests_cnt( prog->calldests); + elf_effects->calldests_count = calldests_sz; + elf_effects->calldests = FD_SCRATCH_ALLOC_APPEND(l, 8UL, calldests_sz * sizeof(uint64_t)); + if( FD_UNLIKELY( _l > output_end ) ) { + return 0UL; + } + + ulong i = 0; + for(ulong target_pc = fd_sbpf_calldests_const_iter_init(prog->calldests); !fd_sbpf_calldests_const_iter_done(target_pc); + target_pc = fd_sbpf_calldests_const_iter_next(prog->calldests, target_pc)) { + elf_effects->calldests[i] = target_pc; + ++i; + } + } while(0); + + ulong actual_end = FD_SCRATCH_ALLOC_FINI( l, 1UL ); + + *output = elf_effects; + return actual_end - (ulong) output_buf; +} diff --git a/src/flamenco/runtime/tests/fd_exec_instr_test.h b/src/flamenco/runtime/tests/fd_exec_instr_test.h index 83c66a504d..8e175fc365 100644 --- a/src/flamenco/runtime/tests/fd_exec_instr_test.h +++ b/src/flamenco/runtime/tests/fd_exec_instr_test.h @@ -71,6 +71,21 @@ fd_exec_instr_test_run( fd_exec_instr_test_runner_t * runner, void * output_buf, ulong output_bufsz ); +/* Loads an ELF binary (in input->elf.data()). + output_buf points to a memory region of output_bufsz bytes where the + result is allocated into. During execution, the contents of + fd_sbpf_program_t are wrapped in *output (backed by output_buf). + + Returns number of bytes allocated at output_buf OR 0UL on any + harness-specific failures. Execution failures still return number of allocated bytes, + but output is incomplete/undefined. + */ +ulong +fd_sbpf_program_load_test_run( fd_exec_test_elf_loader_ctx_t const * input, + fd_exec_test_elf_loader_effects_t ** output, + void * output_buf, + ulong output_bufsz ); + FD_PROTOTYPES_END #endif /* HEADER_fd_src_flamenco_runtime_tests_fd_exec_instr_test_h */ diff --git a/src/flamenco/runtime/tests/fd_exec_sol_compat.c b/src/flamenco/runtime/tests/fd_exec_sol_compat.c index d01c4c5ee9..86d8ea89f7 100644 --- a/src/flamenco/runtime/tests/fd_exec_sol_compat.c +++ b/src/flamenco/runtime/tests/fd_exec_sol_compat.c @@ -134,6 +134,55 @@ sol_compat_instr_execute_v1( uchar * out, return ok; } + +int +sol_compat_elf_loader_v1( uchar * out, + ulong * out_sz, + uchar const * in, + ulong in_sz ) { + ulong fmem[ 64 ]; + fd_scratch_attach( smem, fmem, smax, 64UL ); + fd_scratch_push(); + + pb_istream_t istream = pb_istream_from_buffer( in, in_sz ); + fd_exec_test_elf_loader_ctx_t input[1] = {0}; + int decode_ok = pb_decode_ex( &istream, &fd_exec_test_elf_loader_ctx_t_msg, input, PB_DECODE_NOINIT ); + if( !decode_ok ) { + pb_release( &fd_exec_test_elf_loader_ctx_t_msg, input ); + return 0; + } + + fd_exec_test_elf_loader_effects_t * output = NULL; + do { + ulong out_bufsz = 100000000; + void * out0 = fd_scratch_prepare( 1UL ); + assert( out_bufsz < fd_scratch_free() ); + fd_scratch_publish( (void *)( (ulong)out0 + out_bufsz ) ); + ulong out_used = fd_sbpf_program_load_test_run( input, &output, out0, out_bufsz ); + if( FD_UNLIKELY( !out_used ) ) { + output = NULL; + break; + } + } while(0); + + int ok = 0; + + if( output ) { + pb_ostream_t ostream = pb_ostream_from_buffer( out, *out_sz ); + int encode_ok = pb_encode( &ostream, &fd_exec_test_elf_loader_effects_t_msg, output ); + if( encode_ok ) { + *out_sz = ostream.bytes_written; + ok = 1; + } + } + + pb_release( &fd_exec_test_elf_loader_ctx_t_msg, input ); + fd_scratch_pop(); + fd_scratch_detach( NULL ); + return ok; + +} + sol_compat_features_t const * sol_compat_get_features_v1( void ) { return &features; diff --git a/src/flamenco/runtime/tests/fd_exec_test.pb.c b/src/flamenco/runtime/tests/fd_exec_test.pb.c index ba228781d3..7b8823d330 100644 --- a/src/flamenco/runtime/tests/fd_exec_test.pb.c +++ b/src/flamenco/runtime/tests/fd_exec_test.pb.c @@ -33,4 +33,16 @@ PB_BIND(FD_EXEC_TEST_INSTR_EFFECTS, fd_exec_test_instr_effects_t, AUTO) PB_BIND(FD_EXEC_TEST_INSTR_FIXTURE, fd_exec_test_instr_fixture_t, AUTO) +PB_BIND(FD_EXEC_TEST_ELF_BINARY, fd_exec_test_elf_binary_t, AUTO) + + +PB_BIND(FD_EXEC_TEST_ELF_LOADER_CTX, fd_exec_test_elf_loader_ctx_t, AUTO) + + +PB_BIND(FD_EXEC_TEST_ELF_LOADER_EFFECTS, fd_exec_test_elf_loader_effects_t, AUTO) + + +PB_BIND(FD_EXEC_TEST_ELF_LOADER_FIXTURE, fd_exec_test_elf_loader_fixture_t, AUTO) + + diff --git a/src/flamenco/runtime/tests/fd_exec_test.pb.h b/src/flamenco/runtime/tests/fd_exec_test.pb.h index 0c979b6fa7..df13c951ab 100644 --- a/src/flamenco/runtime/tests/fd_exec_test.pb.h +++ b/src/flamenco/runtime/tests/fd_exec_test.pb.h @@ -121,6 +121,40 @@ typedef struct fd_exec_test_instr_fixture { fd_exec_test_instr_effects_t output; } fd_exec_test_instr_fixture_t; +typedef struct fd_exec_test_elf_binary { + pb_bytes_array_t *data; +} fd_exec_test_elf_binary_t; + +typedef struct fd_exec_test_elf_loader_ctx { + bool has_elf; + fd_exec_test_elf_binary_t elf; + bool has_features; + fd_exec_test_feature_set_t features; + uint64_t elf_sz; + bool deploy_checks; +} fd_exec_test_elf_loader_ctx_t; + +/* Captures the results of a elf binary load. + Structurally similar to fd_sbpf_program_t */ +typedef struct fd_exec_test_elf_loader_effects { + /* loaded program rodata */ + pb_bytes_array_t *rodata; + uint64_t rodata_sz; + uint64_t text_cnt; + uint64_t text_off; + /* program entry point */ + uint64_t entry_pc; + pb_size_t calldests_count; + uint64_t *calldests; +} fd_exec_test_elf_loader_effects_t; + +typedef struct fd_exec_test_elf_loader_fixture { + bool has_input; + fd_exec_test_elf_loader_ctx_t input; + bool has_output; + fd_exec_test_elf_loader_effects_t output; +} fd_exec_test_elf_loader_fixture_t; + #ifdef __cplusplus extern "C" { @@ -136,6 +170,10 @@ extern "C" { #define FD_EXEC_TEST_INSTR_CONTEXT_INIT_DEFAULT {false, {0}, false, {0}, 0, NULL, 0, NULL, NULL, false, 0, false, FD_EXEC_TEST_TXN_CONTEXT_INIT_DEFAULT, false, FD_EXEC_TEST_SLOT_CONTEXT_INIT_DEFAULT, false, FD_EXEC_TEST_EPOCH_CONTEXT_INIT_DEFAULT} #define FD_EXEC_TEST_INSTR_EFFECTS_INIT_DEFAULT {0, false, 0, 0, NULL, 0, NULL} #define FD_EXEC_TEST_INSTR_FIXTURE_INIT_DEFAULT {false, FD_EXEC_TEST_INSTR_CONTEXT_INIT_DEFAULT, false, FD_EXEC_TEST_INSTR_EFFECTS_INIT_DEFAULT} +#define FD_EXEC_TEST_ELF_BINARY_INIT_DEFAULT {NULL} +#define FD_EXEC_TEST_ELF_LOADER_CTX_INIT_DEFAULT {false, FD_EXEC_TEST_ELF_BINARY_INIT_DEFAULT, false, FD_EXEC_TEST_FEATURE_SET_INIT_DEFAULT, 0, 0} +#define FD_EXEC_TEST_ELF_LOADER_EFFECTS_INIT_DEFAULT {NULL, 0, 0, 0, 0, 0, NULL} +#define FD_EXEC_TEST_ELF_LOADER_FIXTURE_INIT_DEFAULT {false, FD_EXEC_TEST_ELF_LOADER_CTX_INIT_DEFAULT, false, FD_EXEC_TEST_ELF_LOADER_EFFECTS_INIT_DEFAULT} #define FD_EXEC_TEST_FEATURE_SET_INIT_ZERO {0, NULL} #define FD_EXEC_TEST_ACCT_STATE_INIT_ZERO {false, {0}, false, 0, NULL, false, 0, false, 0, false, {0}} #define FD_EXEC_TEST_EPOCH_CONTEXT_INIT_ZERO {false, FD_EXEC_TEST_FEATURE_SET_INIT_ZERO} @@ -145,6 +183,10 @@ extern "C" { #define FD_EXEC_TEST_INSTR_CONTEXT_INIT_ZERO {false, {0}, false, {0}, 0, NULL, 0, NULL, NULL, false, 0, false, FD_EXEC_TEST_TXN_CONTEXT_INIT_ZERO, false, FD_EXEC_TEST_SLOT_CONTEXT_INIT_ZERO, false, FD_EXEC_TEST_EPOCH_CONTEXT_INIT_ZERO} #define FD_EXEC_TEST_INSTR_EFFECTS_INIT_ZERO {0, false, 0, 0, NULL, 0, NULL} #define FD_EXEC_TEST_INSTR_FIXTURE_INIT_ZERO {false, FD_EXEC_TEST_INSTR_CONTEXT_INIT_ZERO, false, FD_EXEC_TEST_INSTR_EFFECTS_INIT_ZERO} +#define FD_EXEC_TEST_ELF_BINARY_INIT_ZERO {NULL} +#define FD_EXEC_TEST_ELF_LOADER_CTX_INIT_ZERO {false, FD_EXEC_TEST_ELF_BINARY_INIT_ZERO, false, FD_EXEC_TEST_FEATURE_SET_INIT_ZERO, 0, 0} +#define FD_EXEC_TEST_ELF_LOADER_EFFECTS_INIT_ZERO {NULL, 0, 0, 0, 0, 0, NULL} +#define FD_EXEC_TEST_ELF_LOADER_FIXTURE_INIT_ZERO {false, FD_EXEC_TEST_ELF_LOADER_CTX_INIT_ZERO, false, FD_EXEC_TEST_ELF_LOADER_EFFECTS_INIT_ZERO} /* Field tags (for use in manual encoding/decoding) */ #define FD_EXEC_TEST_FEATURE_SET_FEATURES_TAG 1 @@ -174,6 +216,19 @@ extern "C" { #define FD_EXEC_TEST_INSTR_EFFECTS_RETURN_DATA_TAG 5 #define FD_EXEC_TEST_INSTR_FIXTURE_INPUT_TAG 1 #define FD_EXEC_TEST_INSTR_FIXTURE_OUTPUT_TAG 2 +#define FD_EXEC_TEST_ELF_BINARY_DATA_TAG 1 +#define FD_EXEC_TEST_ELF_LOADER_CTX_ELF_TAG 1 +#define FD_EXEC_TEST_ELF_LOADER_CTX_FEATURES_TAG 2 +#define FD_EXEC_TEST_ELF_LOADER_CTX_ELF_SZ_TAG 3 +#define FD_EXEC_TEST_ELF_LOADER_CTX_DEPLOY_CHECKS_TAG 4 +#define FD_EXEC_TEST_ELF_LOADER_EFFECTS_RODATA_TAG 1 +#define FD_EXEC_TEST_ELF_LOADER_EFFECTS_RODATA_SZ_TAG 2 +#define FD_EXEC_TEST_ELF_LOADER_EFFECTS_TEXT_CNT_TAG 4 +#define FD_EXEC_TEST_ELF_LOADER_EFFECTS_TEXT_OFF_TAG 5 +#define FD_EXEC_TEST_ELF_LOADER_EFFECTS_ENTRY_PC_TAG 6 +#define FD_EXEC_TEST_ELF_LOADER_EFFECTS_CALLDESTS_TAG 7 +#define FD_EXEC_TEST_ELF_LOADER_FIXTURE_INPUT_TAG 1 +#define FD_EXEC_TEST_ELF_LOADER_FIXTURE_OUTPUT_TAG 2 /* Struct field encoding specification for nanopb */ #define FD_EXEC_TEST_FEATURE_SET_FIELDLIST(X, a) \ @@ -250,6 +305,39 @@ X(a, STATIC, OPTIONAL, MESSAGE, output, 2) #define fd_exec_test_instr_fixture_t_input_MSGTYPE fd_exec_test_instr_context_t #define fd_exec_test_instr_fixture_t_output_MSGTYPE fd_exec_test_instr_effects_t +#define FD_EXEC_TEST_ELF_BINARY_FIELDLIST(X, a) \ +X(a, POINTER, SINGULAR, BYTES, data, 1) +#define FD_EXEC_TEST_ELF_BINARY_CALLBACK NULL +#define FD_EXEC_TEST_ELF_BINARY_DEFAULT NULL + +#define FD_EXEC_TEST_ELF_LOADER_CTX_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, MESSAGE, elf, 1) \ +X(a, STATIC, OPTIONAL, MESSAGE, features, 2) \ +X(a, STATIC, SINGULAR, UINT64, elf_sz, 3) \ +X(a, STATIC, SINGULAR, BOOL, deploy_checks, 4) +#define FD_EXEC_TEST_ELF_LOADER_CTX_CALLBACK NULL +#define FD_EXEC_TEST_ELF_LOADER_CTX_DEFAULT NULL +#define fd_exec_test_elf_loader_ctx_t_elf_MSGTYPE fd_exec_test_elf_binary_t +#define fd_exec_test_elf_loader_ctx_t_features_MSGTYPE fd_exec_test_feature_set_t + +#define FD_EXEC_TEST_ELF_LOADER_EFFECTS_FIELDLIST(X, a) \ +X(a, POINTER, SINGULAR, BYTES, rodata, 1) \ +X(a, STATIC, SINGULAR, UINT64, rodata_sz, 2) \ +X(a, STATIC, SINGULAR, UINT64, text_cnt, 4) \ +X(a, STATIC, SINGULAR, UINT64, text_off, 5) \ +X(a, STATIC, SINGULAR, UINT64, entry_pc, 6) \ +X(a, POINTER, REPEATED, UINT64, calldests, 7) +#define FD_EXEC_TEST_ELF_LOADER_EFFECTS_CALLBACK NULL +#define FD_EXEC_TEST_ELF_LOADER_EFFECTS_DEFAULT NULL + +#define FD_EXEC_TEST_ELF_LOADER_FIXTURE_FIELDLIST(X, a) \ +X(a, STATIC, OPTIONAL, MESSAGE, input, 1) \ +X(a, STATIC, OPTIONAL, MESSAGE, output, 2) +#define FD_EXEC_TEST_ELF_LOADER_FIXTURE_CALLBACK NULL +#define FD_EXEC_TEST_ELF_LOADER_FIXTURE_DEFAULT NULL +#define fd_exec_test_elf_loader_fixture_t_input_MSGTYPE fd_exec_test_elf_loader_ctx_t +#define fd_exec_test_elf_loader_fixture_t_output_MSGTYPE fd_exec_test_elf_loader_effects_t + extern const pb_msgdesc_t fd_exec_test_feature_set_t_msg; extern const pb_msgdesc_t fd_exec_test_acct_state_t_msg; extern const pb_msgdesc_t fd_exec_test_epoch_context_t_msg; @@ -259,6 +347,10 @@ extern const pb_msgdesc_t fd_exec_test_instr_acct_t_msg; extern const pb_msgdesc_t fd_exec_test_instr_context_t_msg; extern const pb_msgdesc_t fd_exec_test_instr_effects_t_msg; extern const pb_msgdesc_t fd_exec_test_instr_fixture_t_msg; +extern const pb_msgdesc_t fd_exec_test_elf_binary_t_msg; +extern const pb_msgdesc_t fd_exec_test_elf_loader_ctx_t_msg; +extern const pb_msgdesc_t fd_exec_test_elf_loader_effects_t_msg; +extern const pb_msgdesc_t fd_exec_test_elf_loader_fixture_t_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define FD_EXEC_TEST_FEATURE_SET_FIELDS &fd_exec_test_feature_set_t_msg @@ -270,6 +362,10 @@ extern const pb_msgdesc_t fd_exec_test_instr_fixture_t_msg; #define FD_EXEC_TEST_INSTR_CONTEXT_FIELDS &fd_exec_test_instr_context_t_msg #define FD_EXEC_TEST_INSTR_EFFECTS_FIELDS &fd_exec_test_instr_effects_t_msg #define FD_EXEC_TEST_INSTR_FIXTURE_FIELDS &fd_exec_test_instr_fixture_t_msg +#define FD_EXEC_TEST_ELF_BINARY_FIELDS &fd_exec_test_elf_binary_t_msg +#define FD_EXEC_TEST_ELF_LOADER_CTX_FIELDS &fd_exec_test_elf_loader_ctx_t_msg +#define FD_EXEC_TEST_ELF_LOADER_EFFECTS_FIELDS &fd_exec_test_elf_loader_effects_t_msg +#define FD_EXEC_TEST_ELF_LOADER_FIXTURE_FIELDS &fd_exec_test_elf_loader_fixture_t_msg /* Maximum encoded size of messages (where known) */ /* fd_exec_test_FeatureSet_size depends on runtime parameters */ @@ -278,6 +374,10 @@ extern const pb_msgdesc_t fd_exec_test_instr_fixture_t_msg; /* fd_exec_test_InstrContext_size depends on runtime parameters */ /* fd_exec_test_InstrEffects_size depends on runtime parameters */ /* fd_exec_test_InstrFixture_size depends on runtime parameters */ +/* fd_exec_test_ELFBinary_size depends on runtime parameters */ +/* fd_exec_test_ELFLoaderCtx_size depends on runtime parameters */ +/* fd_exec_test_ELFLoaderEffects_size depends on runtime parameters */ +/* fd_exec_test_ELFLoaderFixture_size depends on runtime parameters */ #define FD_EXEC_TEST_INSTR_ACCT_SIZE 10 #define FD_EXEC_TEST_SLOT_CONTEXT_SIZE 0 #define FD_EXEC_TEST_TXN_CONTEXT_SIZE 0 @@ -293,6 +393,10 @@ extern const pb_msgdesc_t fd_exec_test_instr_fixture_t_msg; #define org_solana_sealevel_v1_InstrContext fd_exec_test_InstrContext #define org_solana_sealevel_v1_InstrEffects fd_exec_test_InstrEffects #define org_solana_sealevel_v1_InstrFixture fd_exec_test_InstrFixture +#define org_solana_sealevel_v1_ELFBinary fd_exec_test_ELFBinary +#define org_solana_sealevel_v1_ELFLoaderCtx fd_exec_test_ELFLoaderCtx +#define org_solana_sealevel_v1_ELFLoaderEffects fd_exec_test_ELFLoaderEffects +#define org_solana_sealevel_v1_ELFLoaderFixture fd_exec_test_ELFLoaderFixture #define ORG_SOLANA_SEALEVEL_V1_FEATURE_SET_INIT_DEFAULT FD_EXEC_TEST_FEATURE_SET_INIT_DEFAULT #define ORG_SOLANA_SEALEVEL_V1_ACCT_STATE_INIT_DEFAULT FD_EXEC_TEST_ACCT_STATE_INIT_DEFAULT #define ORG_SOLANA_SEALEVEL_V1_EPOCH_CONTEXT_INIT_DEFAULT FD_EXEC_TEST_EPOCH_CONTEXT_INIT_DEFAULT @@ -302,6 +406,10 @@ extern const pb_msgdesc_t fd_exec_test_instr_fixture_t_msg; #define ORG_SOLANA_SEALEVEL_V1_INSTR_CONTEXT_INIT_DEFAULT FD_EXEC_TEST_INSTR_CONTEXT_INIT_DEFAULT #define ORG_SOLANA_SEALEVEL_V1_INSTR_EFFECTS_INIT_DEFAULT FD_EXEC_TEST_INSTR_EFFECTS_INIT_DEFAULT #define ORG_SOLANA_SEALEVEL_V1_INSTR_FIXTURE_INIT_DEFAULT FD_EXEC_TEST_INSTR_FIXTURE_INIT_DEFAULT +#define ORG_SOLANA_SEALEVEL_V1_ELF_BINARY_INIT_DEFAULT FD_EXEC_TEST_ELF_BINARY_INIT_DEFAULT +#define ORG_SOLANA_SEALEVEL_V1_ELF_LOADER_CTX_INIT_DEFAULT FD_EXEC_TEST_ELF_LOADER_CTX_INIT_DEFAULT +#define ORG_SOLANA_SEALEVEL_V1_ELF_LOADER_EFFECTS_INIT_DEFAULT FD_EXEC_TEST_ELF_LOADER_EFFECTS_INIT_DEFAULT +#define ORG_SOLANA_SEALEVEL_V1_ELF_LOADER_FIXTURE_INIT_DEFAULT FD_EXEC_TEST_ELF_LOADER_FIXTURE_INIT_DEFAULT #define ORG_SOLANA_SEALEVEL_V1_FEATURE_SET_INIT_ZERO FD_EXEC_TEST_FEATURE_SET_INIT_ZERO #define ORG_SOLANA_SEALEVEL_V1_ACCT_STATE_INIT_ZERO FD_EXEC_TEST_ACCT_STATE_INIT_ZERO #define ORG_SOLANA_SEALEVEL_V1_EPOCH_CONTEXT_INIT_ZERO FD_EXEC_TEST_EPOCH_CONTEXT_INIT_ZERO @@ -311,6 +419,10 @@ extern const pb_msgdesc_t fd_exec_test_instr_fixture_t_msg; #define ORG_SOLANA_SEALEVEL_V1_INSTR_CONTEXT_INIT_ZERO FD_EXEC_TEST_INSTR_CONTEXT_INIT_ZERO #define ORG_SOLANA_SEALEVEL_V1_INSTR_EFFECTS_INIT_ZERO FD_EXEC_TEST_INSTR_EFFECTS_INIT_ZERO #define ORG_SOLANA_SEALEVEL_V1_INSTR_FIXTURE_INIT_ZERO FD_EXEC_TEST_INSTR_FIXTURE_INIT_ZERO +#define ORG_SOLANA_SEALEVEL_V1_ELF_BINARY_INIT_ZERO FD_EXEC_TEST_ELF_BINARY_INIT_ZERO +#define ORG_SOLANA_SEALEVEL_V1_ELF_LOADER_CTX_INIT_ZERO FD_EXEC_TEST_ELF_LOADER_CTX_INIT_ZERO +#define ORG_SOLANA_SEALEVEL_V1_ELF_LOADER_EFFECTS_INIT_ZERO FD_EXEC_TEST_ELF_LOADER_EFFECTS_INIT_ZERO +#define ORG_SOLANA_SEALEVEL_V1_ELF_LOADER_FIXTURE_INIT_ZERO FD_EXEC_TEST_ELF_LOADER_FIXTURE_INIT_ZERO #ifdef __cplusplus } /* extern "C" */ diff --git a/src/flamenco/runtime/tests/fd_exec_test.proto b/src/flamenco/runtime/tests/fd_exec_test.proto index e59cf35ac8..fb65b8bd94 100644 --- a/src/flamenco/runtime/tests/fd_exec_test.proto +++ b/src/flamenco/runtime/tests/fd_exec_test.proto @@ -115,3 +115,38 @@ message InstrFixture { InstrContext input = 1; InstrEffects output = 2; } + +message ELFBinary { + bytes data = 1 + [(nanopb).type = FT_POINTER]; +} + +message ELFLoaderCtx { + ELFBinary elf = 1; + FeatureSet features = 2; + uint64 elf_sz = 3; + bool deploy_checks = 4; +} + +// Captures the results of a elf binary load. +// Structurally similar to fd_sbpf_program_t +message ELFLoaderEffects { + // loaded program rodata + bytes rodata = 1 + [(nanopb).type = FT_POINTER]; + uint64 rodata_sz = 2; + + uint64 text_cnt = 4; + uint64 text_off = 5; + + // program entry point + uint64 entry_pc = 6; + + repeated uint64 calldests = 7 + [(nanopb).type = FT_POINTER]; +} + +message ELFLoaderFixture { + ELFLoaderCtx input = 1; + ELFLoaderEffects output = 2; +} \ No newline at end of file diff --git a/src/flamenco/runtime/tests/test_elf_loader.c b/src/flamenco/runtime/tests/test_elf_loader.c new file mode 100644 index 0000000000..73732c4022 --- /dev/null +++ b/src/flamenco/runtime/tests/test_elf_loader.c @@ -0,0 +1,123 @@ +/* + Run test fixtures for the elf loader. The executable takes in a list of ELFLoaderFixture + message files, executes them and compares the effects with the expected output contained + in the fixture. +*/ +#include "../../../util/fd_util.h" +#include "fd_exec_test.pb.h" +#include "fd_exec_instr_test.h" +#include +#include +#include +#include +#include "../../nanopb/pb_decode.h" +#include + +static int +diff_effects( fd_exec_test_elf_loader_effects_t const * expected, + fd_exec_test_elf_loader_effects_t const * actual ) { + int diff = 0; + // TODO: Report differences at the field level + if( expected->rodata_sz != actual->rodata_sz ) { + diff = 1; + FD_LOG_WARNING(( "ro data size: expected %lu, actual %lu", expected->rodata_sz, actual->rodata_sz )); + }; + if( expected->text_cnt != actual->text_cnt ) { + diff = 1; + FD_LOG_WARNING(( "Instruction count: expected %lu, actual %lu", expected->text_cnt, actual->text_cnt )); + } + if( expected->entry_pc != actual->entry_pc ) { + diff = 1; + FD_LOG_WARNING(( "Entry PC: expected %lu, actual %lu", expected->entry_pc, actual->entry_pc )); + } + if( expected->calldests_count != actual->calldests_count ) { + diff = 1; + FD_LOG_WARNING(( "calldests count: expected %d, actual %d", expected->calldests_count, actual->calldests_count )); + } + if( memcmp( expected->calldests, actual->calldests, expected->calldests_count*sizeof(ulong) ) != 0 ){ + diff = 1; + FD_LOG_WARNING(( "calldests differ" )); + } + + if( memcmp( expected->rodata, actual->rodata, expected->rodata_sz ) != 0 ){ + diff = 1; + FD_LOG_WARNING(( "rodata differ" )); + } + + return diff; +} + +static int +run_test( char const * path ) { + /* Read file content to memory */ + + int file = open( path, O_RDONLY ); + struct stat st; + if( FD_UNLIKELY( 0!=fstat( file, &st ) ) ) { + FD_LOG_WARNING(( "fstat(%s): %s", path, fd_io_strerror( errno ) )); + return 0; + } + ulong file_sz = (ulong)st.st_size; + uchar * buf = fd_scratch_alloc( 1, file_sz ); + FD_TEST( 0==fd_io_read( file, buf, file_sz, file_sz, &file_sz ) ); + FD_TEST( 0==close( file ) ); + + pb_istream_t istream = pb_istream_from_buffer( buf, file_sz ); + fd_exec_test_elf_loader_fixture_t fixture[1] = {0}; + int decode_ok = pb_decode_ex( &istream, &fd_exec_test_elf_loader_fixture_t_msg, fixture, PB_DECODE_NOINIT ); + + if( FD_UNLIKELY( !decode_ok ) ) { + FD_LOG_WARNING(( "%s: failed to decode (%s)", path, PB_GET_ERROR(&istream) )); + pb_release( &fd_exec_test_elf_loader_fixture_t_msg, fixture ); + return 0; + } + + /* Run test */ + fd_exec_test_elf_loader_effects_t * output = NULL; + int diff = 0; + do { + ulong out_bufsz = 50000000; /* 50MB */ + void * out0 = fd_scratch_prepare( 1UL ); + FD_TEST( out_bufsz < fd_scratch_free() ); + fd_scratch_publish( (void *)( (ulong)out0 + out_bufsz ) ); + ulong out_used = fd_sbpf_program_load_test_run( &fixture->input, &output, out0, out_bufsz ); + if( FD_UNLIKELY( !out_used ) ) { + FD_LOG_WARNING(( "Failed to load from fixture %s", path )); + output = NULL; + break; + } + /* Compare effects */ + diff = diff_effects( &fixture->output, output ); + if( diff ) { + /* Need "FAIL" for run_test_vectors script to pickup failure */ + FD_LOG_WARNING(( "FAIL: Elf loader effects differ for fixture %s", path )); + } + + } while(0); + + pb_release( &fd_exec_test_elf_loader_fixture_t_msg, fixture ); + return diff; + +} + + + +int +main( int argc, + char ** argv ) { + fd_boot( &argc, &argv ); + static uchar scratch_mem [ 1<<28 ]; /* 256MB */ + static ulong scratch_fmem[ 4UL ] __attribute((aligned(FD_SCRATCH_FMEM_ALIGN))); + fd_scratch_attach( scratch_mem, scratch_fmem, 1UL<<28, 4UL ); + + ulong fail_cnt = 0UL; + for( int j=1; j0UL; +} diff --git a/src/flamenco/vm/fd_vm_context.c b/src/flamenco/vm/fd_vm_context.c index fc1d817c0c..335fe01f0d 100644 --- a/src/flamenco/vm/fd_vm_context.c +++ b/src/flamenco/vm/fd_vm_context.c @@ -133,6 +133,7 @@ fd_vm_context_validate( fd_vm_exec_context_t const * ctx ) { break; } /* https://github.com/solana-labs/rbpf/blob/b503a1867a9cfa13f93b4d99679a17fe219831de/src/elf.rs#L829-L830 */ + /* TODO: Remove, checked in ELF loader */ case FD_CHECK_CALL: { /* CALL_IMM == FD_BPF_OP_CALL_IMM */ if( instr.src_reg == 0 ) { ulong target_pc = fd_ulong_sat_add( fd_ulong_sat_add( i, instr.imm ), 1 );