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 );