Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

flamenco, fuzz: ELF loader fuzz target #1808

Merged
merged 1 commit into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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))
ravyu-jump marked this conversation as resolved.
Show resolved Hide resolved
$(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{
ravyu-jump marked this conversation as resolved.
Show resolved Hide resolved

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() ));
ravyu-jump marked this conversation as resolved.
Show resolved Hide resolved
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
ravyu-jump marked this conversation as resolved.
Show resolved Hide resolved
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