Skip to content

Commit

Permalink
Fuzz target for ELF loader
Browse files Browse the repository at this point in the history
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
  • Loading branch information
ravyu-jump committed Jun 7, 2024
1 parent bc6880f commit 3bd3634
Show file tree
Hide file tree
Showing 16 changed files with 506 additions and 12 deletions.
1 change: 1 addition & 0 deletions contrib/test/run_test_vectors.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
34 changes: 28 additions & 6 deletions src/ballet/sbpf/fd_sbpf_loader.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 );
Expand Down
11 changes: 9 additions & 2 deletions src/ballet/sbpf/fd_sbpf_loader.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 *****************************************************/
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/ballet/sbpf/test_sbpf_loader.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 );


Expand Down
2 changes: 1 addition & 1 deletion src/flamenco/runtime/program/fd_bpf_loader_v2_program.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
2 changes: 1 addition & 1 deletion src/flamenco/runtime/program/fd_bpf_loader_v3_program.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
2 changes: 1 addition & 1 deletion src/flamenco/runtime/program/fd_bpf_program_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
1 change: 1 addition & 0 deletions src/flamenco/runtime/tests/Local.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
116 changes: 116 additions & 0 deletions src/flamenco/runtime/tests/fd_exec_instr_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 <assert.h>
#include "../sysvar/fd_sysvar_cache.h"

Expand Down Expand Up @@ -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;
}
15 changes: 15 additions & 0 deletions src/flamenco/runtime/tests/fd_exec_instr_test.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
49 changes: 49 additions & 0 deletions src/flamenco/runtime/tests/fd_exec_sol_compat.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
12 changes: 12 additions & 0 deletions src/flamenco/runtime/tests/fd_exec_test.pb.c

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 3bd3634

Please sign in to comment.