diff --git a/contrib/test/run_test_vectors.sh b/contrib/test/run_test_vectors.sh index 8d399b4cae..30e04be264 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/test_vectors_exec 2>&1 +find dump/test-vectors/elf_loader/fixtures -type f -name '*.fix' -exec ./$OBJDIR/unit-test/test_elf_loader {} + >> $LOG_PATH/test_vectors_exec 2>&1 failed=`grep -w FAIL $LOG_PATH/test_vectors_exec | wc -l` echo "Total failed: $failed" diff --git a/src/ballet/sbpf/fd_sbpf_loader.c b/src/ballet/sbpf/fd_sbpf_loader.c index 06977672db..6a01fc1bf8 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 ) @@ -254,6 +260,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 */ @@ -277,6 +287,7 @@ fd_sbpf_load_shdrs( fd_sbpf_elf_info_t * info, of the rodata segment. This is the minimal virtual address range that spans all sections. The offset of each section in virtual addressing and file addressing is guaranteed to be the same. */ + ulong segment_start = FD_SBPF_MM_PROGRAM_ADDR; /* Lower bound of segment virtual address */ ulong segment_end = 0UL; /* Upper bound of segment virtual address */ ulong tot_section_sz = 0UL; /* Size of all sections */ @@ -293,14 +304,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; } @@ -377,18 +391,22 @@ fd_sbpf_load_shdrs( fd_sbpf_elf_info_t * info, REQUIRE( paddr_end >= sh_offset ); REQUIRE( paddr_end <= elf_sz ); + segment_start = fd_ulong_min( segment_start, sh_addr ); /* 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 ); - REQUIRE( tot_section_sz <= segment_end ); + /* 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 */ + REQUIRE( fd_ulong_sat_add( segment_start, tot_section_sz ) <= segment_end ); /* Require .text section */ @@ -408,6 +426,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 ) ); info->entry_pc = (uint)entry_pc; @@ -530,6 +550,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 49e5be2c73..332eb23bfe 100644 --- a/src/ballet/sbpf/fd_sbpf_loader.h +++ b/src/ballet/sbpf/fd_sbpf_loader.h @@ -16,6 +16,7 @@ /* FIXME make error types more specific */ #define FD_SBPF_ERR_INVALID_ELF (1) +#define FD_SBPF_PROG_RODATA_ALIGN 8UL /* Program struct *****************************************************/ @@ -150,7 +151,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, @@ -183,7 +186,11 @@ fd_sbpf_program_new( void * prog_mem, reject_broken_elfs: true 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 03dbab1474..d3dc5d3c93 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 ); - 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 606890addd..34408da367 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 4a5b035923..14d7c112ee 100644 --- a/src/flamenco/runtime/program/fd_bpf_loader_v3_program.c +++ b/src/flamenco/runtime/program/fd_bpf_loader_v3_program.c @@ -128,7 +128,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 b10a728f1c..378c1079e0 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 94dc99a301..5ee968abf0 100644 --- a/src/flamenco/runtime/tests/Local.mk +++ b/src/flamenco/runtime/tests/Local.mk @@ -6,6 +6,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 diff --git a/src/flamenco/runtime/tests/fd_exec_instr_test.c b/src/flamenco/runtime/tests/fd_exec_instr_test.c index 5d05c702eb..14672267da 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" @@ -860,3 +863,96 @@ 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.data->size; + void const * _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 ) ) ) { + /* 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 ); + 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 878fc3affd..7b8823d330 100644 --- a/src/flamenco/runtime/tests/fd_exec_test.pb.c +++ b/src/flamenco/runtime/tests/fd_exec_test.pb.c @@ -1,5 +1,5 @@ /* Automatically generated nanopb constant definitions */ -/* Generated by nanopb-0.4.8-dev */ +/* Generated by nanopb-0.4.9-dev */ #include "fd_exec_test.pb.h" #if PB_PROTO_HEADER_VERSION != 40 @@ -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 f13781e3ea..b759cedc06 100644 --- a/src/flamenco/runtime/tests/fd_exec_test.pb.h +++ b/src/flamenco/runtime/tests/fd_exec_test.pb.h @@ -1,5 +1,5 @@ /* Automatically generated nanopb header */ -/* Generated by nanopb-0.4.8-dev */ +/* Generated by nanopb-0.4.9-dev */ #ifndef PB_ORG_SOLANA_SEALEVEL_V1_FD_EXEC_TEST_PB_H_INCLUDED #define PB_ORG_SOLANA_SEALEVEL_V1_FD_EXEC_TEST_PB_H_INCLUDED @@ -119,6 +119,38 @@ 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; +} 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" { @@ -134,6 +166,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} #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} +#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} @@ -143,6 +179,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} #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} +#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 @@ -171,6 +211,17 @@ extern "C" { #define FD_EXEC_TEST_INSTR_EFFECTS_CU_AVAIL_TAG 4 #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_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) \ @@ -246,6 +297,37 @@ 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) +#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; @@ -255,6 +337,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 @@ -266,6 +352,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 */ @@ -274,9 +364,14 @@ 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 +#define ORG_SOLANA_SEALEVEL_V1_FD_EXEC_TEST_PB_H_MAX_SIZE FD_EXEC_TEST_INSTR_ACCT_SIZE /* Mapping from canonical names (mangle_names or overridden package name) */ #define org_solana_sealevel_v1_FeatureSet fd_exec_test_FeatureSet @@ -288,6 +383,36 @@ 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 +#define ORG_SOLANA_SEALEVEL_V1_SLOT_CONTEXT_INIT_DEFAULT FD_EXEC_TEST_SLOT_CONTEXT_INIT_DEFAULT +#define ORG_SOLANA_SEALEVEL_V1_TXN_CONTEXT_INIT_DEFAULT FD_EXEC_TEST_TXN_CONTEXT_INIT_DEFAULT +#define ORG_SOLANA_SEALEVEL_V1_INSTR_ACCT_INIT_DEFAULT FD_EXEC_TEST_INSTR_ACCT_INIT_DEFAULT +#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 +#define ORG_SOLANA_SEALEVEL_V1_SLOT_CONTEXT_INIT_ZERO FD_EXEC_TEST_SLOT_CONTEXT_INIT_ZERO +#define ORG_SOLANA_SEALEVEL_V1_TXN_CONTEXT_INIT_ZERO FD_EXEC_TEST_TXN_CONTEXT_INIT_ZERO +#define ORG_SOLANA_SEALEVEL_V1_INSTR_ACCT_INIT_ZERO FD_EXEC_TEST_INSTR_ACCT_INIT_ZERO +#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 113bfe1bdd..7c1d0d3726 100644 --- a/src/flamenco/runtime/tests/fd_exec_test.proto +++ b/src/flamenco/runtime/tests/fd_exec_test.proto @@ -111,3 +111,36 @@ 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; +} + +// 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 ce37a0fb89..4ff7c2925b 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 );