diff --git a/src/flamenco/vm/Local.mk b/src/flamenco/vm/Local.mk index 28dc2f354a..9a381ace10 100644 --- a/src/flamenco/vm/Local.mk +++ b/src/flamenco/vm/Local.mk @@ -5,6 +5,9 @@ ifdef FD_HAS_SECP256K1 $(call add-hdrs,fd_vm_base.h fd_vm.h fd_vm_private.h) # FIXME: PRIVATE TEMPORARILY HERE DUE TO SOME MESSINESS IN FD_VM_SYSCALL.H $(call add-objs,fd_vm fd_vm_interp fd_vm_disasm fd_vm_trace,fd_flamenco) +$(call add-hdrs,test_vm_util.h) +$(call add-objs,test_vm_util,fd_flamenco) + $(call make-bin,fd_vm_tool,fd_vm_tool,fd_flamenco fd_funk fd_ballet fd_util fd_disco,$(SECP256K1_LIBS)) $(call make-unit-test,test_vm_interp,test_vm_interp,fd_flamenco fd_funk fd_ballet fd_util fd_disco,$(SECP256K1_LIBS)) diff --git a/src/flamenco/vm/fd_vm.c b/src/flamenco/vm/fd_vm.c index 36cb07ac16..aa005ae0e9 100644 --- a/src/flamenco/vm/fd_vm.c +++ b/src/flamenco/vm/fd_vm.c @@ -1,4 +1,7 @@ #include "fd_vm_private.h" +#include "../runtime/context/fd_exec_epoch_ctx.h" +#include "../runtime/context/fd_exec_slot_ctx.h" +#include "../features/fd_features.h" char const * fd_vm_strerror( int err ) { @@ -50,6 +53,7 @@ fd_vm_strerror( int err ) { case FD_VM_ERR_INCOMPLETE_LDQ: return "INCOMPLETE_LDQ detected an incomplete ldq at program end"; case FD_VM_ERR_LDQ_NO_ADDL_IMM: return "LDQ_NO_ADDL_IMM detected a ldq without an addl imm following it"; case FD_VM_ERR_NO_SUCH_EXT_CALL: return "NO_SUCH_EXT_CALL detected a call imm with no function was registered for that immediate"; + case FD_VM_ERR_INVALID_REG: return "INVALID_REG detected an invalid register number in a callx instruction"; default: break; } @@ -72,15 +76,16 @@ fd_vm_validate( fd_vm_t const * vm ) { /* A mapping of all the possible 1-byte sBPF opcodes to their validation criteria. */ -# define FD_VALID ((uchar)0) /* Valid opcode */ -# define FD_CHECK_JMP ((uchar)1) /* Validation should check that the instruction is a valid jump */ -# define FD_CHECK_END ((uchar)2) /* Validation should check that the instruction is a valid endianness conversion */ -# define FD_CHECK_ST ((uchar)3) /* Validation should check that the instruction is a valid store */ -# define FD_CHECK_LDQ ((uchar)4) /* Validation should check that the instruction is a valid load-quad */ -# define FD_CHECK_DIV ((uchar)5) /* Validation should check that the instruction is a valid division by immediate */ -# define FD_CHECK_SH32 ((uchar)6) /* Validation should check that the immediate is a valid 32-bit shift exponent */ -# define FD_CHECK_SH64 ((uchar)7) /* Validation should check that the immediate is a valid 64-bit shift exponent */ -# define FD_INVALID ((uchar)8) /* The opcode is invalid */ +# define FD_VALID ((uchar)0) /* Valid opcode */ +# define FD_CHECK_JMP ((uchar)1) /* Validation should check that the instruction is a valid jump */ +# define FD_CHECK_END ((uchar)2) /* Validation should check that the instruction is a valid endianness conversion */ +# define FD_CHECK_ST ((uchar)3) /* Validation should check that the instruction is a valid store */ +# define FD_CHECK_LDQ ((uchar)4) /* Validation should check that the instruction is a valid load-quad */ +# define FD_CHECK_DIV ((uchar)5) /* Validation should check that the instruction is a valid division by immediate */ +# define FD_CHECK_SH32 ((uchar)6) /* Validation should check that the immediate is a valid 32-bit shift exponent */ +# define FD_CHECK_SH64 ((uchar)7) /* Validation should check that the immediate is a valid 64-bit shift exponent */ +# define FD_INVALID ((uchar)8) /* The opcode is invalid */ +# define FD_CHECK_CALLX ((uchar)9) /* Validation should check that callx has valid register number */ static uchar const validation_map[ 256 ] = { /* 0x00 */ FD_INVALID, /* 0x01 */ FD_INVALID, /* 0x02 */ FD_INVALID, /* 0x03 */ FD_INVALID, @@ -118,7 +123,7 @@ fd_vm_validate( fd_vm_t const * vm ) { /* 0x80 */ FD_INVALID, /* 0x81 */ FD_INVALID, /* 0x82 */ FD_INVALID, /* 0x83 */ FD_INVALID, /* 0x84 */ FD_VALID, /* 0x85 */ FD_VALID, /* 0x86 */ FD_INVALID, /* 0x87 */ FD_VALID, /* 0x88 */ FD_INVALID, /* 0x89 */ FD_INVALID, /* 0x8a */ FD_INVALID, /* 0x8b */ FD_INVALID, - /* 0x8c */ FD_INVALID, /* 0x8d */ FD_VALID, /* 0x8e */ FD_INVALID, /* 0x8f */ FD_INVALID, + /* 0x8c */ FD_INVALID, /* 0x8d */ FD_CHECK_CALLX,/* 0x8e */ FD_INVALID, /* 0x8f */ FD_INVALID, /* 0x90 */ FD_INVALID, /* 0x91 */ FD_INVALID, /* 0x92 */ FD_INVALID, /* 0x93 */ FD_INVALID, /* 0x94 */ FD_CHECK_DIV, /* 0x95 */ FD_VALID, /* 0x96 */ FD_INVALID, /* 0x97 */ FD_CHECK_DIV, /* 0x98 */ FD_INVALID, /* 0x99 */ FD_INVALID, /* 0x9a */ FD_INVALID, /* 0x9b */ FD_INVALID, @@ -201,6 +206,15 @@ fd_vm_validate( fd_vm_t const * vm ) { break; } + case FD_CHECK_CALLX: { + /* The register number to read is stored in the immediate. + https://github.com/solana-labs/rbpf/blob/v0.8.1/src/verifier.rs#L218 */ + if( FD_UNLIKELY( instr.imm > ( FD_FEATURE_ACTIVE( vm->instr_ctx->slot_ctx, reject_callx_r10 ) ? 9 : 10 ) ) ) { + return FD_VM_ERR_INVALID_REG; + } + break; + } + case FD_INVALID: default: return FD_VM_ERR_INVALID_OPCODE; } @@ -337,6 +351,11 @@ fd_vm_init( return NULL; } + if ( FD_UNLIKELY( instr_ctx == NULL ) ) { + FD_LOG_WARNING(( "NULL instr_ctx" )); + return NULL; + } + // Set the vm fields vm->instr_ctx = instr_ctx; vm->heap_max = heap_max; diff --git a/src/flamenco/vm/fd_vm_base.h b/src/flamenco/vm/fd_vm_base.h index ac7b4e1f6b..dab73f29ec 100644 --- a/src/flamenco/vm/fd_vm_base.h +++ b/src/flamenco/vm/fd_vm_base.h @@ -74,6 +74,7 @@ #define FD_VM_ERR_INCOMPLETE_LDQ (-32) /* detected an incomplete ldq at program end */ #define FD_VM_ERR_LDQ_NO_ADDL_IMM (-33) /* detected a ldq without an addl imm following it */ #define FD_VM_ERR_NO_SUCH_EXT_CALL (-34) /* detected a call imm with no function was registered for that immediate */ +#define FD_VM_ERR_INVALID_REG (-35) /* detected an invalid register */ FD_PROTOTYPES_BEGIN diff --git a/src/flamenco/vm/instr_test/jump.instr b/src/flamenco/vm/instr_test/jump.instr index 59be4f6e03..e9fc023095 100644 --- a/src/flamenco/vm/instr_test/jump.instr +++ b/src/flamenco/vm/instr_test/jump.instr @@ -362,3 +362,21 @@ $ op=dd dst=6 src=5 off=ffff r6=3333333344449444 r5 =3333333344444444 : ok # n $ op=dd dst=6 src=5 off=ffff r6=3333333344444444 r5 =3333333344444444 : err # taken $ op=dd dst=a src=1 off=0000 : vfy $ op=dd dst=9 src=b off=0000 : vfy + +# call_reg reg[imm] (these should fail during exec by default) +$ op=8d dst=9 src=9 imm=0 : err +$ op=8d dst=9 src=9 imm=1 : err +$ op=8d dst=9 src=9 imm=9 : err +$ op=8d dst=9 src=9 imm=b : vfy +$ op=8d dst=9 src=9 imm=ffffffff : vfy +$ op=8d dst=9 src=9 imm=a : err + +$ reject_callx_r10=1 + op=8d dst=9 src=9 imm=a : vfy +$ op=8d dst=9 src=9 imm=0 : err +$ op=8d dst=9 src=9 imm=1 : err +$ op=8d dst=9 src=9 imm=9 : err +$ op=8d dst=9 src=9 imm=b : vfy +$ op=8d dst=9 src=9 imm=fffffffff : vfy + +$ reject_callx_r10=0 diff --git a/src/flamenco/vm/syscall/Local.mk b/src/flamenco/vm/syscall/Local.mk index 26a4230479..fc7a0ac530 100644 --- a/src/flamenco/vm/syscall/Local.mk +++ b/src/flamenco/vm/syscall/Local.mk @@ -3,9 +3,9 @@ ifdef FD_HAS_HOSTED $(call add-hdrs,fd_vm_syscall.h fd_vm_cpi.h) $(call add-objs,fd_vm_syscall fd_vm_syscall_cpi fd_vm_syscall_hash fd_vm_syscall_crypto fd_vm_syscall_curve fd_vm_syscall_pda fd_vm_syscall_runtime fd_vm_syscall_util,fd_flamenco) -$(call make-unit-test,test_vm_syscalls,test_vm_syscalls,fd_flamenco fd_funk fd_ballet fd_util) $(call make-unit-test,test_vm_syscall_cpi,test_vm_syscall_cpi,fd_flamenco fd_funk fd_ballet fd_util) $(call make-unit-test,test_vm_syscall_curve,test_vm_syscall_curve,fd_flamenco fd_funk fd_ballet fd_util) +$(call make-unit-test,test_vm_syscalls,test_vm_syscalls,fd_flamenco fd_funk fd_ballet fd_util) $(call run-unit-test,test_vm_syscalls) $(call run-unit-test,test_vm_syscall_cpi) diff --git a/src/flamenco/vm/syscall/test_vm_syscalls.c b/src/flamenco/vm/syscall/test_vm_syscalls.c index 958e31f509..97aebc5553 100644 --- a/src/flamenco/vm/syscall/test_vm_syscalls.c +++ b/src/flamenco/vm/syscall/test_vm_syscalls.c @@ -1,4 +1,5 @@ #include "fd_vm_syscall.h" +#include "../test_vm_util.h" static inline void set_memory_region( uchar * mem, ulong sz ) { for( ulong i=0UL; i #include #include @@ -44,6 +45,7 @@ struct test_input { ushort off; ulong imm; ulong reg[REG_CNT]; + bool reject_callx_r10; }; typedef struct test_input test_input_t; @@ -315,6 +317,11 @@ parse_token( test_parser_t * p, ulong * out = p->state == PARSE_STATE_ASSERT ? p->effects.reg : p->input.reg; out[ reg ] = parse_hex_int( p ); + } else if( 0==strncmp( word, "reject_callx_r10", word_len ) ) { + + parse_assign_sep( p ); + p->input.reject_callx_r10 = parse_hex_int( p ); + } else { FD_LOG_ERR(( "Unexpected token '%.*s' at %s(%lu)", (int)word_len, word, p->path, p->line )); @@ -396,9 +403,11 @@ run_input( test_input_t const * input, fd_sbpf_syscalls_new( aligned_alloc( fd_sbpf_syscalls_align(), fd_sbpf_syscalls_footprint() ) ) ); + fd_exec_instr_ctx_t * instr_ctx = test_vm_minimal_exec_instr_ctx( fd_libc_alloc_virtual(), input->reject_callx_r10 ); + int vm_ok = !!fd_vm_init( /* vm */ vm, - /* instr_ctx */ NULL, + /* instr_ctx */ instr_ctx, /* heap_max */ 0UL, /* entry_cu */ 100UL, /* rodata */ (uchar const *)text, @@ -423,6 +432,8 @@ run_input( test_input_t const * input, run_input2( out, vm ); + /* Clean up */ + test_vm_exec_instr_ctx_delete( instr_ctx ); free( fd_sbpf_syscalls_delete ( fd_sbpf_syscalls_leave ( syscalls ) ) ); free( fd_sbpf_calldests_delete( fd_sbpf_calldests_leave( calldests ) ) ); free( input_copy ); diff --git a/src/flamenco/vm/test_vm_interp.c b/src/flamenco/vm/test_vm_interp.c index e2f1ad2512..13e13717c0 100644 --- a/src/flamenco/vm/test_vm_interp.c +++ b/src/flamenco/vm/test_vm_interp.c @@ -1,6 +1,7 @@ #include "fd_vm.h" #include "fd_vm_base.h" #include "fd_vm_private.h" +#include "test_vm_util.h" static int accumulator_syscall( FD_PARAM_UNUSED void * _vm, @@ -15,11 +16,12 @@ accumulator_syscall( FD_PARAM_UNUSED void * _vm, } static void -test_program_success( char * test_case_name, - ulong expected_result, - ulong const * text, - ulong text_cnt, - fd_sbpf_syscalls_t * syscalls ) { +test_program_success( char * test_case_name, + ulong expected_result, + ulong const * text, + ulong text_cnt, + fd_sbpf_syscalls_t * syscalls, + fd_exec_instr_ctx_t * instr_ctx ) { //FD_LOG_NOTICE(( "Test program: %s", test_case_name )); fd_sha256_t _sha[1]; @@ -31,7 +33,7 @@ test_program_success( char * test_case_name, int vm_ok = !!fd_vm_init( /* vm */ vm, - /* instr_ctx */ NULL, + /* instr_ctx */ instr_ctx, /* heap_max */ FD_VM_HEAP_DEFAULT, /* entry_cu */ FD_VM_COMPUTE_UNIT_LIMIT, /* rodata */ (uchar *)text, @@ -215,13 +217,14 @@ test_0cu_exit( void ) { fd_vm_instr( FD_SBPF_OP_EXIT, 0, 0, 0, 0 ) }; ulong text_cnt = 3UL; + fd_exec_instr_ctx_t * instr_ctx = test_vm_minimal_exec_instr_ctx( fd_libc_alloc_virtual(), false /* not tested here*/ ); /* Ensure the VM exits with success if the CU count after the final exit instruction reaches zero. */ int vm_ok = !!fd_vm_init( /* vm */ vm, - /* instr_ctx */ NULL, + /* instr_ctx */ instr_ctx, /* heap_max */ FD_VM_HEAP_DEFAULT, /* entry_cu */ text_cnt, /* rodata */ (uchar *)text, @@ -248,7 +251,7 @@ test_0cu_exit( void ) { vm_ok = !!fd_vm_init( /* vm */ vm, - /* instr_ctx */ NULL, + /* instr_ctx */ instr_ctx, /* heap_max */ FD_VM_HEAP_DEFAULT, /* entry_cu */ text_cnt - 1UL, /* rodata */ (uchar *)text, @@ -271,6 +274,7 @@ test_0cu_exit( void ) { FD_TEST( fd_vm_exec ( vm )==FD_VM_ERR_SIGCOST ); fd_vm_delete( fd_vm_leave( vm ) ); + test_vm_exec_instr_ctx_delete( instr_ctx ); fd_sha256_delete( fd_sha256_leave( sha ) ); } @@ -285,11 +289,13 @@ main( int argc, fd_sbpf_syscalls_t * syscalls = fd_sbpf_syscalls_join( fd_sbpf_syscalls_new( _syscalls ) ); FD_TEST( syscalls ); + fd_exec_instr_ctx_t * instr_ctx = test_vm_minimal_exec_instr_ctx( fd_libc_alloc_virtual(), false ); + FD_TEST( fd_vm_syscall_register( syscalls, "accumulator", accumulator_syscall )==FD_VM_SUCCESS ); # define TEST_PROGRAM_SUCCESS( test_case_name, expected_result, text_cnt, ... ) do { \ ulong _text[ text_cnt ] = { __VA_ARGS__ }; \ - test_program_success( (test_case_name), (expected_result), _text, (text_cnt), syscalls ); \ + test_program_success( (test_case_name), (expected_result), _text, (text_cnt), syscalls, instr_ctx ); \ } while(0) # define FD_SBPF_INSTR(op, dst, src, off, val) (fd_vm_instr( op, dst, src, off, val )) @@ -921,23 +927,24 @@ main( int argc, ulong * text = (ulong *)malloc( sizeof(ulong)*text_cnt ); /* FIXME: gross */ generate_random_alu_instrs( rng, text, text_cnt ); - test_program_success( "alu_bench", 0x0, text, text_cnt, syscalls ); + test_program_success( "alu_bench", 0x0, text, text_cnt, syscalls, instr_ctx ); generate_random_alu64_instrs( rng, text, text_cnt ); - test_program_success( "alu64_bench", 0x0, text, text_cnt, syscalls ); + test_program_success( "alu64_bench", 0x0, text, text_cnt, syscalls, instr_ctx ); text_cnt = 1024UL; generate_random_alu_instrs( rng, text, text_cnt ); - test_program_success( "alu_bench_short", 0x0, text, text_cnt, syscalls ); + test_program_success( "alu_bench_short", 0x0, text, text_cnt, syscalls, instr_ctx ); generate_random_alu64_instrs( rng, text, text_cnt ); - test_program_success( "alu64_bench_short", 0x0, text, text_cnt, syscalls ); + test_program_success( "alu64_bench_short", 0x0, text, text_cnt, syscalls, instr_ctx ); test_0cu_exit(); free( text ); fd_sbpf_syscalls_delete( fd_sbpf_syscalls_leave( syscalls ) ); + test_vm_exec_instr_ctx_delete( instr_ctx ); FD_LOG_NOTICE(( "pass" )); fd_rng_delete( fd_rng_leave( rng ) ); diff --git a/src/flamenco/vm/test_vm_util.c b/src/flamenco/vm/test_vm_util.c new file mode 100644 index 0000000000..72e2a326d6 --- /dev/null +++ b/src/flamenco/vm/test_vm_util.c @@ -0,0 +1,64 @@ +#include "test_vm_util.h" +#include "../runtime/context/fd_exec_epoch_ctx.h" +#include "../runtime/context/fd_exec_slot_ctx.h" + +/* Generates a minimal instruction context to supply to fd_vm_t. + For now, we just need to setup feature flags. */ +fd_exec_instr_ctx_t * +test_vm_minimal_exec_instr_ctx( + fd_valloc_t valloc, + bool reject_callx_r10 ) { + + void * _ctx = fd_exec_instr_ctx_new( fd_valloc_malloc( valloc, FD_EXEC_INSTR_CTX_ALIGN, FD_EXEC_INSTR_CTX_FOOTPRINT ) ); + + fd_exec_instr_ctx_t * ctx = fd_exec_instr_ctx_join( _ctx ); + + if ( !ctx ) { + return NULL; + } + + ctx->valloc = valloc; + + /* Keep slot_ctx and epoch_ctx initialization simple. We only want features ATM. + Feel free to change this to use actual init semantics (*_new and *_join), + but remember to update the cleanup function below :) */ + void * _slot_ctx = fd_valloc_malloc( valloc, FD_EXEC_SLOT_CTX_ALIGN, FD_EXEC_SLOT_CTX_FOOTPRINT ); + fd_exec_slot_ctx_t * slot_ctx = (fd_exec_slot_ctx_t *)( _slot_ctx ); + + void * _epoch_ctx = fd_valloc_malloc( valloc, fd_exec_epoch_ctx_align() , sizeof(fd_exec_epoch_ctx_t) ); + fd_exec_epoch_ctx_t * epoch_ctx = (fd_exec_epoch_ctx_t *) _epoch_ctx; + + if ( !epoch_ctx || !slot_ctx ) { + return NULL; + } + + ctx->epoch_ctx = epoch_ctx; /* technically not necessary, given how FEATURE_ACTIVE macro works */ + ctx->slot_ctx = slot_ctx; + + slot_ctx->epoch_ctx = epoch_ctx; + + /* Setup feature flags */ + fd_features_disable_all( &epoch_ctx->features ); + if ( reject_callx_r10 ) { + fd_features_set( &epoch_ctx->features, fd_feature_id_query(TEST_VM_REJECT_CALLX_R10_FEATURE_PREFIX), 0UL ); + } + + return ctx; +} + +void +test_vm_exec_instr_ctx_delete( + fd_exec_instr_ctx_t * ctx ) { + + fd_valloc_t valloc = ctx->valloc; + fd_exec_slot_ctx_t * slot_ctx = ctx->slot_ctx; + fd_exec_epoch_ctx_t * epoch_ctx = slot_ctx->epoch_ctx; + + fd_exec_instr_ctx_delete( fd_exec_instr_ctx_leave( ctx ) ); + + fd_valloc_free( valloc, epoch_ctx ); + fd_valloc_free( valloc, slot_ctx ); + fd_valloc_free( valloc, ctx ); + + return; +} diff --git a/src/flamenco/vm/test_vm_util.h b/src/flamenco/vm/test_vm_util.h new file mode 100644 index 0000000000..226f4ae830 --- /dev/null +++ b/src/flamenco/vm/test_vm_util.h @@ -0,0 +1,21 @@ +#ifndef HEADER_fd_src_flamenco_vm_test_vm_util_h +#define HEADER_fd_src_flamenco_vm_test_vm_util_h + +#include "fd_vm.h" +#include "../runtime/context/fd_exec_instr_ctx.h" +#include "../../util/valloc/fd_valloc.h" + +#define TEST_VM_REJECT_CALLX_R10_FEATURE_PREFIX (0x7e787d5c6d662d23) + +fd_exec_instr_ctx_t * +test_vm_minimal_exec_instr_ctx( + fd_valloc_t valloc, + bool reject_callx_r10 +); + +void +test_vm_exec_instr_ctx_delete( + fd_exec_instr_ctx_t * ctx +); + +#endif