diff --git a/contrib/test/test-vectors-commit-sha.txt b/contrib/test/test-vectors-commit-sha.txt index 3b889abc02c..e92ec7e29e7 100644 --- a/contrib/test/test-vectors-commit-sha.txt +++ b/contrib/test/test-vectors-commit-sha.txt @@ -1 +1 @@ -697bd4ac4f19e042403fe17a2ba6d0baaa161773 +67e30036a4b3b448dfcf73649974d60c55a10bea diff --git a/src/ballet/elf/fd_elf.h b/src/ballet/elf/fd_elf.h index cef85f66623..530aaeb2d6a 100644 --- a/src/ballet/elf/fd_elf.h +++ b/src/ballet/elf/fd_elf.h @@ -90,6 +90,7 @@ #define FD_ELF_DT_REL 17 #define FD_ELF_DT_RELSZ 18 #define FD_ELF_DT_RELENT 19 +#define FD_ELF_DT_NUM 35 /* FD_ELF64_ST_TYPE extracts the symbol type from symbol st_info */ diff --git a/src/ballet/elf/fd_elf64.h b/src/ballet/elf/fd_elf64.h index 6232ba39347..21d316ace55 100644 --- a/src/ballet/elf/fd_elf64.h +++ b/src/ballet/elf/fd_elf64.h @@ -84,10 +84,12 @@ struct __attribute__((packed)) fd_elf64_rela_ { }; typedef struct fd_elf64_rela_ fd_elf64_rela; -/* fd_elf64_dyn: Dynamic section entry */ +/* fd_elf64_dyn: Dynamic section entry + NOTE: The ELF specification states that d_tag should be a signed + long, but the Solana ELF loader uses an unsigned long. */ struct __attribute__((packed)) fd_elf64_dyn_ { - long d_tag; + ulong d_tag; union { ulong d_val; ulong d_ptr; diff --git a/src/ballet/sbpf/fd_sbpf_loader.c b/src/ballet/sbpf/fd_sbpf_loader.c index 73b307e82e4..cc68634f6a1 100644 --- a/src/ballet/sbpf/fd_sbpf_loader.c +++ b/src/ballet/sbpf/fd_sbpf_loader.c @@ -8,43 +8,6 @@ #include #include -/* Error handling *****************************************************/ - -/* Thread local storage last error value */ - -static FD_TL int ldr_errno = 0; -static FD_TL int ldr_err_srcln = -1; -#define FD_SBPF_ERRBUF_SZ (128UL) -static FD_TL char fd_sbpf_errbuf[ FD_SBPF_ERRBUF_SZ ] = {0}; - -/* fd_sbpf_loader_seterr remembers the error ID and line number of the - current file at which the last error occurred. */ - -__attribute__((cold,noinline)) static int -fd_sbpf_loader_seterr( int err, - int srcln ) { - ldr_errno = err; - ldr_err_srcln = srcln; - return err; -} - -/* Macros for returning an error from the current function while also - remembering the error code. */ - -#define ERR( err ) return fd_sbpf_loader_seterr( (err), __LINE__ ) -#define FAIL() ERR( FD_SBPF_ERR_INVALID_ELF ) -#define REQUIRE(x) do { if ( FD_UNLIKELY( !(x) ) ) FAIL(); } while (0) - -char const * -fd_sbpf_strerror( void ) { - if( FD_UNLIKELY( ldr_errno==0 ) ) - strcpy( fd_sbpf_errbuf, "ok" ); - else - snprintf( fd_sbpf_errbuf, FD_SBPF_ERRBUF_SZ, - "code %d at %s(%d)", ldr_errno, __FILE__, ldr_err_srcln ); - return fd_sbpf_errbuf; -} - /* ELF loader, part 1 ************************************************** Start with a static piece of scratch memory and do basic validation @@ -89,455 +52,58 @@ typedef union fd_sbpf_elf fd_sbpf_elf_t; #define EXPECTED_PHDR_CNT (4U) -/* _fd_int_store_if_negative stores x to *p if *p is negative (branchless) */ +struct fd_sbpf_range { + ulong lo; + ulong hi; +}; +typedef struct fd_sbpf_range fd_sbpf_range_t; +/* fd_sbpf_range_contains returns 1 if x is in the range + [range.lo, range.hi) and 0 otherwise. */ static inline int -_fd_int_store_if_negative( int * p, - int x ) { - return (*p = fd_int_if( (*p)<0, x, *p )); -} - -/* fd_sbpf_check_ehdr verifies the ELF file header. */ - -static int -fd_sbpf_check_ehdr( fd_elf64_ehdr const * ehdr, - ulong elf_sz, - uint min_version, - uint max_version ) { - - /* Validate ELF magic */ - REQUIRE( ( fd_uint_load_4( ehdr->e_ident )==0x464c457fU ) - /* 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 ) - & ( ehdr->e_ident[ FD_ELF_EI_OSABI ]==FD_ELF_OSABI_NONE ) - & ( ehdr->e_type ==FD_ELF_ET_DYN ) - & ( ( ehdr->e_machine ==FD_ELF_EM_BPF ) - | ( ehdr->e_machine ==FD_ELF_EM_SBPF ) ) - & ( ehdr->e_version ==1 ) - /* Coherence checks */ - & ( ehdr->e_ehsize ==sizeof(fd_elf64_ehdr) ) - & ( ehdr->e_phentsize==sizeof(fd_elf64_phdr) ) - & ( ehdr->e_shentsize==sizeof(fd_elf64_shdr) ) - & ( ehdr->e_shstrndx < ehdr->e_shnum ) - & ( ehdr->e_flags >= min_version ) - & ( max_version - ? ( ehdr->e_flags <= max_version ) - : ( ehdr->e_flags != FD_ELF_EF_SBPF_V2 ) - ) - ); - - /* Bounds check program header table */ - - ulong const phoff = ehdr->e_phoff; - ulong const phnum = ehdr->e_phnum; - REQUIRE( ( fd_ulong_is_aligned( phoff, 8UL ) ) - & ( phoff<=elf_sz ) ); /* out of bounds */ - - REQUIRE( phnum<=(ULONG_MAX/sizeof(fd_elf64_phdr)) ); /* overflow */ - ulong const phsz = phnum*sizeof(fd_elf64_phdr); - - ulong const phoff_end = phoff+phsz; - REQUIRE( ( phoff_end>=phoff ) /* overflow */ - & ( phoff_end<=elf_sz ) /* out of bounds */ - & ( (phoff_end==0UL) /* overlaps file header */ - | (phoff>=sizeof(fd_elf64_ehdr)) ) ); - - /* Bounds check section header table */ - - ulong const shoff = ehdr->e_shoff; - ulong const shnum = ehdr->e_shnum; - REQUIRE( ( fd_ulong_is_aligned( shoff, 8UL ) ) - & ( shoff>=sizeof(fd_elf64_ehdr) ) /* overlaps file header */ - & ( shoff< elf_sz ) /* out of bounds */ - & ( shnum> 0UL ) ); /* not enough sections */ - - REQUIRE( shoff<=(ULONG_MAX/sizeof(fd_elf64_shdr)) ); /* overflow */ - ulong const shsz = shnum*sizeof(fd_elf64_shdr); - - ulong const shoff_end = shoff+shsz; - REQUIRE( ( shoff_end>=shoff ) /* overflow */ - & ( shoff_end<=elf_sz ) ); /* out of bounds */ - - /* Overlap checks */ - - REQUIRE( (phoff>=shoff_end) | (shoff>=phoff_end) ); /* overlap shdrs<>phdrs */ - - return 0; +fd_sbpf_range_contains( fd_sbpf_range_t const * range, ulong x ) { + return !!(( range->lo<=x ) & ( xhi )); } -/* shdr_get_loaded_size returns the loaded size of a section, i.e. the - number of bytes loaded into the rodata segment. sBPF ELFs grossly - misuse the sh_size parameter. When SHT_NOBITS is set, the actual - section size is zero, and the section size is ignored. */ +/* Mimics Elf64Shdr::file_range(). Returns a pointer to range (Some) if + the section header type is not SHT_NOBITS, and sets range.{lo, hi} to + the section header offset and offset + size, respectively. Returns + NULL (None) otherwise, and sets both range.{lo, hi} to 0 (the default + values for a Rust Range type). -static ulong -shdr_get_loaded_size( fd_elf64_shdr const * shdr ) { - return fd_ulong_if( shdr->sh_type==FD_ELF_SHT_NOBITS, 0UL, shdr->sh_size ); -} + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L87-L93 */ -/* check_cstr verifies a string in a string table. Returns non-NULL if - the string is null terminated and contains at most max non-NULL - characters. Returns NULL if the off is out of bounds, or if the max - or EOF are reached before the null terminator. */ - -static char const * -check_cstr( uchar const * bin, - ulong bin_sz, - ulong off, - ulong max, - ulong * opt_sz ) { - if( FD_UNLIKELY( off>=bin_sz ) ) return NULL; - max += 1UL; /* include NULL terminator */ - max = fd_ulong_min( max, bin_sz-off ); /* truncate to available size */ - char const * cstr = (char const *)( bin+off ); - ulong len = strnlen( cstr, max ); - if( opt_sz ) *opt_sz = len; - return lensh_type==FD_ELF_SHT_NOBITS ) { + *range = (fd_sbpf_range_t) { .lo = 0UL, .hi = 0UL }; + return NULL; + } else { + *range = (fd_sbpf_range_t) { .lo = shdr->sh_offset, .hi = fd_ulong_sat_add( shdr->sh_offset, shdr->sh_size ) }; + return range; + } } -/* fd_sbpf_load_phdrs walks the program header table. Remembers info - along the way, and performs various validations. - - Assumes that ... - - table does not overlap with file header or section header table and - is within bounds - - offset of program header table is 8 byte aligned */ - +/* Converts an ElfParserError code to an ElfError code. + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L112-L132 */ static int -fd_sbpf_load_phdrs( fd_sbpf_elf_info_t * info, - fd_sbpf_elf_t const * elf, - ulong elf_sz ) { - - ulong const pht_offset = elf->ehdr.e_phoff; - ulong const pht_cnt = elf->ehdr.e_phnum; - - /* Virtual address of last seen program header */ - ulong p_load_vaddr = 0UL; - - /* Read program header table */ - fd_elf64_phdr const * phdr = (fd_elf64_phdr const *)( elf->bin + pht_offset ); - for( ulong i=0; iphndx_dyn, (int)i ); - break; - case FD_ELF_PT_LOAD: - /* LOAD segments must be ordered */ - REQUIRE( phdr[ i ].p_vaddr >= p_load_vaddr ); - p_load_vaddr = phdr[ i ].p_vaddr; - /* Segment must be within bounds */ - REQUIRE( ( phdr[ i ].p_offset + phdr[ i ].p_filesz >= phdr[ i ].p_offset ) - & ( phdr[ i ].p_offset + phdr[ i ].p_filesz <= elf_sz ) ); - /* No overlap checks */ - break; +fd_sbpf_elf_parser_err_to_elf_err( int err ) { + switch( err ) { + case FD_SBPF_ELF_SUCCESS: + return err; + case FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS: + return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS; + case FD_SBPF_ELF_PARSER_ERR_INVALID_PROGRAM_HEADER: + return FD_SBPF_ELF_ERR_INVALID_PROGRAM_HEADER; default: - /* Ignore other segment types */ - break; - } + return FD_SBPF_ELF_ERR_FAILED_TO_PARSE; } - - return 0; } -/* FD_SBPF_SECTION_NAME_SZ_MAX is the maximum length of a symbol name cstr - including zero terminator. - https://github.com/solana-labs/rbpf/blob/c168a8715da668a71584ea46696d85f25c8918f6/src/elf_parser/mod.rs#L12 */ +/* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L11-L13 */ #define FD_SBPF_SECTION_NAME_SZ_MAX (16UL) - -/* fd_sbpf_load_shdrs walks the section header table. Remembers info - along the way, and performs various validations. - - Assumes that ... - - table does not overlap with file header or program header table and - is within bounds - - offset of section header table is 8 byte aligned - - section header table has at least one entry */ - -static int -fd_sbpf_load_shdrs( fd_sbpf_elf_info_t * info, - fd_sbpf_elf_t const * elf, - ulong elf_sz, - int elf_deploy_checks ) { - - /* File Header */ - ulong const eh_offset = 0UL; - ulong const eh_offend = sizeof(fd_elf64_ehdr); - - /* Section Header Table */ - ulong const sht_offset = elf->ehdr.e_shoff; - ulong const sht_cnt = elf->ehdr.e_shnum; - ulong const sht_sz = sht_cnt*sizeof(fd_elf64_shdr); - ulong const sht_offend = sht_offset + sht_sz; - - fd_elf64_shdr const * shdr = (fd_elf64_shdr const *)( elf->bin + sht_offset ); - - /* Program Header Table */ - ulong const pht_offset = elf->ehdr.e_phoff; - 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 */ - REQUIRE( shdr[ elf->ehdr.e_shstrndx ].sh_type==FD_ELF_SHT_STRTAB ); - - ulong shstr_off = shdr[ elf->ehdr.e_shstrndx ].sh_offset; - ulong shstr_sz = shdr[ elf->ehdr.e_shstrndx ].sh_size; - REQUIRE( shstr_offloaded_sections, 0, sizeof(info->loaded_sections) ); - - /* Validate section header table. - Check that all sections are in bounds, ordered, and don't overlap. */ - - ulong min_sh_offset = 0UL; /* lowest permitted section offset */ - - /* Keep track of the physical (file address) end of all relevant - sections to determine rodata_sz */ - ulong psegment_end = 0UL; /* Upper bound of physical (file) addressing */ - - /* While validating section header table, also figure out size - of the rodata segment. This is the minimal virtual address range - that spans all sections. */ - ulong vsegment_start = FD_SBPF_MM_PROGRAM_ADDR; /* Lower bound of segment virtual address */ - ulong vsegment_end = 0UL; /* Upper bound of segment virtual address */ - - ulong tot_section_sz = 0UL; /* Size of all sections */ - ulong lowest_addr = 0UL; - ulong highest_addr = 0UL; - - for( ulong i=0UL; i0UL || sh_type==FD_ELF_SHT_NULL ); - - /* check that physical range has no overflow and is within bounds */ - REQUIRE( sh_offend >= sh_offset ); - 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 - 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; - } - - if( sh_type==FD_ELF_SHT_DYNAMIC ) { - /* Remember first SHT_DYNAMIC segment */ - _fd_int_store_if_negative( &info->shndx_dyn, (int)i ); - } - - ulong name_off = shstr_off + (ulong)sh_name; - REQUIRE( ( name_offbin + shstr_off, shstr_sz, sh_name, FD_SBPF_SECTION_NAME_SZ_MAX-1UL, NULL ); - REQUIRE( name_ptr ); - char __attribute__((aligned(16UL))) name[ FD_SBPF_SECTION_NAME_SZ_MAX ] = {0}; - strncpy( name, name_ptr, FD_SBPF_SECTION_NAME_SZ_MAX-1UL ); - - /* Check name */ - /* TODO switch table for this? */ - /* TODO reject duplicate sections */ - - /* https://github.com/firedancer-io/sbpf/blob/sbpf-v0.11.1-patches/src/elf.rs#L855 */ - if( FD_LIKELY( strncmp( name, ".text", sizeof(".text") )==0 || - strncmp( name, ".rodata", sizeof(".rodata") )==0 || - strncmp( name, ".data.rel.ro", sizeof(".data.rel.ro") )==0 || - strncmp( name, ".eh_frame", sizeof(".eh_frame") )==0 ) ) { - lowest_addr = fd_ulong_min( lowest_addr, sh_addr ); - highest_addr = fd_ulong_max( highest_addr, fd_ulong_sat_add( sh_addr, sh_size ) ); - } - - int load = 0; /* should section be loaded? */ - - /**/ if( 0==memcmp( name, ".text", 6UL /* equals */ ) ) { - REQUIRE( (info->shndx_text)<0 ); /* check for duplicate */ - info->shndx_text = (int)i; - load = 1; - } - else if( (0==memcmp( name, ".rodata", 8UL /* equals */ ) ) - | (0==memcmp( name, ".data.rel.ro", 13UL /* equals */ ) ) - | (0==memcmp( name, ".eh_frame", 10UL /* equals */ ) ) ) { - load = 1; - } - else if( 0==memcmp( name, ".symtab", 8UL /* equals */ ) ) { - REQUIRE( (info->shndx_symtab)<0 ); - info->shndx_symtab = (int)i; - } - else if( 0==memcmp( name, ".strtab", 8UL /* equals */ ) ) { - REQUIRE( (info->shndx_strtab)<0 ); - info->shndx_strtab = (int)i; - } - else if( 0==memcmp( name, ".dynstr", 8UL /* equals */ ) ) { - REQUIRE( (info->shndx_dynstr)<0 ); - info->shndx_dynstr = (int)i; - } - else if( 0==memcmp( name, ".bss", 4UL /* has prefix */ ) ) { - FAIL(); - } - else if( 0==memcmp( name, ".data.rel", 9UL /* has prefix */ ) ) {} /* ignore */ - else if( (0==memcmp( name, ".data", 5UL /* has prefix */ ) ) - & ( ( shdr[ i ].sh_flags & (FD_ELF_SHF_ALLOC|FD_ELF_SHF_WRITE) ) - ==(FD_ELF_SHF_ALLOC|FD_ELF_SHF_WRITE) ) ) { - FAIL(); - } - else {} /* ignore */ - /* else ignore */ - - if( load ) { - /* Remember that section should be loaded */ - - info->loaded_sections[ i>>6UL ] |= (1UL)<<(i&63UL); - - /* Check that virtual address range is in MM_PROGRAM bounds */ - - ulong sh_actual_size = shdr_get_loaded_size( &shdr[ i ] ); - ulong sh_virtual_end = sh_addr + sh_actual_size; - - /* https://github.com/solana-labs/rbpf/blob/v0.8.0/src/elf.rs#L426 */ - if ( FD_UNLIKELY( elf_deploy_checks ) ){ - REQUIRE( sh_addr == sh_offset ); - } - REQUIRE( sh_addr < FD_SBPF_MM_PROGRAM_ADDR ); /* overflow check */ - REQUIRE( sh_actual_size < FD_SBPF_MM_PROGRAM_ADDR ); /* overflow check */ - REQUIRE( sh_virtual_end <= FD_SBPF_MM_STACK_ADDR-FD_SBPF_MM_PROGRAM_ADDR ); /* check overlap with stack */ - - /* Check that physical address range is in bounds - (Seems redundant?) */ - ulong paddr_end = sh_offset + sh_actual_size; - REQUIRE( paddr_end >= sh_offset ); - REQUIRE( paddr_end <= elf_sz ); - - vsegment_start = fd_ulong_min( vsegment_start, sh_addr ); - /* Expand range to fit section */ - psegment_end = fd_ulong_max( psegment_end, paddr_end ); - vsegment_end = fd_ulong_max( vsegment_end, sh_virtual_end ); - - /* Coherence check sum of section sizes */ - REQUIRE( tot_section_sz + sh_actual_size >= tot_section_sz ); /* overflow check */ - tot_section_sz += sh_actual_size; - } - } - - /* https://github.com/firedancer-io/sbpf/blob/sbpf-v0.11.1-patches/src/elf.rs#L982 */ - REQUIRE( fd_ulong_sat_sub( highest_addr, lowest_addr ) <= elf_sz ); /* addr out of bounds */ - - /* More coherence checks */ - REQUIRE( psegment_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 */ - if ( FD_UNLIKELY( elf_deploy_checks ) ){ - REQUIRE( fd_ulong_sat_add( vsegment_start, tot_section_sz) <= vsegment_end ); - } - - /* Require .text section */ - - REQUIRE( (info->shndx_text)>=0 ); - fd_elf64_shdr const * shdr_text = &shdr[ info->shndx_text ]; - REQUIRE( (shdr_text->sh_addr <= elf->ehdr.e_entry) - /* check that entrypoint is in text VM range */ - & (elf->ehdr.e_entry < fd_ulong_sat_add( shdr_text->sh_addr, shdr_text->sh_size ) ) ); - /* NOTE: Does NOT check that the entrypoint is in text section file - range (which may be 0 sz if SHT_NOBITS). This check is - separately done in the sBPF verifier. */ - - info->text_off = (uint)shdr_text->sh_offset; - ulong text_size = shdr_get_loaded_size( shdr_text ); - info->text_sz = text_size; - info->text_cnt = (uint) text_size / 8U; - - - /* Convert entrypoint offset to program counter */ - - info->rodata_sz = (uint)psegment_end; - info->rodata_footprint = (uint)elf_sz; - - 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; - - if( (info->shndx_dynstr)>=0 ) { - fd_elf64_shdr const * shdr_dynstr = &shdr[ info->shndx_dynstr ]; - ulong sh_offset = shdr_dynstr->sh_offset; - ulong sh_size = shdr_dynstr->sh_size; - REQUIRE( (sh_offset+sh_size>=sh_offset) & (sh_offset+sh_size<=info->rodata_footprint) ); - info->dynstr_off = (uint)sh_offset; - info->dynstr_sz = (uint)sh_size; - } - - return 0; -} - -fd_sbpf_elf_info_t * -fd_sbpf_elf_peek_old( fd_sbpf_elf_info_t * info, - void const * bin, - ulong elf_sz, - int elf_deploy_checks, - uint sbpf_min_version, - uint sbpf_max_version ) { - - /* Reject overlong ELFs (using uint addressing internally). - This is well beyond Solana's max account size of 10 MB. */ - if( FD_UNLIKELY( elf_sz>UINT_MAX ) ) - return NULL; - - fd_sbpf_elf_t const * elf = (fd_sbpf_elf_t const *)bin; - int err; - - /* Validate file header */ - if( FD_UNLIKELY( (err=fd_sbpf_check_ehdr( &elf->ehdr, elf_sz, sbpf_min_version, sbpf_max_version ))!=0 ) ) - return NULL; - - /* Program headers */ - if( FD_UNLIKELY( (err=fd_sbpf_load_phdrs( info, elf, elf_sz ))!=0 ) ) - return NULL; - - /* Section headers */ - if( FD_UNLIKELY( (err=fd_sbpf_load_shdrs( info, elf, elf_sz, elf_deploy_checks ))!=0 ) ) - return NULL; - - /* Set SBPF version from ELF e_flags */ - info->sbpf_version = sbpf_max_version ? elf->ehdr.e_flags : 0UL; - - return info; -} +#define FD_SBPF_SYMBOL_NAME_SZ_MAX (64UL) /* ELF loader, part 2 ************************************************** @@ -564,15 +130,16 @@ fd_sbpf_program_align( void ) { ulong fd_sbpf_program_footprint( fd_sbpf_elf_info_t const * info ) { FD_COMPILER_UNPREDICTABLE( info ); /* Make this appear as FD_FN_PURE (e.g. footprint might depened on info contents in future) */ - if( FD_UNLIKELY( fd_sbpf_enable_stricter_elf_headers( info->sbpf_version ) ) ) { + if( FD_UNLIKELY( fd_sbpf_enable_stricter_elf_headers_enabled( info->sbpf_version ) ) ) { /* SBPF v3+ no longer neeeds calldests bitmap */ return FD_LAYOUT_FINI( FD_LAYOUT_APPEND( FD_LAYOUT_INIT, alignof(fd_sbpf_program_t), sizeof(fd_sbpf_program_t) ), alignof(fd_sbpf_program_t) ); } + ulong pc_max = fd_ulong_max( 1UL, info->text_cnt ); return FD_LAYOUT_FINI( FD_LAYOUT_APPEND( FD_LAYOUT_APPEND( FD_LAYOUT_INIT, alignof(fd_sbpf_program_t), sizeof(fd_sbpf_program_t) ), - fd_sbpf_calldests_align(), fd_sbpf_calldests_footprint( info->rodata_sz / 8UL ) ), /* calldests bitmap */ + fd_sbpf_calldests_align(), fd_sbpf_calldests_footprint( pc_max ) ), /* calldests bitmap */ alignof(fd_sbpf_program_t) ); } @@ -591,7 +158,7 @@ fd_sbpf_program_new( void * prog_mem, return NULL; } - if( FD_UNLIKELY( ((elf_info->rodata_footprint)>0U) & (!rodata)) ) { + if( FD_UNLIKELY( ((elf_info->bin_sz)>0U) & (!rodata)) ) { FD_LOG_WARNING(( "NULL rodata" )); return NULL; } @@ -607,24 +174,27 @@ fd_sbpf_program_new( void * prog_mem, FD_SCRATCH_ALLOC_INIT( laddr, prog_mem ); fd_sbpf_program_t * prog = FD_SCRATCH_ALLOC_APPEND( laddr, alignof(fd_sbpf_program_t), sizeof(fd_sbpf_program_t) ); + /* Note that entry_pc and rodata_sz get set during the loading phase. */ *prog = (fd_sbpf_program_t) { .info = *elf_info, .rodata = rodata, - .rodata_sz = elf_info->rodata_sz, + .rodata_sz = 0UL, .text = (ulong *)((ulong)rodata + elf_info->text_off), /* FIXME: WHAT IF MISALIGNED */ .text_off = elf_info->text_off, .text_cnt = elf_info->text_cnt, .text_sz = elf_info->text_sz, - .entry_pc = elf_info->entry_pc + .entry_pc = ULONG_MAX, }; - if( FD_UNLIKELY( fd_sbpf_enable_stricter_elf_headers( elf_info->sbpf_version ) ) ) { + if( FD_UNLIKELY( fd_sbpf_enable_stricter_elf_headers_enabled( elf_info->sbpf_version ) ) ) { /* No calldests map in SBPF v3+ */ prog->calldests_shmem = NULL; prog->calldests = NULL; } else { - /* Initialize calldests map */ - ulong pc_max = elf_info->rodata_sz / 8UL; + /* Initialize calldests map. The text section may be empty, so we + should initialize the calldests set with at least 1 element + of capacity (0-sized calldests is UB). */ + ulong pc_max = fd_ulong_max( 1UL, elf_info->text_cnt ); prog->calldests_shmem = fd_sbpf_calldests_new( FD_SCRATCH_ALLOC_APPEND( laddr, fd_sbpf_calldests_align(), fd_sbpf_calldests_footprint( pc_max ) ), @@ -644,156 +214,124 @@ fd_sbpf_program_delete( fd_sbpf_program_t * mem ) { return (void *)mem; } -/* fd_sbpf_loader_t contains various temporary state during loading */ +/* fd_sbpf_loader_t contains various temporary state during loading. */ struct fd_sbpf_loader { /* External objects */ - ulong * calldests; /* owned by program */ - fd_sbpf_syscalls_t * syscalls; /* owned by caller */ - - /* Dynamic table */ - uint dyn_off; /* File offset of dynamic table (UINT_MAX=missing) */ - uint dyn_cnt; /* Number of dynamic table entries */ - - /* Dynamic table entries */ - ulong dt_rel; - ulong dt_relent; - ulong dt_relsz; - ulong dt_symtab; - - /* Dynamic symbols */ - uint dynsym_off; /* File offset of .dynsym section (0=missing) */ - uint dynsym_cnt; /* Symbol count */ - - int elf_deploy_checks; + ulong * calldests; /* owned by program */ + fd_sbpf_syscalls_t * syscalls; /* owned by caller */ }; typedef struct fd_sbpf_loader fd_sbpf_loader_t; -/* FD_SBPF_SYM_NAME_SZ_MAX is the maximum length of a symbol name cstr - including zero terminator. - https://github.com/solana-labs/rbpf/blob/c168a8715da668a71584ea46696d85f25c8918f6/src/elf_parser/mod.rs#L13 */ -#define FD_SBPF_SYM_NAME_SZ_MAX (64UL) - - -static int -fd_sbpf_find_dynamic( fd_sbpf_loader_t * loader, - fd_sbpf_elf_t const * elf, - ulong elf_sz, - fd_sbpf_elf_info_t const * info ) { - - fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff ); - fd_elf64_phdr const * phdrs = (fd_elf64_phdr const *)( elf->bin + elf->ehdr.e_phoff ); - - /* Try first PT_DYNAMIC in program header table */ - - if( (info->phndx_dyn)>=0 ) { - ulong dyn_off = phdrs[ info->phndx_dyn ].p_offset; - ulong dyn_sz = phdrs[ info->phndx_dyn ].p_filesz; - ulong dyn_end = dyn_off+dyn_sz; - - /* Fall through to SHT_DYNAMIC if invalid */ - - if( FD_LIKELY( ( dyn_end>=dyn_off ) /* overflow */ - & ( dyn_end<=elf_sz ) /* out of bounds */ - & fd_ulong_is_aligned( dyn_off, 8UL ) /* misaligned */ - & fd_ulong_is_aligned( dyn_sz, sizeof(fd_elf64_dyn) ) /* misaligned sz */ ) ) { - loader->dyn_off = (uint)dyn_off; - loader->dyn_cnt = (uint)(dyn_sz / sizeof(fd_elf64_dyn)); - return 0; - } +/* Queries a single string from a section which is marked as SHT_STRTAB. + Returns an ElfParserError on failure. On success, returns 0 and + writes the queried string into out_str, which should be a + pre-allocated buffer of at least maximum_length bytes. The resulting + string is guaranteed to be null-terminated on success. + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L467-L496 */ +int +fd_sbpf_lenient_get_string_in_section( void const * elf_bytes, + ulong elf_bytes_len, + fd_elf64_shdr const * section_header, + uint offset_in_section, + ulong maximum_length, + char * out_str ) { + /* This could be checked only once outside the loop, but to keep the code the same... + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L474-L476 */ + if( FD_UNLIKELY( section_header->sh_type!=FD_ELF_SHT_STRTAB ) ) { + return FD_SBPF_ELF_PARSER_ERR_INVALID_SECTION_HEADER; } - /* Try first SHT_DYNAMIC in section header table */ - - if( (info->shndx_dyn)>0 ) { - ulong dyn_off = shdrs[ info->shndx_dyn ].sh_offset; - ulong dyn_sz = shdrs[ info->shndx_dyn ].sh_size; - ulong dyn_end = dyn_off+dyn_sz; - - /* This time, don't tolerate errors */ - - REQUIRE( ( dyn_end>=dyn_off ) /* overflow */ - & ( dyn_end<=elf_sz ) /* out of bounds */ - & fd_ulong_is_aligned( dyn_off, 8UL ) /* misaligned */ - & fd_ulong_is_aligned( dyn_sz, sizeof(fd_elf64_dyn) ) /* misaligned sz */ ); + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L477-L482 */ + ulong offset_in_file = section_header->sh_offset+(ulong)offset_in_section; /* can't overflow */ + ulong string_range_start = offset_in_file; + ulong string_range_end = fd_ulong_min( section_header->sh_offset+section_header->sh_size, offset_in_file+maximum_length ); + if( FD_UNLIKELY( string_range_end>elf_bytes_len ) ) { + return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS; + } + /* In rust vec.get([n..n]) returns [], so this is accepted. + vec.get([n..m]) with mdyn_off = (uint)dyn_off; - loader->dyn_cnt = (uint)(dyn_sz / sizeof(fd_elf64_dyn)); - return 0; + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L486-L495 */ + if( FD_UNLIKELY( memchr( (uchar const *)elf_bytes+string_range_start, 0, string_range_end-string_range_start )==NULL ) ) { + return FD_SBPF_ELF_PARSER_ERR_STRING_TOO_LONG; } - /* Missing or invalid PT_DYNAMIC and missing SHT_DYNAMIC, skip. */ - return 0; + /* Write the string to the output buffer. */ + memcpy( out_str, (uchar const *)elf_bytes+string_range_start, string_range_end-string_range_start ); + return FD_SBPF_ELF_SUCCESS; } +/* Registers a target PC into the calldests function registry. Returns + 0 on success, inserts the target PC into the calldests, and sets + *opt_out_pc_hash to murmur3_32(target_pc) (if opt_out_pc_hash is + non-NULL). Returns FD_SBPF_ELF_ERR_SYMBOL_HASH_COLLISION on failure + if the target PC is already in the syscalls registry and leaves + out_pc_hash in an undefined state. + + An important note is that Agave's implementation uses a map to store + key-value pairs of (murmur3_32(target_pc), target_pc) within the + calldests. We optimize this by using a set containing + target_pc (this is our calldests map), and then deriving + the target PC on the fly given murmur3_32(target_pc) (provided as + imm) in the VM by computing the inverse hash (since murmur3_32 is + bijective for uints). + + Another important note is that if a key-value pair already exists in + Agave's calldests map, they will only throw a symbol hash collision + error if the target PC is different from the one already registered. + We can omit this check because of the hash function's bijective + property, since the key-value pairs are deterministically derived + from one another. + + TODO: this function will have to be adapted to hash the target PC + depending on the SBPF version (>= V3). That has not been implemented + yet. + + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/program.rs#L142-L178 */ static int -fd_sbpf_load_dynamic( fd_sbpf_loader_t * loader, - fd_sbpf_elf_t const * elf, - ulong elf_sz ) { - - fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff ); - - /* Skip if no dynamic table was found */ - - if( !loader->dyn_cnt ) return 0; - - /* Walk dynamic table */ - - fd_elf64_dyn const * dyn = (fd_elf64_dyn const *)( elf->bin + loader->dyn_off ); - ulong const dyn_cnt = loader->dyn_cnt; - - for( ulong i=0; idt_rel =d_val; break; - case FD_ELF_DT_RELENT: loader->dt_relent=d_val; break; - case FD_ELF_DT_RELSZ: loader->dt_relsz =d_val; break; - case FD_ELF_DT_SYMTAB: loader->dt_symtab=d_val; break; - } - } - - /* Load dynamic symbol table */ - - if( loader->dt_symtab ) { - /* Search for dynamic symbol table - FIXME unfortunate bounded O(n^2) -- could convert to binary search */ - - /* FIXME this could be clobbered by relocations, causing strict - aliasing violations */ - - fd_elf64_shdr const * shdr_dynsym = NULL; - - for( ulong i=0; iehdr.e_shnum; i++ ) { - if( shdrs[ i ].sh_addr == loader->dt_symtab ) { - /* TODO: verify this ... */ - /* Check section type */ - uint sh_type = shdrs[ i ].sh_type; - // https://github.com/solana-labs/rbpf/blob/v0.8.5/src/elf_parser/mod.rs#L500 - REQUIRE( (sh_type==FD_ELF_SHT_SYMTAB) | (sh_type==FD_ELF_SHT_DYNSYM) ); - - shdr_dynsym = &shdrs[ i ]; - break; - } +fd_sbpf_register_function_hashed_legacy( fd_sbpf_loader_t * loader, + fd_sbpf_program_t * prog, + char const * name, + ulong target_pc, + uint * opt_out_pc_hash ) { + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/program.rs#L156-L160 */ + uint pc_hash; + uchar is_entrypoint = strcmp( name, "entrypoint" )==0 || + target_pc==FD_SBPF_ENTRYPOINT_PC; + if( FD_UNLIKELY( is_entrypoint ) ) { + if( FD_UNLIKELY( prog->entry_pc!=ULONG_MAX && prog->entry_pc!=target_pc ) ) { + /* We already registered the entrypoint to a different target PC, + so we cannot register it again. */ + return FD_SBPF_ELF_ERR_SYMBOL_HASH_COLLISION; } - REQUIRE( shdr_dynsym ); + prog->entry_pc = target_pc; - /* Check if out of bounds or misaligned */ + /* Optimization for this constant value */ + pc_hash = FD_SBPF_ENTRYPOINT_HASH; + } else { + pc_hash = fd_pchash( (uint)target_pc ); + } - ulong sh_offset = shdr_dynsym->sh_offset; - ulong sh_size = shdr_dynsym->sh_size; + /* loader.get_function_registry() is their equivalent of our syscalls + registry. Fail if the target PC is present there. - REQUIRE( ( sh_offset+sh_size>=sh_offset ) - & ( sh_offset+sh_size<=elf_sz ) - & ( fd_ulong_is_aligned( sh_offset, 8UL ) ) - & ( sh_size % sizeof (fd_elf64_sym) == 0UL ) ); + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/program.rs#L161-L163 */ + if( FD_UNLIKELY( fd_sbpf_syscalls_query( loader->syscalls, pc_hash, NULL ) ) ) { + return FD_SBPF_ELF_ERR_SYMBOL_HASH_COLLISION; + } - loader->dynsym_off = (uint)sh_offset; - loader->dynsym_cnt = (uint)(sh_size/sizeof(fd_elf64_sym)); + /* Insert the target PC into the calldests set if it's not the + entrypoint. */ + if( FD_LIKELY( !is_entrypoint ) ) { + fd_sbpf_calldests_insert( loader->calldests, target_pc ); } - return 0; + if( opt_out_pc_hash ) *opt_out_pc_hash = pc_hash; + return FD_SBPF_ELF_SUCCESS; } /* ELF Dynamic Relocations ********************************************* @@ -852,497 +390,304 @@ fd_sbpf_load_dynamic( fd_sbpf_loader_t * loader, first immediate field, high 32 bits in second immediate field) Bits 0..32 32..64 64..96 96..128 - [ ... ] [ IMM_LO ] [ ... ] [ IMM_HI ] */ + [ ... ] [ IMM_LO ] [ ... ] [ IMM_HI ] + + Returns 0 on success and writes the imm offset to the rodata. + Returns the error code on failure. + + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1069-L1141 */ static int -fd_sbpf_r_bpf_64_64( fd_sbpf_loader_t const * loader, - fd_sbpf_elf_t const * elf, +fd_sbpf_r_bpf_64_64( fd_sbpf_elf_t const * elf, ulong elf_sz, uchar * rodata, fd_sbpf_elf_info_t const * info, - fd_elf64_rel const * rel ) { - - (void)info; - - uint r_sym = FD_ELF64_R_SYM( rel->r_info ); - ulong r_offset = rel->r_offset; - - /* Bounds check */ - REQUIRE( ( r_offset+16UL> r_offset ) - & ( r_offset+16UL<=elf_sz ) ); + fd_elf64_rel const * dt_rel, + ulong r_offset ) { - /* Offsets of implicit addend (immediate fields) */ - ulong A_off_lo = r_offset+ 4UL; - ulong A_off_hi = r_offset+12UL; - - /* Read implicit addend (imm field of first insn slot) */ - // SBF_V2: ulong A_off = is_text ? r_offset+4UL : r_offset; - REQUIRE( A_off_lo+4ULdynsym_cnt ); - fd_elf64_sym const * dynsyms = (fd_elf64_sym const *)( elf->bin + loader->dynsym_off ); - fd_elf64_sym const * sym = &dynsyms[ r_sym ]; - ulong S = sym->st_value; - - /* Relocate */ - ulong A = FD_LOAD( uint, &rodata[ A_off_lo ] ); - ulong V = fd_ulong_sat_add( S, A ); - if( Vbin + elf->ehdr.e_shoff ); - /* Write back */ - FD_STORE( uint, &rodata[ A_off_lo ], (uint)(V ) ); - FD_STORE( uint, &rodata[ A_off_hi ], (uint)(V>>32UL) ); + /* Note that the sbpf_version variable is ALWAYS V0 (see Agave's code + to understand why). + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1070-L1080 */ + ulong imm_offset = fd_ulong_sat_add( r_offset, 4UL /* BYTE_OFFSET_IMMEDIATE */ ); - return 0; -} + /* Bounds check. + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1084-L1086 */ + if( FD_UNLIKELY( fd_ulong_sat_add( imm_offset, 4UL /* BYTE_LENGTH_IMMEDIATE */ )>elf_sz ) ) { + return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS; + } -/* R_BPF_64_RELATIVE is almost entirely Solana specific. */ + /* Get the symbol entry from the dynamic symbol table. + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1089-L1092 */ + fd_elf64_sym const * symbol = NULL; + { + /* Ensure the dynamic symbol table exists. */ + if( FD_UNLIKELY( info->shndx_dynsymtab<0 ) ) { + return FD_SBPF_ELF_ERR_UNKNOWN_SYMBOL; + } -static int -fd_sbpf_r_bpf_64_relative( fd_sbpf_elf_t const * elf, - ulong elf_sz, - uchar * rodata, - fd_sbpf_elf_info_t const * info, - fd_elf64_rel const * rel ) { - - ulong r_offset = rel->r_offset; - - /* Is reloc target in .text section? */ - fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff ); - fd_elf64_shdr const * shdr_text = &shdrs[ info->shndx_text ]; - int is_text = ( ( r_offset >= shdr_text->sh_offset ) & - ( r_offset < shdr_text->sh_offset + - shdr_get_loaded_size( shdr_text ) ) ); - - if( is_text ) { - /* If reloc target is in .text, behave like R_BPF_64_64, except: - - R_SYM(r_info) is ignored - - If implicit addend looks like a physical address, make it - a virtual address (by adding a constant offset) - - This relocation type seems to make little sense but is required - for most programs. */ - - REQUIRE( (r_offset+16UL>r_offset) & (r_offset+16UL<=elf_sz) ); - ulong imm_lo_off = r_offset+ 4UL; - ulong imm_hi_off = r_offset+12UL; - - /* Read implicit addend */ - uint va_lo = FD_LOAD( uint, rodata+imm_lo_off ); - uint va_hi = FD_LOAD( uint, rodata+imm_hi_off ); - ulong va = ( (ulong)va_hi<<32UL ) | va_lo; - - REQUIRE( va!=0UL ); - va = va>32UL ) ); - } else { - /* Outside .text do a 64-bit write */ + /* Get the dynamic symbol table section header. The section header + was already validated in fd_sbpf_lenient_elf_parse() so we can + directly get the symbol table. */ + fd_elf64_shdr const * sh_dynsym = &shdrs[ info->shndx_dynsymtab ]; + fd_elf64_sym const * dynsym_table = (fd_elf64_sym const *)( elf->bin + sh_dynsym->sh_offset ); + ulong dynsym_cnt = (ulong)(sh_dynsym->sh_size / sizeof(fd_elf64_sym)); + + /* The symbol table index is stored in the lower 4 bytes of r_info. + Check the bounds of the symbol table index. */ + ulong r_sym = FD_ELF64_R_SYM( dt_rel->r_info ); + if( FD_UNLIKELY( r_sym>=dynsym_cnt ) ) { + return FD_SBPF_ELF_ERR_UNKNOWN_SYMBOL; + } + symbol = &dynsym_table[ r_sym ]; + } - /* Bounds checks */ - REQUIRE( (r_offset+8UL>r_offset) & (r_offset+8UL<=elf_sz) ); + /* Use the relative address as an offset to derive the relocated + address. + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1094-L1096 */ + uint refd_addr = FD_LOAD( uint, &rodata[ imm_offset ] ); + ulong addr = fd_ulong_sat_add( symbol->st_value, refd_addr ); + + /* We need to normalize the address into the VM's memory space, which + is rooted at 0x1_0000_0000 (the program ro-data region). If the + linker hasn't normalized the addresses already, we treat addr as + a relative offset into the program ro-data region. */ + if( addrelf_sz ) ) { + return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS; + } /* Write back */ - FD_STORE( ulong, rodata+r_offset, va ); + FD_STORE( uint, rodata+imm_low_offset, (uint)addr ); } - return 0; -} - -static int -fd_sbpf_r_bpf_64_32( fd_sbpf_loader_t const * loader, - fd_sbpf_elf_t const * elf, - ulong elf_sz, - uchar * rodata, - fd_sbpf_elf_info_t const * info, - fd_elf64_rel const * rel ) { - - uint r_sym = FD_ELF64_R_SYM( rel->r_info ); - ulong r_offset = rel->r_offset; - - /* Lookup symbol */ - REQUIRE( r_sym < loader->dynsym_cnt ); - fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff ); - fd_elf64_sym const * dynsyms = (fd_elf64_sym const *)( elf->bin + loader->dynsym_off ); - fd_elf64_sym const * sym = &dynsyms[ r_sym ]; - ulong S = sym->st_value; - - /* Verify .dynstr (TODO can we lift this out of the reloc handler?) */ - REQUIRE( info->shndx_dynstr > 0 ); - REQUIRE( shdrs[ info->shndx_dynstr ].sh_type == FD_ELF_SHT_STRTAB ); - - /* Verify symbol name */ - ulong name_len; - char const * name = check_cstr( elf->bin + info->dynstr_off, info->dynstr_sz, sym->st_name, FD_SBPF_SYM_NAME_SZ_MAX-1UL, &name_len ); - REQUIRE( name ); - - /* Value to write into relocated field */ - uint V; - - int is_func_call = ( FD_ELF64_ST_TYPE( sym->st_info ) == FD_ELF_STT_FUNC ) - & ( S!=0UL ); - if( is_func_call ) { - /* Check whether function call is in virtual memory range of text section. */ - fd_elf64_shdr const * shdr_text = &shdrs[ info->shndx_text ]; - ulong sh_addr = shdr_text->sh_addr; - ulong sh_size = shdr_text->sh_size; - REQUIRE( (S>=sh_addr) & (S= 10UL && 0==strncmp( name, "entrypoint", name_len ) ) { - /* Skip insertion of "entrypoint" relocation entries to calldests. This - emulates Solana/Agave's behavior of unregistering these entries before - registering the entrypoint manually. - Entrypoint is registered in fd_sbpf_program_load. - Hash is still applied. */ - hash = 0x71e3cf81; - } else { - hash = fd_pchash( (uint)target_pc ); - if( FD_LIKELY( target_pc < (info->rodata_sz / 8UL ) ) ) - fd_sbpf_calldests_insert( loader->calldests, target_pc ); - } - - /* Check for collision with syscall ID - https://github.com/solana-labs/rbpf/blob/57139e9e1fca4f01155f7d99bc55cdcc25b0bc04/src/program.rs#L142-L146 */ - REQUIRE( !fd_sbpf_syscalls_query( loader->syscalls, (ulong)hash, NULL ) ); - V = (uint)hash; - } else { - /* FIXME Should cache Murmur hashes. - If max ELF size is 10MB, can fit about 640k relocs. - Each reloc could point to a symbol with the same st_name, - which results in 640MB hash input data without caching. */ - uint hash = fd_murmur3_32( name, name_len, 0UL ); - /* Ensure that requested syscall ID exists only when deploying - https://github.com/solana-labs/rbpf/blob/v0.8.0/src/elf.rs#L1097 */ - if ( FD_UNLIKELY( loader->elf_deploy_checks ) ) { - REQUIRE( fd_sbpf_syscalls_query( loader->syscalls, (ulong)hash, NULL ) ); + /* Same as above, but for the imm high offset. + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1125-L1134 */ + { + /* Bounds check before writing to the rodata. */ + if( FD_UNLIKELY( fd_ulong_sat_add( imm_high_offset, 4UL /* BYTE_LENGTH_IMMEDIATE */ )>elf_sz ) ) { + return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS; } - V = hash; + /* Write back */ + FD_STORE( uint, rodata+imm_high_offset, (uint)(addr>>32UL) ); } - /* Bounds checks */ - REQUIRE( (r_offset+8UL>r_offset) & (r_offset+8UL<=elf_sz) ); - ulong A_off = r_offset+4UL; - - /* Apply relocation */ - FD_STORE( uint, rodata+A_off, V ); - - return 0; -} + /* ...rest of this function is a no-op because + enable_symbol_and_section_labels is disabled in production. */ -static int -fd_sbpf_apply_reloc( fd_sbpf_loader_t const * loader, - fd_sbpf_elf_t const * elf, - ulong elf_sz, - uchar * rodata, - fd_sbpf_elf_info_t const * info, - fd_elf64_rel const * rel ) { - switch( FD_ELF64_R_TYPE( rel->r_info ) ) { - case FD_ELF_R_BPF_64_64: - return fd_sbpf_r_bpf_64_64 ( loader, elf, elf_sz, rodata, info, rel ); - case FD_ELF_R_BPF_64_RELATIVE: - return fd_sbpf_r_bpf_64_relative( elf, elf_sz, rodata, info, rel ); - case FD_ELF_R_BPF_64_32: - return fd_sbpf_r_bpf_64_32 ( loader, elf, elf_sz, rodata, info, rel ); - default: - ERR( FD_SBPF_ERR_INVALID_ELF ); - } + return FD_SBPF_ELF_SUCCESS; } -/* fd_sbpf_hash_calls converts local call instructions in the "LLVM - form" (immediate is a program counter offset) to eBPF form (immediate - is a hash of the target program counter). Corresponds to - fixup_relative calls in solana-labs/rbpf. +/* R_BPF_64_RELATIVE is almost entirely Solana specific. Returns 0 on + success and an ElfError on failure. - Assumes that the text section range exists, is within bounds, and - does not overlap with the ELF file header, program header table, or - section header table. */ + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1142-L1247 */ static int -fd_sbpf_hash_calls( fd_sbpf_loader_t * loader, - fd_sbpf_program_t * prog, - fd_sbpf_elf_t const * elf ) { - - fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff ); - fd_sbpf_elf_info_t * info = &prog->info; - uchar * rodata = prog->rodata; - - fd_elf64_shdr const * shtext = &shdrs[ info->shndx_text ]; - fd_sbpf_calldests_t * calldests = loader->calldests; - - uchar * ptr = rodata + shtext->sh_offset; - ulong insn_cnt = shdr_get_loaded_size( shtext ) / 8UL; - - for( ulong i=0; i> 32UL); - if( (opc!=0x85) | (imm==-1) ) - continue; + fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff ); + fd_elf64_shdr const * sh_text = &shdrs[ info->shndx_text ]; - /* Mark function call destination */ - long target_pc_s; - REQUIRE( 0==__builtin_saddl_overflow( (long)i+1L, imm, &target_pc_s ) ); - ulong target_pc = (ulong)target_pc_s; - REQUIRE( target_pcsyscalls, (ulong)pc_hash, NULL ) ); + /* We are relocating a lddw (load double word) instruction which + spans two instruction slots. The address top be relocated is + split in two halves in the two imms of the instruction slots. - FD_STORE( uint, ptr+4UL, pc_hash ); - } + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1159-L1162 */ + ulong imm_low_offset = imm_offset; + ulong imm_high_offset = fd_ulong_sat_add( r_offset, + 4UL /* BYTE_OFFSET_IMMEDIATE */ + 8UL /* INSN_SIZE */ ); - return 0; -} + /* Read the low side of the address. Perform a bounds check first. + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1164-L1171 */ + if( FD_UNLIKELY( fd_ulong_sat_add( imm_low_offset, 4UL /* BYTE_LENGTH_IMMEDIATE */ )>elf_sz ) ) { + return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS; + } + uint va_low = FD_LOAD( uint, rodata+imm_low_offset ); -static int -fd_sbpf_relocate( fd_sbpf_loader_t const * loader, - fd_sbpf_elf_t const * elf, - ulong elf_sz, - uchar * rodata, - fd_sbpf_elf_info_t const * info ) { - - ulong const dt_rel = loader->dt_rel; - ulong const dt_relent = loader->dt_relent; - ulong const dt_relsz = loader->dt_relsz; - - /* Skip relocation if DT_REL is missing */ - - if( dt_rel == 0UL ) return 0; - - /* Validate reloc table params */ - - REQUIRE( dt_relent==sizeof(fd_elf64_rel) ); - REQUIRE( dt_relsz !=0UL ); - REQUIRE( (dt_relsz % sizeof(fd_elf64_rel))==0UL ); - - /* Resolve DT_REL virtual address to file offset - First, attempt to find segment containing DT_REL */ - - ulong rel_off = ULONG_MAX; - - fd_elf64_phdr const * phdrs = (fd_elf64_phdr const *)( elf->bin + elf->ehdr.e_phoff ); - ulong rel_phnum; - for( rel_phnum=0; rel_phnum < elf->ehdr.e_phnum; rel_phnum++ ) { - ulong va_lo = phdrs[ rel_phnum ].p_vaddr; - ulong va_hi = phdrs[ rel_phnum ].p_memsz + va_lo; - REQUIRE( va_hi>=va_lo ); - if( (dt_rel>=va_lo) & (dt_rel=va_off) - & (pa_lo < elf_sz) ); - rel_off = pa_lo; - break; + /* Read the high side of the address. Perform a bounds check first. + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1174-L1180 */ + if( FD_UNLIKELY( fd_ulong_sat_add( imm_high_offset, 4UL /* BYTE_LENGTH_IMMEDIATE */ )>elf_sz ) ) { + return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS; } - } + uint va_high = FD_LOAD( uint, rodata+imm_high_offset ); - /* DT_REL not contained in any segment. Fallback to section header - table for finding first dynamic reloc section. */ + /* Put the address back together. + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1182-L1187 */ + ulong refd_addr = ( (ulong)va_high<<32UL ) | va_low; + if( FD_UNLIKELY( refd_addr==0UL ) ) { + return FD_SBPF_ELF_ERR_INVALID_VIRTUAL_ADDRESS; + } - if( rel_phnum == elf->ehdr.e_phnum ) { - fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff ); - ulong rel_shnum; - for( rel_shnum=0; rel_shnum < elf->ehdr.e_shnum; rel_shnum++ ) - if( shdrs[ rel_shnum ].sh_addr==dt_rel ) - break; - REQUIRE( rel_shnum < elf->ehdr.e_shnum ); - rel_off = shdrs[ rel_shnum ].sh_offset; - } + /* We need to normalize the address into the VM's memory space, which + is rooted at 0x1_0000_0000 (the program ro-data region). If the + linker hasn't normalized the addresses already, we treat addr as + a relative offset into the program ro-data region. + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1189-L1193 */ + if( refd_addrelf_sz ) ) { + return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS; + } + FD_STORE( uint, rodata+imm_low_offset, (uint)refd_addr ); - /* Load section and reloc tables - Assume section header already validated at this point */ + /* Write back the high half. Perform a bounds check first. + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1205-L1214 */ + if( FD_UNLIKELY( fd_ulong_sat_add( imm_high_offset, 4UL /* BYTE_LENGTH_IMMEDIATE */ )>elf_sz ) ) { + return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS; + } + FD_STORE( uint, rodata+imm_high_offset, (uint)(refd_addr>>32UL) ); + } else { + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1216-L1228 */ + ulong refd_addr = 0UL; - fd_elf64_rel const * rel = (fd_elf64_rel const *)( elf->bin + rel_off ); - ulong rel_cnt = dt_relsz/sizeof(fd_elf64_rel); + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1230-L1239 */ + if( FD_UNLIKELY( fd_ulong_sat_add( r_offset, 4UL /* BYTE_LENGTH_IMMEDIATE */ )>elf_sz ) ) { + return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS; + } + refd_addr = FD_LOAD( uint, rodata+imm_offset ); + refd_addr = fd_ulong_sat_add( refd_addr, FD_SBPF_MM_PROGRAM_ADDR ); - /* Apply each reloc */ + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1242-L1245 */ + if( FD_UNLIKELY( fd_ulong_sat_add( r_offset, sizeof(ulong) )>elf_sz ) ) { + return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS; + } - for( ulong i=0; ibin + elf->ehdr.e_shoff ); - - /* memset gaps between sections to zero. - Assume section sh_addrs are monotonically increasing. - Assume section virtual address ranges equal physical address ranges. - Assume ranges are not overflowing. */ - /* FIXME match Solana more closely here */ - - ulong cursor = 0UL; - for( ulong i=0; iehdr.e_shnum; i++ ) { - if( !( info->loaded_sections[ i>>6UL ] & (1UL<<(i&63UL)) ) ) continue; - - fd_elf64_shdr const * shdr = &shdrs[ i ]; - - /* NOBITS sections are included in rodata, but may have invalid - offsets, thus we can't trust the shdr->sh_offset field. */ - if( FD_UNLIKELY( shdr->sh_type==FD_ELF_SHT_NOBITS ) ) continue; - - ulong off = shdr->sh_offset; - ulong sz = shdr->sh_size; - assert( cursor<=off ); /* Invariant: Monotonically increasing offsets */ - assert( off+sz>=off ); /* Invariant: No integer overflow */ - assert( off+sz<=info->rodata_sz ); /* Invariant: No buffer overflow */ - - /* Fill gap with zeros */ - ulong gap = off - cursor; - fd_memset( rodata+cursor, 0, gap ); - - cursor = off+sz; +fd_sbpf_r_bpf_64_32( fd_sbpf_loader_t * loader, + fd_sbpf_program_t * prog, + fd_sbpf_elf_t const * elf, + ulong elf_sz, + uchar * rodata, + fd_sbpf_elf_info_t const * info, + fd_elf64_rel const * dt_rel, + ulong r_offset, + fd_sbpf_loader_config_t const * config ) { + + fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff ); + fd_elf64_shdr const * sh_text = &shdrs[ info->shndx_text ]; + fd_elf64_shdr const * dyn_section_names_shdr = &shdrs[ info->shndx_dynstr ]; + + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1253-L1254 */ + ulong imm_offset = fd_ulong_sat_add( r_offset, 4UL /* BYTE_OFFSET_IMMEDIATE */ ); + + /* Get the symbol entry from the dynamic symbol table. + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1256-L1259 */ + fd_elf64_sym const * symbol = NULL; + + /* Ensure the dynamic symbol table exists. */ + if( FD_UNLIKELY( info->shndx_dynsymtab<0 ) ) { + return FD_SBPF_ELF_ERR_UNKNOWN_SYMBOL; } - fd_memset( rodata+cursor, 0, info->rodata_sz - cursor ); - - return 0; -} - -int -fd_sbpf_program_load_old( fd_sbpf_program_t * prog, - void const * _bin, - ulong elf_sz, - fd_sbpf_syscalls_t * syscalls, - int elf_deploy_checks ) { - fd_sbpf_loader_seterr( 0, 0 ); - - int err; - fd_sbpf_elf_t * elf = (fd_sbpf_elf_t *)_bin; - - fd_sbpf_loader_t loader = { - .calldests = prog->calldests, - .syscalls = syscalls, - - .dyn_off = 0U, - .dyn_cnt = 0U, - - .dt_rel = 0UL, - .dt_relent = 0UL, - .dt_relsz = 0UL, - .dt_symtab = 0UL, - - .dynsym_off = 0U, - .dynsym_cnt = 0U, - .elf_deploy_checks = elf_deploy_checks - }; - - /* Find dynamic section */ - if( FD_UNLIKELY( (err=fd_sbpf_find_dynamic( &loader, elf, elf_sz, &prog->info ))!=0 ) ) - return err; - - /* Load dynamic section */ - if( FD_UNLIKELY( (err=fd_sbpf_load_dynamic( &loader, elf, elf_sz ))!=0 ) ) - return err; - - /* Register entrypoint to calldests. */ - fd_sbpf_calldests_insert( prog->calldests, prog->entry_pc ); - - /* Copy rodata segment */ - fd_memcpy( prog->rodata, elf->bin, prog->info.rodata_footprint ); - - /* Convert calls with PC relative immediate to hashes */ - if( FD_UNLIKELY( (err=fd_sbpf_hash_calls ( &loader, prog, elf ))!=0 ) ) - return err; - - /* Apply relocations */ - if( FD_UNLIKELY( (err=fd_sbpf_relocate ( &loader, elf, elf_sz, prog->rodata, &prog->info ))!=0 ) ) - return err; - - /* Create read-only segment */ - if( FD_UNLIKELY( (err=fd_sbpf_zero_rodata( elf, prog->rodata, &prog->info ))!=0 ) ) - return err; + /* Get the dynamic symbol table section header. The section header + was already validated in fd_sbpf_lenient_elf_parse() so we can + directly get the symbol table. */ + fd_elf64_shdr const * sh_dynsym = &shdrs[ info->shndx_dynsymtab ]; + fd_elf64_sym const * dynsym_table = (fd_elf64_sym const *)( elf->bin + sh_dynsym->sh_offset ); + ulong dynsym_cnt = (ulong)(sh_dynsym->sh_size / sizeof(fd_elf64_sym)); + + /* The symbol table index is stored in the lower 4 bytes of r_info. + Check the bounds of the symbol table index. */ + ulong r_sym = FD_ELF64_R_SYM( dt_rel->r_info ); + if( FD_UNLIKELY( r_sym>=dynsym_cnt ) ) { + return FD_SBPF_ELF_ERR_UNKNOWN_SYMBOL; + } + symbol = &dynsym_table[ r_sym ]; - return 0; -} + /* Verify symbol name. + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1261-L1263 */ + char name[ FD_SBPF_SYMBOL_NAME_SZ_MAX ]; + if( FD_UNLIKELY( fd_sbpf_lenient_get_string_in_section( elf, elf_sz, dyn_section_names_shdr, symbol->st_name, FD_SBPF_SYMBOL_NAME_SZ_MAX, name ) ) ) { + return FD_SBPF_ELF_ERR_UNKNOWN_SYMBOL; + } -int -fd_sbpf_program_get_sbpf_version_or_err( void const * bin, - ulong bin_sz, - fd_sbpf_loader_config_t const * config ) { - /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L376-L381 */ - const ulong E_FLAGS_OFFSET = 48UL; - const uint E_FLAGS_SBPF_V2 = 0x20; + /* If the symbol is defined, this is a bpf-to-bpf call. + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1265-L1295 */ + uint key = 0U; + int symbol_is_function = ( FD_ELF64_ST_TYPE( symbol->st_info )==FD_ELF_STT_FUNC ); + { + if( symbol_is_function && symbol->st_value!=0UL ) { + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1267-L1269 */ + fd_sbpf_range_t text_section_range = (fd_sbpf_range_t) { + .lo = sh_text->sh_addr, + .hi = fd_ulong_sat_add( sh_text->sh_addr, sh_text->sh_size ) }; + if( FD_UNLIKELY( !fd_sbpf_range_contains( &text_section_range, symbol->st_value ) ) ) { + return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS; + } - if( FD_UNLIKELY( bin_sz < E_FLAGS_OFFSET+sizeof(uint) ) ) { - return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS; + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1270-L1279 */ + ulong target_pc = fd_ulong_sat_sub( symbol->st_value, sh_text->sh_addr ) / 8UL; + int err = fd_sbpf_register_function_hashed_legacy( loader, prog, name, target_pc, &key ); + if( FD_UNLIKELY( err!=FD_SBPF_ELF_SUCCESS ) ) { + return err; + } + } else { + /* Else, it's a syscall. Ensure that the syscall can be resolved. + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1281-L1294 */ + key = fd_murmur3_32(name, strlen( name ), 0UL ); + if( FD_UNLIKELY( config->reject_broken_elfs && + fd_sbpf_syscalls_query( loader->syscalls, key, NULL )==NULL ) ) { + return FD_SBPF_ELF_ERR_UNRESOLVED_SYMBOL; + } + } } - uint e_flags = fd_uint_load_4( (uchar const *)bin + E_FLAGS_OFFSET ); - uint sbpf_version = 0U; - if( FD_UNLIKELY( config->sbpf_max_version==FD_SBPF_V0 ) ) { - /* https://github.com/anza-xyz/sbpf/blob/main/src/elf.rs#L384-L388 */ - sbpf_version = e_flags==E_FLAGS_SBPF_V2 ? FD_SBPF_RESERVED : FD_SBPF_V0; - } else { - /* https://github.com/anza-xyz/sbpf/blob/main/src/elf.rs#L390-L396 */ - sbpf_version = e_flags < FD_SBPF_VERSION_COUNT ? e_flags : FD_SBPF_RESERVED; + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1297-L1300 */ + if( FD_UNLIKELY( fd_ulong_sat_add( imm_offset, 4UL /* BYTE_LENGTH_IMMEDIATE */ )>elf_sz ) ) { + return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS; } - /* https://github.com/anza-xyz/sbpf/blob/main/src/elf.rs#L399-L401 */ - if( FD_UNLIKELY( !( config->sbpf_min_version <= sbpf_version && sbpf_version <= config->sbpf_max_version ) ) ) { - return FD_SBPF_ELF_ERR_UNSUPPORTED_SBPF_VERSION; - } + FD_STORE( uint, rodata+imm_offset, key ); - /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L403-L407 */ - return (int)sbpf_version; + return FD_SBPF_ELF_SUCCESS; } -int -fd_sbpf_elf_peek_strict( fd_sbpf_elf_info_t * info, - void const * bin, - ulong bin_sz, - fd_sbpf_loader_config_t const * config ) { - (void)config; +static int +fd_sbpf_elf_peek_strict( fd_sbpf_elf_info_t * info, + void const * bin, + ulong bin_sz ) { /* Parse file header */ /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L425 - https://github.com/anza-xyz/sbpf/blob/main/src/elf_parser/mod.rs#L278 + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L278 (Agave does some extra checks on alignment, but they don't seem necessary) */ if( FD_UNLIKELY( bin_szrodata_sz = (uint)phdr[ 1 ].p_memsz; - info->rodata_footprint = (uint)bin_sz; - info->entry_pc = (uint)entry_pc; - info->text_off = (uint)phdr[ 0 ].p_offset; - info->text_sz = (uint)phdr[ 0 ].p_memsz; - info->text_cnt = (uint)( phdr[ 0 ].p_memsz / 8UL ); + info->bin_sz = bin_sz; + info->text_off = (uint)phdr[ 0 ].p_offset; + info->text_sz = (uint)phdr[ 0 ].p_memsz; + info->text_cnt = (uint)( phdr[ 0 ].p_memsz / 8UL ); - return 0; + return FD_SBPF_ELF_SUCCESS; } +static inline int +fd_sbpf_check_overlap( ulong a_start, ulong a_end, ulong b_start, ulong b_end ) { + return !( ( a_end <= b_start || b_end <= a_start ) ); +} + +/* Mirrors Elf64::parse() in Agave. Returns an ElfParserError code on + failure and 0 on success. + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L148 */ int -fd_sbpf_elf_peek_lenient( fd_sbpf_elf_info_t * info, - void const * bin, - ulong bin_sz, - fd_sbpf_loader_config_t const * config ) { - // FIXME - fd_sbpf_elf_info_t * res = fd_sbpf_elf_peek_old( info, bin, bin_sz, config->elf_deploy_checks, config->sbpf_min_version, config->sbpf_max_version ); +fd_sbpf_lenient_elf_parse( fd_sbpf_elf_info_t * info, + void const * bin, + ulong bin_sz ) { + + /* This documents the values that will be set in this function */ + info->bin_sz = bin_sz; + info->phndx_dyn = -1; + info->shndx_dyn = -1; + info->shndx_symtab = -1; + info->shndx_strtab = -1; + info->shndx_dynstr = -1; + info->shndx_dynsymtab = -1; + + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L149 */ + if( FD_UNLIKELY( bin_sz= ehdr.e_shnum ) + ; + if( FD_UNLIKELY( parse_ehdr_err ) ) { + return FD_SBPF_ELF_PARSER_ERR_INVALID_FILE_HEADER; + } + + /* Program headers + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L164-L165 */ + + ulong phdr_sz = sizeof(fd_elf64_phdr) * (ulong)( ehdr.e_phnum ); /* this can't overflow */ + ulong phdr_start = ehdr.e_phoff; + ulong phdr_end = phdr_sz + ehdr.e_phoff; + /* Elf64::parse_program_header_table() */ + { + if( FD_UNLIKELY( phdr_end < phdr_sz ) ) { /* add overflow */ + /* ArithmeticOverflow -> ElfParserError::OutOfBounds + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L671-L675 */ + return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS; + } + + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L301 */ + if( FD_UNLIKELY( fd_sbpf_check_overlap( ehdr_start, ehdr_end, phdr_start, phdr_end ) ) ) { + return FD_SBPF_ELF_PARSER_ERR_OVERLAP; + } + + /* Ensure program header table range lies within the file, like slice_from_bytes */ + if( FD_UNLIKELY( phdr_end > bin_sz ) ) { + return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS; + } + } + + /* Section headers + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L167-L172 */ + + ulong shdr_sz = sizeof(fd_elf64_shdr) * (ulong)( ehdr.e_shnum ); /* this can't overflow */ + ulong shdr_start = ehdr.e_shoff; + ulong shdr_end = shdr_sz + ehdr.e_shoff; + /* Elf64::parse_section_header_table() */ + { + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L314-L317 */ + if( FD_UNLIKELY( shdr_end < shdr_sz ) ) { /* add overflow */ + /* ArithmeticOverflow -> ElfParserError::OutOfBounds + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L671-L675 */ + return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS; + } + + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L318 */ + if( FD_UNLIKELY( fd_sbpf_check_overlap( ehdr_start, ehdr_end, shdr_start, shdr_end ) ) ) { + return FD_SBPF_ELF_PARSER_ERR_OVERLAP; + } + + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L319 */ + if( FD_UNLIKELY( fd_sbpf_check_overlap( phdr_start, phdr_end, shdr_start, shdr_end ) ) ) { + return FD_SBPF_ELF_PARSER_ERR_OVERLAP; + } + + /* Ensure section header table range lies within the file, like slice_from_bytes */ + if( FD_UNLIKELY( shdr_end > bin_sz ) ) { + return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS; + } + } + + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L174-L177 */ + fd_elf64_shdr shdr = FD_LOAD( fd_elf64_shdr, bin + ehdr.e_shoff ); + if( FD_UNLIKELY( shdr.sh_type != FD_ELF_SHT_NULL ) ) { + return FD_SBPF_ELF_PARSER_ERR_INVALID_SECTION_HEADER; + } + + /* Parse each program header + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L179-L196 */ + ulong vaddr = 0UL; + for( ulong i=0; iphndx_dyn == -1 ) { + info->phndx_dyn = (int)i; + } + continue; + } + if( FD_UNLIKELY( phdr.p_vaddr < vaddr ) ) { + return FD_SBPF_ELF_PARSER_ERR_INVALID_PROGRAM_HEADER; + } + if( FD_UNLIKELY( phdr.p_offset + phdr.p_filesz < phdr.p_offset ) ) { /* add overflow */ + return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS; + } + if( FD_UNLIKELY( phdr.p_offset + phdr.p_filesz > bin_sz ) ) { + return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS; + } + vaddr = phdr.p_vaddr; + } + + /* Parse each section header + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L198-L216 */ + ulong offset = 0UL; + for( ulong i=0; ishndx_dyn == -1 ) { + info->shndx_dyn = (int)i; + } + + ulong sh_start = shdr.sh_offset; + ulong sh_end = shdr.sh_offset + shdr.sh_size; + if( FD_UNLIKELY( sh_end < sh_start ) ) { /* add overflow */ + return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS; + } + + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L206-L208 */ + if( FD_UNLIKELY( fd_sbpf_check_overlap( sh_start, sh_end, ehdr_start, ehdr_end ) ) ) { + return FD_SBPF_ELF_PARSER_ERR_OVERLAP; + } + if( FD_UNLIKELY( fd_sbpf_check_overlap( sh_start, sh_end, phdr_start, phdr_end ) ) ) { + return FD_SBPF_ELF_PARSER_ERR_OVERLAP; + } + if( FD_UNLIKELY( fd_sbpf_check_overlap( sh_start, sh_end, shdr_start, shdr_end ) ) ) { + return FD_SBPF_ELF_PARSER_ERR_OVERLAP; + } + + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L209-L215 */ + if( FD_UNLIKELY( sh_start < offset ) ) { + return FD_SBPF_ELF_PARSER_ERR_SECTION_NOT_IN_ORDER; + } + offset = sh_end; + if( FD_UNLIKELY( sh_end > bin_sz ) ) { + return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS; + } + } + + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L218-L224 + section_header_table.get() returning ok is equivalent to ehdr.e_shstrndx < ehdr.e_shnum, + and this is already checked above. So, nothing to do here. */ + + /* Parse sections + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L240 */ + { + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L340-L342 */ + if( FD_UNLIKELY( ehdr.e_shstrndx == 0 ) ) { + return FD_SBPF_ELF_PARSER_ERR_NO_SECTION_NAME_STRING_TABLE; + } + + /* Use section name string table to identify well-known sections */ + ulong section_names_shdr_idx = ehdr.e_shstrndx; + fd_elf64_shdr section_names_shdr = FD_LOAD( fd_elf64_shdr, bin + shdr_start + section_names_shdr_idx*sizeof(fd_elf64_shdr) ); + /* Agave repeats the following validation all the times, we can do it once here + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L474-L476 */ + if( FD_UNLIKELY( section_names_shdr.sh_type != FD_ELF_SHT_STRTAB ) ) { + return FD_SBPF_ELF_PARSER_ERR_INVALID_SECTION_HEADER; + } + + /* Iterate sections and record indices for .text, .symtab, .strtab, .dyn, .dynstr */ + for( ulong i=0; i { + if self.symbol_section_header.is_some() { + return Err(ElfParserError::InvalidSectionHeader); + } + self.symbol_section_header = Some(section_header); + } + ... + _ => {} + } + */ + if( fd_memeq( name, ".symtab", sizeof(".symtab") ) ) { + if( FD_UNLIKELY( info->shndx_symtab != -1 ) ) { + return FD_SBPF_ELF_PARSER_ERR_INVALID_SECTION_HEADER; + } + info->shndx_symtab = (int)i; + } else if( fd_memeq( name, ".strtab", sizeof(".strtab") ) ) { + if( FD_UNLIKELY( info->shndx_strtab != -1 ) ) { + return FD_SBPF_ELF_PARSER_ERR_INVALID_SECTION_HEADER; + } + info->shndx_strtab = (int)i; + } else if( fd_memeq( name, ".dynstr", sizeof(".dynstr") ) ) { + if( FD_UNLIKELY( info->shndx_dynstr != -1 ) ) { + return FD_SBPF_ELF_PARSER_ERR_INVALID_SECTION_HEADER; + } + info->shndx_dynstr = (int)i; + } + } + } + + /* Parse dynamic + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L241 */ + { + /* Try PT_DYNAMIC first; if invalid or absent, fall back to SHT_DYNAMIC. + Note that only the first PT_DYNAMIC and SHT_DYNAMIC are used because of Rust iter().find(). + Mirrors Rust logic: + - Try PT_DYNAMIC: https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L364-L372 + - Fallback to SHT_DYNAMIC if PT missing/invalid: https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L374-L387 + If neither exists, return OK (static file). If SHT_DYNAMIC exists but is invalid, error. */ + + ulong dynamic_table_start = ULONG_MAX; + ulong dynamic_table_end = ULONG_MAX; + + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L364-L372 */ + if( info->phndx_dyn >= 0 ) { + fd_elf64_phdr dyn_ph = FD_LOAD( fd_elf64_phdr, bin + phdr_start + (ulong)info->phndx_dyn*sizeof(fd_elf64_phdr) ); + dynamic_table_start = dyn_ph.p_offset; + dynamic_table_end = dyn_ph.p_offset + dyn_ph.p_filesz; + + /* slice_from_program_header also checks that the size of the + slice is a multiple of the type size and that the alignment is + correct. */ + if( FD_UNLIKELY( dynamic_table_endbin_sz || + dyn_ph.p_filesz%sizeof(fd_elf64_dyn)!=0UL || + !fd_ulong_is_aligned( dynamic_table_start, 8UL ) ) ) { + /* skip - try SHT_DYNAMIC instead */ + dynamic_table_start = ULONG_MAX; + dynamic_table_end = ULONG_MAX; + } + } + + /* If PT_DYNAMIC did not validate, try SHT_DYNAMIC + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L376-L387 */ + if( dynamic_table_start==ULONG_MAX && info->shndx_dyn >= 0 ) { + fd_elf64_shdr dyn_sh = FD_LOAD( fd_elf64_shdr, bin + shdr_start + (ulong)info->shndx_dyn*sizeof(fd_elf64_shdr) ); + dynamic_table_start = dyn_sh.sh_offset; + dynamic_table_end = dyn_sh.sh_offset + dyn_sh.sh_size; + if( FD_UNLIKELY( ( dynamic_table_end < dynamic_table_start ) + | ( dynamic_table_end > bin_sz ) + | ( dyn_sh.sh_size % sizeof(fd_elf64_dyn) != 0UL ) ) ) { + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L382-L385 */ + return FD_SBPF_ELF_PARSER_ERR_INVALID_DYNAMIC_SECTION_TABLE; + } + } + + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L393 */ + if( dynamic_table_start==ULONG_MAX ) { + return FD_SBPF_ELF_SUCCESS; + } + + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L396-L407 */ + ulong dynamic_table[ FD_ELF_DT_NUM ] = { 0UL }; + ulong dyn_cnt = (dynamic_table_end - dynamic_table_start) / (ulong)sizeof(fd_elf64_dyn); + for( ulong i = 0UL; i=FD_ELF_DT_NUM ) ) { + continue; + } + + dynamic_table[ dyn.d_tag ] = dyn.d_un.d_val; + } + + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L409 */ + do { + ulong vaddr = dynamic_table[ FD_ELF_DT_REL ]; + if( FD_UNLIKELY( vaddr==0UL ) ) { + break; /* from this do-while */ + } + + if ( FD_UNLIKELY( dynamic_table[ FD_ELF_DT_RELENT ] != sizeof(fd_elf64_rel) ) ) { + return FD_SBPF_ELF_PARSER_ERR_INVALID_DYNAMIC_SECTION_TABLE; + } + + ulong size = dynamic_table[ FD_ELF_DT_RELSZ ]; + if( FD_UNLIKELY( size==0UL ) ) { + return FD_SBPF_ELF_PARSER_ERR_INVALID_DYNAMIC_SECTION_TABLE; + } + + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L430-L444 */ + ulong offset = ULONG_MAX; + for( ulong i=0; i bin_sz ) ) ) { + return FD_SBPF_ELF_PARSER_ERR_INVALID_DYNAMIC_SECTION_TABLE; + } + + /* Save the dynamic relocation table info */ + info->dt_rel_off = (uint)offset; + info->dt_rel_sz = (uint)size; + } while( 0 ); /* so we can break out */ + + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L410 */ + do { + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L452-L455 */ + ulong vaddr = dynamic_table[ FD_ELF_DT_SYMTAB ]; + if( FD_UNLIKELY( vaddr==0UL ) ) { + break; /* from this do-while */ + } + + fd_elf64_shdr shdr_sym = { 0 }; + for( ulong i=0; ishndx_dynsymtab = (int)i; + break; + } + } + + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L457-L461 */ + if( FD_UNLIKELY( info->shndx_dynsymtab==-1 ) ) { + return FD_SBPF_ELF_PARSER_ERR_INVALID_DYNAMIC_SECTION_TABLE; + } + + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L463-L464 */ + { + if( FD_UNLIKELY( shdr_sym.sh_type != FD_ELF_SHT_SYMTAB && shdr_sym.sh_type != FD_ELF_SHT_DYNSYM ) ) { + return FD_SBPF_ELF_PARSER_ERR_INVALID_SECTION_HEADER; + } + ulong shdr_sym_start = shdr_sym.sh_offset; + ulong shdr_sym_end = shdr_sym.sh_offset + shdr_sym.sh_size; + if( FD_UNLIKELY( ( shdr_sym_end < shdr_sym_start ) + | ( shdr_sym_end > bin_sz ) + | ( shdr_sym.sh_size % sizeof(fd_elf64_sym) != 0UL ) ) ) { + return FD_SBPF_ELF_PARSER_ERR_INVALID_SIZE; + } + } + } while( 0 ); /* so we can break out */ + } + + return FD_SBPF_ELF_SUCCESS; +} + +/* Performs validation checks on the ELF. Returns an ElfError on failure + and 0 on success. + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L719-L809 */ +int +fd_sbpf_lenient_elf_validate( fd_sbpf_elf_info_t * info, + void const * bin, + ulong bin_sz, + fd_elf64_shdr * text_shdr ) { + + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L721-L736 */ + fd_elf64_ehdr ehdr = FD_LOAD( fd_elf64_ehdr, bin ); + if( FD_UNLIKELY( ehdr.e_ident[ FD_ELF_EI_CLASS ] != FD_ELF_CLASS_64 ) ) { + return FD_SBPF_ELF_ERR_WRONG_CLASS; + } + if( FD_UNLIKELY( ehdr.e_ident[ FD_ELF_EI_DATA ] != FD_ELF_DATA_LE ) ) { + return FD_SBPF_ELF_ERR_WRONG_ENDIANNESS; + } + if( FD_UNLIKELY( ehdr.e_ident[ FD_ELF_EI_OSABI ] != FD_ELF_OSABI_NONE ) ) { + return FD_SBPF_ELF_ERR_WRONG_ABI; + } + if( FD_UNLIKELY( ehdr.e_machine != FD_ELF_EM_BPF && ehdr.e_machine != FD_ELF_EM_SBPF ) ) { + return FD_SBPF_ELF_ERR_WRONG_MACHINE; + } + if( FD_UNLIKELY( ehdr.e_type != FD_ELF_ET_DYN ) ) { + return FD_SBPF_ELF_ERR_WRONG_TYPE; + } + + /* This code doesn't do anything: + 1. version is already checked at the very beginning of elf_peek + 2. the if condition is never true because sbpf_version is always v0 + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L738-L763 */ + + ulong shdr_start = ehdr.e_shoff; + ulong section_names_shdr_idx = ehdr.e_shstrndx; + fd_elf64_shdr section_names_shdr = FD_LOAD( fd_elf64_shdr, bin + shdr_start + section_names_shdr_idx*sizeof(fd_elf64_shdr) ); + + /* We do a single iteration over the section header table, collect all info + we need and return the errors later to match Agave. */ + + int shndx_text = -1; + int writeable_err = 0; + for( ulong i=0; ish_addr <= ehdr.e_entry && ehdr.e_entry < fd_ulong_sat_add( text_shdr->sh_addr, text_shdr->sh_size ) + ) ) ) { + return FD_SBPF_ELF_ERR_ENTRYPOINT_OUT_OF_BOUNDS; + } + + /* Get text section file ranges to calculate the size. */ + fd_sbpf_range_t text_section_range; + fd_shdr_get_file_range( text_shdr, &text_section_range ); + + info->text_off = (uint)text_shdr->sh_addr; + info->text_sz = text_section_range.hi-text_section_range.lo; + info->text_cnt = (uint)( info->text_sz/8UL ); + info->shndx_text = shndx_text; + + return FD_SBPF_ELF_SUCCESS; +} + +/* First part of Agave's load_with_lenient_parser(). We split up this + function into two parts so we know how much memory we need to + allocate for the loading step. Returns an ElfError on failure and 0 + on success. + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L593-L638 */ +static int +fd_sbpf_elf_peek_lenient( fd_sbpf_elf_info_t * info, + void const * bin, + ulong bin_sz ) { + + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L607 */ + int res = fd_sbpf_lenient_elf_parse( info, bin, bin_sz ); + if( FD_UNLIKELY( res<0 ) ) { + return fd_sbpf_elf_parser_err_to_elf_err( res ); + } + + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L617 */ + fd_elf64_shdr text_shdr = { 0 }; + res = fd_sbpf_lenient_elf_validate( info, bin, bin_sz, &text_shdr ); + if( FD_UNLIKELY( res<0 ) ) { + return res; + } + + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L620-L638 */ + { + ulong text_section_vaddr = fd_ulong_sat_add( text_shdr.sh_addr, FD_SBPF_MM_RODATA_ADDR ); + ulong vaddr_end = text_section_vaddr; + + /* Validate bounds - reject broken ELFs */ + if( FD_UNLIKELY( vaddr_end>FD_SBPF_MM_STACK_ADDR ) ) { + return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS; + } + } /* Peek (vs load) stops here - https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L639 */ - return res==NULL ? FD_SBPF_ELF_PARSER_ERR_INVALID_FILE_HEADER : 0; + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L638 */ + + return FD_SBPF_ELF_SUCCESS; +} + +static int +fd_sbpf_program_get_sbpf_version_or_err( void const * bin, + ulong bin_sz, + fd_sbpf_loader_config_t const * config ) { + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L376-L381 */ + const ulong E_FLAGS_OFFSET = 48UL; + + if( FD_UNLIKELY( bin_szsbpf_max_version==FD_SBPF_V0 ) ) { + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L384-L388 */ + sbpf_version = e_flags==E_FLAGS_SBPF_V2 ? FD_SBPF_RESERVED : FD_SBPF_V0; + } else { + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L390-L396 */ + sbpf_version = e_flags < FD_SBPF_VERSION_COUNT ? e_flags : FD_SBPF_RESERVED; + } + + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L399-L401 */ + if( FD_UNLIKELY( !( config->sbpf_min_version <= sbpf_version && sbpf_version <= config->sbpf_max_version ) ) ) { + return FD_SBPF_ELF_ERR_UNSUPPORTED_SBPF_VERSION; + } + + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L403-L407 */ + return (int)sbpf_version; } int @@ -1464,7 +1375,7 @@ fd_sbpf_elf_peek( fd_sbpf_elf_info_t * info, ulong bin_sz, fd_sbpf_loader_config_t const * config ) { /* Extract sbpf_version (or error) - https://github.com/anza-xyz/sbpf/blob/main/src/elf.rs#L376-L401 */ + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L376-L401 */ int maybe_sbpf_version = fd_sbpf_program_get_sbpf_version_or_err( bin, bin_sz, config ); if( FD_UNLIKELY( maybe_sbpf_version<0 ) ) { return maybe_sbpf_version; @@ -1472,43 +1383,361 @@ fd_sbpf_elf_peek( fd_sbpf_elf_info_t * info, /* Initialize info struct */ *info = (fd_sbpf_elf_info_t) { - .text_off = 0U, - .text_cnt = 0U, - .text_sz = 0UL, - .dynstr_off = 0U, - .dynstr_sz = 0U, - .rodata_sz = 0U, - .rodata_footprint = 0U, - .shndx_text = -1, - .shndx_symtab = -1, - .shndx_strtab = -1, - .shndx_dyn = -1, - .shndx_dynstr = -1, - .phndx_dyn = -1, - .entry_pc = 0U, - .sbpf_version = (uint)maybe_sbpf_version, + .bin_sz = 0U, + .text_off = 0U, + .text_cnt = 0U, + .text_sz = 0UL, + .shndx_text = -1, + .shndx_symtab = -1, + .shndx_strtab = -1, + .shndx_dyn = -1, + .shndx_dynstr = -1, + .shndx_dynsymtab = -1, + .phndx_dyn = -1, + .dt_rel_off = 0UL, + .dt_rel_sz = 0UL, + .sbpf_version = (uint)maybe_sbpf_version, /* !!! Keep this in sync with -Werror=missing-field-initializers */ }; - /* Invoke strict vs lenient parser - https://github.com/anza-xyz/sbpf/blob/main/src/elf.rs#L403-L407 */ - if( FD_UNLIKELY( fd_sbpf_enable_stricter_elf_headers( info->sbpf_version ) ) ) { - return fd_sbpf_elf_peek_strict( info, bin, bin_sz, config ); + /* Invoke strict vs lenient parser. The strict parser is used for + SBPF version >= 3. The strict parser also returns an ElfParserError + while the lenient parser returns an ElfError, so we have to map + the strict parser's error code. + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L403-L407 */ + if( FD_UNLIKELY( fd_sbpf_enable_stricter_elf_headers_enabled( info->sbpf_version ) ) ) { + return fd_sbpf_elf_parser_err_to_elf_err( fd_sbpf_elf_peek_strict( info, bin, bin_sz ) ); } - return fd_sbpf_elf_peek_lenient( info, bin, bin_sz, config ); + return fd_sbpf_elf_peek_lenient( info, bin, bin_sz ); } -int +/* Parses and concatenates the readonly data sections. This function + also computes and sets the rodata_sz field inside the SBPF program + struct. + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L812-L987 */ +static int +fd_sbpf_parse_ro_sections( fd_sbpf_program_t * prog, + void const * bin, + ulong bin_sz, + fd_sbpf_loader_config_t const * config ) { + + fd_sbpf_elf_t const * elf = (fd_sbpf_elf_t const *)bin; + fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff ); + fd_elf64_shdr const * section_names_shdr = &shdrs[ elf->ehdr.e_shstrndx ]; + uchar * rodata = prog->rodata; + + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L818-L834 */ + ulong lowest_addr = ULONG_MAX; /* Lowest section address */ + ulong highest_addr = 0UL; /* Highest section address */ + ulong ro_fill_length = 0UL; /* Aggregated section length, excluding gaps between sections */ + uchar invalid_offsets = 0; /* Whether the section has invalid offsets */ + + /* Store the section header indices of ro slices to fill later. */ + ulong ro_slices_shidxs[ elf->ehdr.e_shnum ]; + ulong ro_slices_cnt = 0UL; + + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L837-L909 */ + for( uint i=0U; iehdr.e_shnum; i++ ) { + fd_elf64_shdr const * section_header = &shdrs[ i ]; + + /* Match the section name. + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L838-L845 */ + char name[ FD_SBPF_SECTION_NAME_SZ_MAX ]; + if( FD_UNLIKELY( fd_sbpf_lenient_get_string_in_section( bin, bin_sz, section_names_shdr, section_header->sh_name, FD_SBPF_SECTION_NAME_SZ_MAX, name ) ) ) { + continue; + } + + if( FD_UNLIKELY( strncmp( name, ".text", sizeof(".text") ) && + strncmp( name, ".rodata", sizeof(".rodata") ) && + strncmp( name, ".data.rel.ro", sizeof(".data.rel.ro") ) && + strncmp( name, ".eh_frame", sizeof(".eh_frame") ) ) ) { + continue; + } + + ulong section_addr = section_header->sh_addr; + + /* Handling for the section header offsets. If ELF vaddrs are + enabled, the section header addresses are allowed to be > the + section header offsets, as long as address - offset is constant + across all sections. Otherwise, the section header addresses + and offsets must match. + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L865-L884 */ + if( FD_LIKELY( !invalid_offsets ) ) { + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L866-L880 */ + if( FD_UNLIKELY( section_addr!=section_header->sh_offset ) ) { + invalid_offsets = 1; + } + } + + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L886-L897 */ + ulong vaddr_end = section_addr; + if( section_addrreject_broken_elfs && invalid_offsets ) || + vaddr_end>FD_SBPF_MM_STACK_ADDR ) ) { + return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS; + } + + /* Append the ro slices vector and update the lowest / highest addr + and ro_fill_length variables. Agave stores three fields in the + ro slices array that can all be derived from the section header, + so we just need to store the indices. + + The call to fd_shdr_get_file_range() is allowed to fail (Agave's + unwrap_or_default() call returns a range of 0..0 in this case). + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L899-L908 */ + fd_sbpf_range_t section_header_range; + fd_shdr_get_file_range( section_header, §ion_header_range ); + if( FD_UNLIKELY( section_header_range.hi>bin_sz ) ) { + return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS; + } + ulong section_data_len = section_header_range.hi-section_header_range.lo; + + lowest_addr = fd_ulong_min( lowest_addr, section_addr ); + highest_addr = fd_ulong_max( highest_addr, fd_ulong_sat_add( section_addr, section_data_len ) ); + ro_fill_length = fd_ulong_sat_add( ro_fill_length, section_data_len ); + ro_slices_shidxs[ ro_slices_cnt++ ] = i; + } + + /* This checks that the ro sections are not overlapping. This check + is incomplete, however, because it does not account for the + existence of gaps between sections in calculations. + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L910-L913 */ + if( FD_UNLIKELY( config->reject_broken_elfs && + fd_ulong_sat_add( lowest_addr, ro_fill_length )>highest_addr ) ) { + return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS; + } + + /* Note that optimize_rodata is always false. + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L923-L984 */ + { + /* Readonly / non-readonly sections are mixed, so non-readonly + sections must be zeroed and the readonly sections must be copied + at their respective offsets. + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L950-L983 */ + lowest_addr = 0UL; + + /* Bounds check. */ + ulong buf_len = highest_addr; + if( FD_UNLIKELY( buf_len>bin_sz ) ) { + return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS; + } + + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L971-L976 */ + uchar ro_section[ buf_len ]; fd_memset( ro_section, 0, buf_len ); + for( ulong i=0UL; ish_addr; + + /* This was checked above and should never fail. */ + fd_sbpf_range_t slice_range; + fd_shdr_get_file_range( shdr, &slice_range ); + if( FD_UNLIKELY( slice_range.hi>bin_sz ) ) { + return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS; + } + + ulong buf_offset_start = fd_ulong_sat_sub( section_addr, lowest_addr ); + ulong slice_len = slice_range.hi-slice_range.lo; + if( FD_UNLIKELY( slice_len>buf_len ) ) { + return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS; + } + + fd_memcpy( ro_section+buf_offset_start, rodata+slice_range.lo, slice_len ); + } + + /* Copy the rodata section back in. */ + prog->rodata_sz = buf_len; + fd_memcpy( rodata, ro_section, buf_len ); + } + + return FD_SBPF_ELF_SUCCESS; +} + +/* Applies ELF relocations in-place. Returns 0 on success and an + ElfError error code on failure. + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L990-L1331 */ +static int +fd_sbpf_program_relocate( fd_sbpf_program_t * prog, + void const * bin, + ulong bin_sz, + fd_sbpf_loader_config_t const * config, + fd_sbpf_loader_t * loader ) { + fd_sbpf_elf_info_t const * elf_info = &prog->info; + fd_sbpf_elf_t const * elf = (fd_sbpf_elf_t const *)bin; + uchar * rodata = prog->rodata; + fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff ); + fd_elf64_shdr const * shtext = &shdrs[ elf_info->shndx_text ]; + + /* Copy rodata segment */ + fd_memcpy( rodata, elf->bin, elf_info->bin_sz ); + + /* Fixup all program counter relative call instructions + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1005-L1041 */ + { + /* Validate the bytes range of the text section. + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1006-L1008 */ + fd_sbpf_range_t text_section_range; + fd_shdr_get_file_range( shtext, &text_section_range ); + + ulong insn_cnt = (text_section_range.hi-text_section_range.lo)/8UL; + if( FD_UNLIKELY( shtext->sh_size+shtext->sh_offset>bin_sz ) ) { + return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS; + } + + uchar * ptr = rodata + shtext->sh_offset; + + for( ulong i=0UL; i> 32UL); + if( (opc!=FD_SBPF_OP_CALL_IMM) || (imm==-1) ) continue; + + /* Calculate and check the target PC + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1016-L1021 */ + long target_pc = fd_long_sat_add( fd_long_sat_add( (long)i, 1L ), imm); + if( FD_UNLIKELY( target_pc<0L || target_pc>=(long)insn_cnt ) ) { + return FD_SBPF_ELF_ERR_RELATIVE_JUMP_OUT_OF_BOUNDS; + } + + /* Update the calldests + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1027-L1032 */ + uint pc_hash; + int err = fd_sbpf_register_function_hashed_legacy( loader, prog, "", (ulong)target_pc, &pc_hash ); + if( FD_UNLIKELY( err!=FD_SBPF_ELF_SUCCESS ) ) { + return err; + } + + /* Store PC hash in text section. Check for writes outside the + text section. + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1034-L1038 */ + ulong offset = fd_ulong_sat_add( fd_ulong_sat_mul( i, 8UL ), 4UL ); // offset in text section + if( FD_UNLIKELY( offset+4UL>shtext->sh_size ) ) { + return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS; + } + + FD_STORE( uint, ptr+4UL, pc_hash ); + } + } + + /* Fixup all the relocations in the relocation section if exists. The + dynamic relocations table was already parsed and validated in + fd_sbpf_lenient_elf_parse(). + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1046-L1304 */ + { + fd_elf64_rel const * dt_rels = (fd_elf64_rel const *)( elf->bin + elf_info->dt_rel_off ); + uint dt_rel_cnt = elf_info->dt_rel_sz / sizeof(fd_elf64_rel); + + for( uint i=0U; ir_offset; + + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1068-L1303 */ + int err; + switch( FD_ELF64_R_TYPE( dt_rel->r_info ) ) { + case FD_ELF_R_BPF_64_64: + err = fd_sbpf_r_bpf_64_64( elf, bin_sz, rodata, elf_info, dt_rel, r_offset ); + break; + case FD_ELF_R_BPF_64_RELATIVE: + err = fd_sbpf_r_bpf_64_relative(elf, bin_sz, rodata, elf_info, r_offset ); + break; + case FD_ELF_R_BPF_64_32: + err = fd_sbpf_r_bpf_64_32( loader, prog, elf, bin_sz, rodata, elf_info, dt_rel, r_offset, config ); + break; + default: + return FD_SBPF_ELF_ERR_UNKNOWN_RELOCATION; + } + + if( FD_UNLIKELY( err!=FD_SBPF_ELF_SUCCESS ) ) { + return err; + } + } + } + + /* ...rest of this function is a no-op because + enable_symbol_and_section_labels is disabled in production. */ + + return FD_SBPF_ELF_SUCCESS; +} + +/* Second part of load_with_lenient_parser(). + + This function is responsible for "loading" an sBPF program. This + means... + 1. Applies any relocations in-place to the rodata section. + 2. Registers the program entrypoint and other valid calldests. + 3. Parses and validates the rodata sections, zeroing out any gaps + between sections. + + Returns 0 on success and an ElfError error code on failure. + + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L640-L689 + */ +static int fd_sbpf_program_load_lenient( fd_sbpf_program_t * prog, void const * bin, ulong bin_sz, - fd_sbpf_syscalls_t * syscalls, + fd_sbpf_loader_t * loader, fd_sbpf_loader_config_t const * config ) { + /* Load (vs peek) starts here - https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L639 */ + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L641 */ + + fd_sbpf_elf_t const * elf = (fd_sbpf_elf_t const *)bin; + fd_sbpf_elf_info_t * elf_info = &prog->info; + fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff ); + fd_elf64_shdr const * sh_text = &shdrs[ elf_info->shndx_text ]; + + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L642-L647 */ + int err = fd_sbpf_program_relocate( prog, bin, bin_sz, config, loader ); + if( FD_UNLIKELY( err ) ) return err; - // FIXME - return fd_sbpf_program_load_old( prog, bin, bin_sz, syscalls, config->elf_deploy_checks ); + /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L649-L653 */ + ulong offset = fd_ulong_sat_sub( elf->ehdr.e_entry, sh_text->sh_addr ); + if( FD_UNLIKELY( offset&0x7UL ) ) { /* offset % 8 != 0 */ + return FD_SBPF_ELF_ERR_INVALID_ENTRYPOINT; + } + + /* Unregister the entrypoint from the calldests, and register the + entry_pc. Our behavior slightly diverges from Agave's because we + rely on an explicit entry_pc field within the elf_info struct + to handle the b"entrypoint" symbol, and rely on PC hash inverses + for any other CALL_IMM targets. + + Note that even though we won't use the calldests value for the + entry pc, we still need to "register" it to check for any potential + symbol collisions and report errors accordingly. We unregister it + first by setting it to ULONG_MAX. + + TODO: Add special casing for static syscalls enabled. For now, it + is not implemented. + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L654-L667 */ + prog->entry_pc = ULONG_MAX; + ulong entry_pc = offset/8UL; + err = fd_sbpf_register_function_hashed_legacy( + loader, + prog, + "entrypoint", + entry_pc, + NULL ); + if( FD_UNLIKELY( err!=FD_SBPF_ELF_SUCCESS ) ) { + return err; + } + + /* Parse the ro sections. + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L669-L676 */ + err = fd_sbpf_parse_ro_sections( prog, bin, bin_sz, config ); + if( FD_UNLIKELY( err!=FD_SBPF_ELF_SUCCESS ) ) { + return err; + } + + return FD_SBPF_ELF_SUCCESS; } int @@ -1517,14 +1746,31 @@ fd_sbpf_program_load( fd_sbpf_program_t * prog, ulong bin_sz, fd_sbpf_syscalls_t * syscalls, fd_sbpf_loader_config_t const * config ) { + fd_sbpf_loader_t loader = { + .calldests = prog->calldests, + .syscalls = syscalls, + }; + /* Invoke strict vs lenient loader Note: info.sbpf_version is already set by fd_sbpf_program_parse() - https://github.com/anza-xyz/sbpf/blob/main/src/elf.rs#L409-L413 */ - if( FD_UNLIKELY( fd_sbpf_enable_stricter_elf_headers( prog->info.sbpf_version ) ) ) { - /* There is nothing else to do in the strict case*/ - return 0; + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L403-L409 */ + if( FD_UNLIKELY( fd_sbpf_enable_stricter_elf_headers_enabled( prog->info.sbpf_version ) ) ) { + /* There is nothing else to do in the strict case except updating + the prog->rodata_sz field from phdr[ 1 ].p_memsz, and setting + the entry_pc. */ + fd_elf64_ehdr ehdr = FD_LOAD( fd_elf64_ehdr, bin ); + fd_elf64_phdr phdr_0 = FD_LOAD( fd_elf64_phdr, bin+sizeof(fd_elf64_ehdr) ); + fd_elf64_phdr phdr_1 = FD_LOAD( fd_elf64_phdr, bin+sizeof(fd_elf64_ehdr)+sizeof(fd_elf64_phdr) ); + prog->rodata_sz = phdr_1.p_memsz; + prog->entry_pc = ( ehdr.e_entry-phdr_0.p_vaddr )/8UL; + return FD_SBPF_ELF_SUCCESS; + } + int res = fd_sbpf_program_load_lenient( prog, bin, bin_sz, &loader, config ); + if( FD_UNLIKELY( res!=FD_SBPF_ELF_SUCCESS ) ) { + return res; } - return fd_sbpf_program_load_lenient( prog, bin, bin_sz, syscalls, config ); + + return FD_SBPF_ELF_SUCCESS; } #undef ERR diff --git a/src/ballet/sbpf/fd_sbpf_loader.h b/src/ballet/sbpf/fd_sbpf_loader.h index 52f71277aff..d25f7ea7ddd 100644 --- a/src/ballet/sbpf/fd_sbpf_loader.h +++ b/src/ballet/sbpf/fd_sbpf_loader.h @@ -19,6 +19,7 @@ #define FD_SBPF_PROG_RODATA_ALIGN 8UL /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L17 */ +#define FD_SBPF_ELF_PARSER_SUCCESS ( 0) #define FD_SBPF_ELF_PARSER_ERR_INVALID_FILE_HEADER (-1) #define FD_SBPF_ELF_PARSER_ERR_INVALID_PROGRAM_HEADER (-2) #define FD_SBPF_ELF_PARSER_ERR_INVALID_SECTION_HEADER (-3) @@ -35,20 +36,47 @@ #define FD_SBPF_ELF_PARSER_ERR_NO_STRING_TABLE (-14) #define FD_SBPF_ELF_PARSER_ERR_NO_DYNAMIC_STRING_TABLE (-15) -/* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L40 */ +/* Map Rust ElfError (elf.rs v0.12.2) to C error codes */ +/* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L40-L66 */ +#define FD_SBPF_ELF_SUCCESS ( 0) +#define FD_SBPF_ELF_ERR_FAILED_TO_PARSE ( -1) +#define FD_SBPF_ELF_ERR_ENTRYPOINT_OUT_OF_BOUNDS ( -2) +#define FD_SBPF_ELF_ERR_INVALID_ENTRYPOINT ( -3) +#define FD_SBPF_ELF_ERR_FAILED_TO_GET_SECTION ( -4) +#define FD_SBPF_ELF_ERR_UNRESOLVED_SYMBOL ( -5) +#define FD_SBPF_ELF_ERR_SECTION_NOT_FOUND ( -6) +#define FD_SBPF_ELF_ERR_RELATIVE_JUMP_OUT_OF_BOUNDS ( -7) +#define FD_SBPF_ELF_ERR_SYMBOL_HASH_COLLISION ( -8) +#define FD_SBPF_ELF_ERR_WRONG_ENDIANNESS ( -9) +#define FD_SBPF_ELF_ERR_WRONG_ABI (-10) +#define FD_SBPF_ELF_ERR_WRONG_MACHINE (-11) +#define FD_SBPF_ELF_ERR_WRONG_CLASS (-12) +#define FD_SBPF_ELF_ERR_NOT_ONE_TEXT_SECTION (-13) +#define FD_SBPF_ELF_ERR_WRITABLE_SECTION_NOT_SUPPORTED (-14) +#define FD_SBPF_ELF_ERR_ADDRESS_OUTSIDE_LOADABLE_SECTION (-15) +#define FD_SBPF_ELF_ERR_INVALID_VIRTUAL_ADDRESS (-16) +#define FD_SBPF_ELF_ERR_UNKNOWN_RELOCATION (-17) +#define FD_SBPF_ELF_ERR_FAILED_TO_READ_RELOCATION_INFO (-18) +#define FD_SBPF_ELF_ERR_WRONG_TYPE (-19) +#define FD_SBPF_ELF_ERR_UNKNOWN_SYMBOL (-20) +#define FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS (-21) #define FD_SBPF_ELF_ERR_UNSUPPORTED_SBPF_VERSION (-22) +#define FD_SBPF_ELF_ERR_INVALID_PROGRAM_HEADER (-23) -/* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/program.rs - FD_SBPF_VERSION_COUNT represents the latest active version, - which is V3 for Agave 2.3 and V4 for Agave 3.x. - To build for Agave 3.x, set FD_SBPF_VERSION_COUNT to 5U. */ -#define FD_SBPF_VERSION_COUNT (4U) +/* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/program.rs */ +#define FD_SBPF_VERSION_COUNT (5U) #define FD_SBPF_V0 (0U) #define FD_SBPF_V1 (1U) #define FD_SBPF_V2 (2U) #define FD_SBPF_V3 (3U) #define FD_SBPF_V4 (4U) -#define FD_SBPF_RESERVED (FD_SBPF_VERSION_COUNT+1U) +#define FD_SBPF_RESERVED (FD_SBPF_VERSION_COUNT) + +/* Hardcoded constant for the murmur3_32 hash of the entrypoint. */ +#define FD_SBPF_ENTRYPOINT_PC (0xb00c380U) +#define FD_SBPF_ENTRYPOINT_HASH (0x71e3cf81U) /* fd_pchash( FD_SBPF_ENTRYPOINT_PC ) */ + +#define E_FLAGS_SBPF_V2 (0x20U) /* Program struct *****************************************************/ @@ -117,15 +145,11 @@ typedef struct fd_sbpf_syscalls fd_sbpf_syscalls_t; to fully load the program. */ struct fd_sbpf_elf_info { - uint text_off; /* File offset of .text section (overlaps rodata segment) */ - uint text_cnt; /* Instruction count */ - ulong text_sz; /* Length of text segment */ - - uint dynstr_off; /* File offset of .dynstr section (0=missing) */ - uint dynstr_sz; /* Dynstr char count */ + ulong bin_sz; /* size of ELF binary */ - uint rodata_sz; /* size of rodata segment */ - uint rodata_footprint; /* size of ELF binary */ + uint text_off; /* File offset of .text section (overlaps rodata segment) */ + uint text_cnt; /* Instruction count */ + ulong text_sz; /* Length of text segment */ /* Known section indices In [-1,USHORT_MAX) where -1 means "not found" */ @@ -134,15 +158,14 @@ struct fd_sbpf_elf_info { int shndx_strtab; int shndx_dyn; int shndx_dynstr; + int shndx_dynsymtab; /* Section header index of the dynamic symbol table */ /* Known program header indices (like shndx_*) */ int phndx_dyn; - uint entry_pc; /* Program counter of entry point - NOTE: MIGHT BE OUT OF BOUNDS! */ - - /* Bitmap of sections to be loaded (LSB => MSB) */ - ulong loaded_sections[ 1024UL ]; + /* Dynamic relocation table entries */ + uint dt_rel_off; /* File offset of dynamic relocation table */ + uint dt_rel_sz; /* Number of dynamic relocation table entries */ /* SBPF version, SIMD-0161 */ ulong sbpf_version; @@ -151,40 +174,62 @@ typedef struct fd_sbpf_elf_info fd_sbpf_elf_info_t; /* fd_sbpf_program_t describes a loaded program in memory. - [rodata,rodata+rodata_sz) is an externally allocated buffer holding + [rodata,rodata+bin_sz) is an externally allocated buffer holding the read-only segment to be loaded into the VM. WARNING: The rodata - area required doing load (rodata_footprint) is larger than the area - mapped into the VM (rodata_sz). + area required doing load (bin_sz) is larger than the area mapped into + the VM (rodata_sz). [text,text+8*text_cnt) is a sub-region of the read-only segment - containing executable code. */ + containing executable code. + + We need to maintain a separate value tracking the entrypoint calldest + because we lay out our calldests in a set instead of a map (like + Agave does), which is more performant but comes with a few footguns. + Since we only store the target PC and not a keypair of , we need to make sure we unregister the correct target PC from + the map. For all other cases besides the b"entrypoint" string, we can + simply check for membership within the calldests set because the + 32-bit murmur3 hash function is bijective, implying key collision iff + value collision. However, the b"entrypoint" string is a special case + because the key is the hardcoded hash of the b"entrypoint" string, + but the value can correspond to any target PC. This means that + someone could register several different target PCs with the same + entrypoint PC, and we cannot figure out which target PC we must + unregister. Additionally, we would not be able to check for + collisions for multiple registered b"entrypoint" strings with + different target PCs. + + Once entry_pc is set, any future calls to set the entry_pc within the + loader will error out with FD_SBPF_ELF_ERR_SYMBOL_HASH_COLLISION. */ struct __attribute__((aligned(32UL))) fd_sbpf_program { fd_sbpf_elf_info_t info; /* rodata segment to be mapped into VM memory */ void * rodata; /* rodata segment data */ - ulong rodata_sz; /* size of data */ + ulong rodata_sz; /* size of read-only data */ /* text section within rodata segment */ ulong * text; ulong text_cnt; /* instruction count */ ulong text_off; /* instruction offset for use in CALL_REG instructions */ - ulong text_sz; /* size of text segment */ - ulong entry_pc; /* entrypoint PC (at text[ entry_pc - start_pc ]) ... FIXME: HMMMM ... CODE SEEMS TO USE TEXT[ ENTRY_PC ] */ + ulong text_sz; /* size of text segment. Guaranteed to be <= bin_sz. */ + ulong entry_pc; /* entrypoint PC (at text[ entry_pc ]). ULONG_MAX if not set. */ - /* Bit vector of valid call destinations (bit count is rodata_sz) */ + /* Bit vector of valid call destinations (bit count is text_cnt). */ void * calldests_shmem; - /* Local join to bit vector of valid call destinations */ + /* Local join to bit vector of valid call destinations (target PCs) */ fd_sbpf_calldests_t * calldests; }; typedef struct fd_sbpf_program fd_sbpf_program_t; struct fd_sbpf_loader_config { - int elf_deploy_checks; + union { + int elf_deploy_checks; + int reject_broken_elfs; + }; uint sbpf_min_version; uint sbpf_max_version; - int enable_symbol_and_section_labels; }; typedef struct fd_sbpf_loader_config fd_sbpf_loader_config_t; @@ -241,29 +286,22 @@ fd_sbpf_program_new( void * prog_mem, Initializes and populates the program struct with information about the program and prepares the read-only segment provided in - fd_sbpf_program_new. + fd_sbpf_program_new. This includes performing relocations in the + ELF file and zeroing gaps between rodata sections. Memory region [bin,bin+bin_sz) contains the ELF file to be loaded. + syscalls should be a pointer to a map of registered syscalls and + will be checked against when registering calldests for potential + symbol collisions. + On success, returns 0. - On error, returns FD_SBPF_ERR_* and leaves prog in an undefined - state. + On error, returns FD_SBPF_ERR_*. ### Compliance - This loader does not yet adhere to Solana protocol specs. - It is mostly compatible with solana-labs/rbpf v0.3.0 with the - following config: - - new_elf_parser: true - enable_elf_vaddr: false - reject_broken_elfs: elf_deploy_checks - - For documentation on these config params, see: - https://github.com/anza-xyz/sbpf/blob/v0.3.0/src/vm.rs#L198 - - Solana/Agave equivalent: - https://github.com/anza-xyz/sbpf/blob/v0.8.0/src/elf.rs#L361 + As of writing, this loader is conformant with Solana SBPF v0.12.2, + SBPF versions V0, V1, and V2. */ int @@ -279,17 +317,36 @@ fd_sbpf_program_load( fd_sbpf_program_t * prog, void * fd_sbpf_program_delete( fd_sbpf_program_t * program ); -/* fd_csv_strerror: Returns a cstr describing the source line and error - kind after the last call to `fd_sbpf_program_load` from the same - thread returned non-zero. - Always returns a valid cstr, though the content is undefined in case - the last call to `fd_sbpf_program_load` returned zero (success). */ +/* SBPF versions and features. This should stay in sync with the macro + definitions in fd_vm_private.h until they are removed (once Agave + cleans up the jump table). + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/program.rs#L28 */ + +#define FD_VM_SBPF_DYNAMIC_STACK_FRAMES_ALIGN (64U) + +/* SIMD-0166 */ +static inline int fd_sbpf_dynamic_stack_frames_enabled ( ulong sbpf_version ) { return sbpf_version>=FD_SBPF_V1; } + +/* SIMD-0173 */ +static inline int fd_sbpf_callx_uses_src_reg_enabled ( ulong sbpf_version ) { return sbpf_version>=FD_SBPF_V2; } +static inline int fd_sbpf_enable_lddw_enabled ( ulong sbpf_version ) { return sbpf_version=FD_SBPF_V2; } + +/* SIMD-0174 */ +static inline int fd_sbpf_enable_neg_enabled ( ulong sbpf_version ) { return sbpf_version=FD_SBPF_V2; } +static inline int fd_sbpf_explicit_sign_ext_enabled ( ulong sbpf_version ) { return sbpf_version>=FD_SBPF_V2; } +static inline int fd_sbpf_enable_pqr_enabled ( ulong sbpf_version ) { return sbpf_version>=FD_SBPF_V2; } -char const * -fd_sbpf_strerror( void ); +/* SIMD-0178 */ +static inline int fd_sbpf_static_syscalls_enabled ( ulong sbpf_version ) { return sbpf_version>=FD_SBPF_V3; } +static inline int fd_sbpf_enable_elf_vaddr_enabled ( ulong sbpf_version ) { return sbpf_version!=FD_SBPF_V0; } +static inline int fd_sbpf_reject_rodata_stack_overlap_enabled( ulong sbpf_version ) { return sbpf_version!=FD_SBPF_V0; } /* SIMD-0189 */ -static inline int fd_sbpf_enable_stricter_elf_headers( ulong sbpf_version ) { return sbpf_version >= FD_SBPF_V3; } +static inline int fd_sbpf_enable_stricter_elf_headers_enabled( ulong sbpf_version ) { return sbpf_version>=FD_SBPF_V3; } +static inline int fd_sbpf_enable_lower_bytecode_vaddr_enabled( ulong sbpf_version ) { return sbpf_version>=FD_SBPF_V3; } FD_PROTOTYPES_END diff --git a/src/ballet/sbpf/fuzz_sbpf_loader.c b/src/ballet/sbpf/fuzz_sbpf_loader.c index b8474b8a33c..80aeff1f60e 100644 --- a/src/ballet/sbpf/fuzz_sbpf_loader.c +++ b/src/ballet/sbpf/fuzz_sbpf_loader.c @@ -42,7 +42,7 @@ LLVMFuzzerTestOneInput( uchar const * data, /* Allocate objects */ - void * rodata = malloc( info.rodata_footprint ); + void * rodata = malloc( info.bin_sz ); FD_TEST( rodata ); fd_sbpf_program_t * prog = fd_sbpf_program_new( aligned_alloc( fd_sbpf_program_align(), fd_sbpf_program_footprint( &info ) ), &info, rodata ); diff --git a/src/ballet/sbpf/test_sbpf_elf_peek.c b/src/ballet/sbpf/test_sbpf_elf_peek.c index 747c788c480..975b56c9569 100644 --- a/src/ballet/sbpf/test_sbpf_elf_peek.c +++ b/src/ballet/sbpf/test_sbpf_elf_peek.c @@ -28,7 +28,7 @@ void test_sbpf_version_default( void ) { FD_TEST_CUSTOM( res<0, "hello_solana_program (old) v2 unsupported" ); res = fd_sbpf_elf_peek( &info, ptoken_program_v3_elf, ptoken_program_v3_elf_sz, &config ); - FD_TEST_CUSTOM( res<0, "ptoken_program v3 unsupported" ); + FD_TEST_CUSTOM( info.sbpf_version==FD_SBPF_V0, "ptoken_program v3 accepted as v0" ); } void test_sbpf_version_from_elf_header( void ) { @@ -122,9 +122,7 @@ test_sbpf_elf_peek_strict_hex( void ) { FD_TEST( info.text_off==0x120U ); FD_TEST( info.text_sz ==8UL ); FD_TEST( info.text_cnt==1U ); - FD_TEST( info.entry_pc==0U ); - FD_TEST( info.rodata_sz==0U ); - FD_TEST( info.rodata_footprint==(uint)bin_sz ); + FD_TEST( info.bin_sz==bin_sz ); } int diff --git a/src/ballet/sbpf/test_sbpf_load_prog.c b/src/ballet/sbpf/test_sbpf_load_prog.c index 116d321ffba..afb74d23373 100644 --- a/src/ballet/sbpf/test_sbpf_load_prog.c +++ b/src/ballet/sbpf/test_sbpf_load_prog.c @@ -67,12 +67,13 @@ main( int argc, config.elf_deploy_checks = 1; config.sbpf_min_version = FD_SBPF_V0; config.sbpf_max_version = FD_SBPF_V3; - if( FD_UNLIKELY( fd_sbpf_elf_peek( &elf_info, bin_buf, bin_sz, &config )<0 ) ) - FD_LOG_ERR(( "FAIL: %s", fd_sbpf_strerror() )); + int err = fd_sbpf_elf_peek( &elf_info, bin_buf, bin_sz, &config ); + if( FD_UNLIKELY( err<0 ) ) + FD_LOG_ERR(( "FAIL: %d", err )); /* Allocate rodata segment */ - void * rodata = malloc( elf_info.rodata_footprint ); + void * rodata = malloc( elf_info.bin_sz ); FD_TEST( rodata ); /* Allocate objects */ @@ -112,7 +113,7 @@ main( int argc, /* Yield result */ if( FD_UNLIKELY( load_err!=0 ) ) - FD_LOG_ERR(( "FAIL: %s", fd_sbpf_strerror() )); + FD_LOG_ERR(( "FAIL: %d", load_err )); FD_LOG_NOTICE(( "pass" )); fd_halt(); diff --git a/src/ballet/sbpf/test_sbpf_loader.c b/src/ballet/sbpf/test_sbpf_loader.c index 7019deaa19e..1c379968959 100644 --- a/src/ballet/sbpf/test_sbpf_loader.c +++ b/src/ballet/sbpf/test_sbpf_loader.c @@ -49,12 +49,13 @@ void test_duplicate_entrypoint_entry( void ) { fd_sbpf_elf_peek( &info, duplicate_entrypoint_entry_elf, duplicate_entrypoint_entry_elf_sz, &config ); - void * rodata = fd_scratch_alloc( FD_SBPF_PROG_RODATA_ALIGN, info.rodata_footprint ); + void * rodata = fd_scratch_alloc( FD_SBPF_PROG_RODATA_ALIGN, info.bin_sz ); FD_TEST( rodata ); fd_sbpf_program_t * prog = fd_sbpf_program_new( fd_scratch_alloc( fd_sbpf_program_align(), fd_sbpf_program_footprint( &info ) ), &info, rodata ); fd_sbpf_syscalls_t * syscalls = fd_sbpf_syscalls_new( fd_scratch_alloc( fd_sbpf_syscalls_align(), fd_sbpf_syscalls_footprint() ) ); + for( uint const * x = _syscalls; *x; x++ ) fd_sbpf_syscalls_insert( syscalls, (ulong)*x ); @@ -63,8 +64,9 @@ void test_duplicate_entrypoint_entry( void ) { // end of boilerplate - FD_TEST( fd_sbpf_calldests_test( prog->calldests, 595 ) == 0 ); - FD_TEST( fd_sbpf_calldests_test( prog->calldests, 3920 ) == 1 ); + FD_TEST( fd_sbpf_calldests_test( prog->calldests, 595UL )==0 ); + FD_TEST( fd_sbpf_calldests_test( prog->calldests, 3920UL )==0 ); + FD_TEST( prog->entry_pc==3920UL ); } diff --git a/src/flamenco/runtime/program/fd_bpf_loader_program.c b/src/flamenco/runtime/program/fd_bpf_loader_program.c index 7a2fd2b10a9..17235c4ad92 100644 --- a/src/flamenco/runtime/program/fd_bpf_loader_program.c +++ b/src/flamenco/runtime/program/fd_bpf_loader_program.c @@ -177,7 +177,7 @@ fd_deploy_program( fd_exec_instr_ctx_t * instr_ctx, } /* Allocate rodata segment */ - void * rodata = fd_spad_alloc( spad, FD_SBPF_PROG_RODATA_ALIGN, elf_info->rodata_footprint ); + void * rodata = fd_spad_alloc( spad, FD_SBPF_PROG_RODATA_ALIGN, elf_info->bin_sz ); if( FD_UNLIKELY( !rodata ) ) { return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; } @@ -187,7 +187,7 @@ fd_deploy_program( fd_exec_instr_ctx_t * instr_ctx, ulong prog_footprint = fd_sbpf_program_footprint( elf_info ); fd_sbpf_program_t * prog = fd_sbpf_program_new( fd_spad_alloc( spad, prog_align, prog_footprint ), elf_info, rodata ); if( FD_UNLIKELY( !prog ) ) { - FD_LOG_ERR(( "fd_sbpf_program_new() failed: %s", fd_sbpf_strerror() )); + FD_LOG_ERR(( "fd_sbpf_program_new() failed" )); } /* Load program */ diff --git a/src/flamenco/runtime/program/fd_program_cache.c b/src/flamenco/runtime/program/fd_program_cache.c index 9e5ba18085e..393eb5ca4a2 100644 --- a/src/flamenco/runtime/program/fd_program_cache.c +++ b/src/flamenco/runtime/program/fd_program_cache.c @@ -31,7 +31,7 @@ fd_program_cache_entry_new( void * mem, cache_entry->calldests_shmem_off = l; /* rodata backing memory */ - l = FD_LAYOUT_APPEND( l, fd_sbpf_calldests_align(), fd_sbpf_calldests_footprint(elf_info->rodata_sz/8UL) ); + l = FD_LAYOUT_APPEND( l, fd_sbpf_calldests_align(), fd_sbpf_calldests_footprint(elf_info->text_cnt) ); cache_entry->rodata_off = l; /* SBPF version */ @@ -63,8 +63,8 @@ ulong fd_program_cache_entry_footprint( fd_sbpf_elf_info_t const * elf_info ) { ulong l = FD_LAYOUT_INIT; l = FD_LAYOUT_APPEND( l, alignof(fd_program_cache_entry_t), sizeof(fd_program_cache_entry_t) ); - l = FD_LAYOUT_APPEND( l, fd_sbpf_calldests_align(), fd_sbpf_calldests_footprint(elf_info->rodata_sz/8UL) ); - l = FD_LAYOUT_APPEND( l, 8UL, elf_info->rodata_footprint ); + l = FD_LAYOUT_APPEND( l, fd_sbpf_calldests_align(), fd_sbpf_calldests_footprint( elf_info->text_cnt ) ); + l = FD_LAYOUT_APPEND( l, 8UL, elf_info->bin_sz ); l = FD_LAYOUT_FINI( l, alignof(fd_program_cache_entry_t) ); return l; } @@ -110,7 +110,7 @@ fd_program_cache_parse_elf_info( fd_bank_t * bank, config.sbpf_max_version = max_sbpf_version; if( FD_UNLIKELY( fd_sbpf_elf_peek( elf_info, program_data, program_data_len, &config )<0 ) ) { - FD_LOG_DEBUG(( "fd_sbpf_elf_peek() failed: %s", fd_sbpf_strerror() )); + FD_LOG_DEBUG(( "fd_sbpf_elf_peek() failed" )); return -1; } return 0; @@ -284,7 +284,7 @@ fd_program_cache_validate_sbpf_program( fd_bank_t * bank, fd_sbpf_loader_config_t config = { 0 }; if( FD_UNLIKELY( 0!=fd_sbpf_program_load( prog, program_data, program_data_len, syscalls, &config ) ) ) { - FD_LOG_DEBUG(( "fd_sbpf_program_load() failed: %s", fd_sbpf_strerror() )); + FD_LOG_DEBUG(( "fd_sbpf_program_load() failed" )); cache_entry->failed_verification = 1; fd_sbpf_syscalls_leave( syscalls ); return -1; @@ -336,7 +336,7 @@ fd_program_cache_validate_sbpf_program( fd_bank_t * bank, } /* FIXME: Super expensive memcpy. */ - fd_memcpy( fd_program_cache_get_calldests_shmem( cache_entry ), prog->calldests_shmem, fd_sbpf_calldests_footprint( prog->rodata_sz/8UL ) ); + fd_memcpy( fd_program_cache_get_calldests_shmem( cache_entry ), prog->calldests_shmem, fd_sbpf_calldests_footprint( prog->text_cnt ) ); cache_entry->entry_pc = prog->entry_pc; cache_entry->text_off = prog->text_off; diff --git a/src/flamenco/runtime/tests/fd_elf_harness.c b/src/flamenco/runtime/tests/fd_elf_harness.c index dd3e469a9b1..79013a0f60f 100644 --- a/src/flamenco/runtime/tests/fd_elf_harness.c +++ b/src/flamenco/runtime/tests/fd_elf_harness.c @@ -1,8 +1,15 @@ #include "fd_solfuzz.h" +#include "fd_solfuzz_private.h" #include "generated/elf.pb.h" #include "../../../ballet/sbpf/fd_sbpf_loader.h" +#include "../program/fd_bpf_loader_program.h" #include "../../vm/fd_vm_base.h" +#define SORT_NAME sort_ulong +#define SORT_KEY_T ulong +#define SORT_BEFORE(a,b) (a)<(b) +#include "../../../util/tmpl/fd_sort.c" + ulong fd_solfuzz_elf_loader_run( fd_solfuzz_runner_t * runner, void const * input_, @@ -13,13 +20,15 @@ fd_solfuzz_elf_loader_run( fd_solfuzz_runner_t * runner, fd_exec_test_elf_loader_effects_t ** output = fd_type_pun( output_ ); fd_sbpf_elf_info_t info; + fd_spad_t * spad = runner->spad; if( FD_UNLIKELY( !input->has_elf || !input->elf.data ) ) { return 0UL; } - void const * elf_bin = input->elf.data->bytes; - ulong elf_sz = input->elf.data->size; + ulong elf_sz = input->elf.data->size; + void * elf_bin = fd_spad_alloc_check( spad, 8UL, elf_sz ); + fd_memcpy( elf_bin, input->elf.data->bytes, elf_sz ); // Allocate space for captured effects ulong output_end = (ulong)output_buf + output_bufsz; @@ -38,27 +47,39 @@ fd_solfuzz_elf_loader_run( fd_solfuzz_runner_t * runner, immediately if execution fails at any point */ do{ - - fd_sbpf_loader_config_t config = { 0 }; - config.elf_deploy_checks = input->deploy_checks; - config.sbpf_min_version = FD_SBPF_V0; - config.sbpf_max_version = FD_SBPF_V3; - if( FD_UNLIKELY( fd_sbpf_elf_peek( &info, elf_bin, elf_sz, &config )<0 ) ) { - /* return incomplete effects on execution failures */ + fd_features_t feature_set = {0}; + fd_runtime_fuzz_restore_features( &feature_set, &input->features ); + + fd_sbpf_loader_config_t config = { + .elf_deploy_checks = input->deploy_checks, + }; + fd_bpf_get_sbpf_versions( + &config.sbpf_min_version, + &config.sbpf_max_version, + UINT_MAX, + &feature_set ); + + int err = fd_sbpf_elf_peek( &info, elf_bin, elf_sz, &config ); + + if( FD_UNLIKELY( err ) ) { + /* TODO: Capture error code */ break; } - fd_spad_t * spad = runner->spad; - void * rodata = fd_spad_alloc_check( spad, FD_SBPF_PROG_RODATA_ALIGN, info.rodata_footprint ); - - fd_sbpf_program_t * prog = fd_sbpf_program_new( fd_spad_alloc_check( spad, fd_sbpf_program_align(), fd_sbpf_program_footprint( &info ) ), &info, rodata ); - + void * rodata = fd_spad_alloc_check( spad, FD_SBPF_PROG_RODATA_ALIGN, info.bin_sz ); + fd_sbpf_program_t * prog = fd_sbpf_program_new( fd_spad_alloc_check( spad, fd_sbpf_program_align(), fd_sbpf_program_footprint( &info ) ), &info, rodata ); fd_sbpf_syscalls_t * syscalls = fd_sbpf_syscalls_new( fd_spad_alloc_check( spad, fd_sbpf_syscalls_align(), fd_sbpf_syscalls_footprint() )); - fd_vm_syscall_register_all( syscalls, 0 ); + /* Register any syscalls given the active feature set */ + fd_vm_syscall_register_slot( + syscalls, + UINT_MAX /* Arbitrary slot, doesn't matter */, + &feature_set, + !!config.elf_deploy_checks ); - int res = fd_sbpf_program_load( prog, elf_bin, elf_sz, syscalls, &config ); - if( FD_UNLIKELY( res ) ) { + err = fd_sbpf_program_load( prog, elf_bin, elf_sz, syscalls, &config ); + if( FD_UNLIKELY( err ) ) { + /* TODO: Capture error code */ break; } @@ -77,20 +98,26 @@ fd_solfuzz_elf_loader_run( fd_solfuzz_runner_t * runner, 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)); + pb_size_t max_calldests_sz = (pb_size_t)fd_sbpf_calldests_cnt( prog->calldests)+1U; + elf_effects->calldests = FD_SCRATCH_ALLOC_APPEND(l, 8UL, max_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; + /* Add the entrypoint to the calldests */ + elf_effects->calldests[elf_effects->calldests_count++] = prog->entry_pc; + + /* Add the rest of the calldests */ + 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) ) { + if( FD_LIKELY( target_pc!=prog->entry_pc ) ) { + elf_effects->calldests[elf_effects->calldests_count++] = target_pc; + } } + + /* Sort the calldests in ascending order */ + sort_ulong_inplace( elf_effects->calldests, elf_effects->calldests_count ); } while(0); ulong actual_end = FD_SCRATCH_ALLOC_FINI( l, 1UL ); diff --git a/src/flamenco/vm/fd_vm.c b/src/flamenco/vm/fd_vm.c index bf810ec4f81..6e413b52375 100644 --- a/src/flamenco/vm/fd_vm.c +++ b/src/flamenco/vm/fd_vm.c @@ -331,7 +331,7 @@ fd_vm_validate( fd_vm_t const * vm ) { /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/verifier.rs#L233-L235 */ ulong function_start = 0UL; ulong function_next = 0UL; - if( fd_sbpf_enable_stricter_elf_headers(sbpf_version) ) { + if( fd_sbpf_enable_stricter_elf_headers_enabled( sbpf_version ) ) { if( FD_UNLIKELY( !fd_sbpf_is_function_start( fd_sbpf_instr( text[0] ) ) ) ) { return FD_VM_INVALID_FUNCTION; } @@ -347,7 +347,7 @@ fd_vm_validate( fd_vm_t const * vm ) { used to validate jumps. Note that the first function always starts at 0, and similarly the last function always ends at text_cnt-1. */ - if( FD_UNLIKELY( fd_sbpf_enable_stricter_elf_headers(sbpf_version) && fd_sbpf_is_function_start( instr ) ) ) { + if( FD_UNLIKELY( fd_sbpf_enable_stricter_elf_headers_enabled( sbpf_version ) && fd_sbpf_is_function_start( instr ) ) ) { function_start = i; function_next = i+1; while( function_next=frame_max ) ) goto sigstack; /* Note: untaken branches don't consume BTB */ \ - if( !FD_VM_SBPF_DYNAMIC_STACK_FRAMES( sbpf_version ) ) reg[10] += vm->stack_frame_size; + if( !fd_sbpf_dynamic_stack_frames_enabled( sbpf_version ) ) reg[10] += vm->stack_frame_size; /* We subtract the heap cost in the BPF loader */ @@ -799,7 +799,7 @@ FD_VM_INTERP_STACK_PUSH; - ulong vaddr = FD_VM_SBPF_CALLX_USES_SRC_REG(sbpf_version) ? reg_src : reg[ imm & 15U ]; + ulong vaddr = fd_sbpf_callx_uses_src_reg_enabled( sbpf_version ) ? reg_src : reg[ imm & 15U ]; /* Notes: Agave checks region and target_pc before updating the pc. To match their state, we do the same, even though we could simply diff --git a/src/flamenco/vm/fd_vm_private.h b/src/flamenco/vm/fd_vm_private.h index 6865491d3bc..6297b6af831 100644 --- a/src/flamenco/vm/fd_vm_private.h +++ b/src/flamenco/vm/fd_vm_private.h @@ -77,8 +77,7 @@ typedef struct fd_vm_vec fd_vm_vec_t; FD_STATIC_ASSERT( sizeof(fd_vm_vec_t)==FD_VM_VEC_SIZE, fd_vm_vec size mismatch ); /* SBPF version and features - https://github.com/solana-labs/rbpf/blob/4b2c3dfb02827a0119cd1587eea9e27499712646/src/program.rs#L22 - + https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/program.rs#L28 Note: SIMDs enable or disable features, e.g. BPF instructions. If we have macros with names ENABLE vs DISABLE, we have the advantage that the condition is always pretty clear: sbpf_version <= activation_version, @@ -112,8 +111,6 @@ FD_STATIC_ASSERT( sizeof(fd_vm_vec_t)==FD_VM_VEC_SIZE, fd_vm_vec size mismatch ) by the ELF loader, not really by the VM #define FD_VM_SBPF_ENABLE_STRICTER_ELF_HEADERS(v) ( v >= FD_SBPF_V3 ) */ -#define FD_VM_SBPF_DYNAMIC_STACK_FRAMES_ALIGN (64U) - #define FD_VM_OFFSET_MASK (0xffffffffUL) FD_PROTOTYPES_BEGIN @@ -246,7 +243,7 @@ fd_vm_generate_access_violation( ulong vaddr, ulong sbpf_version ) { stack access violation. */ long rel_offset = fd_long_sat_sub( (long)vaddr, (long)FD_VM_MEM_MAP_STACK_REGION_START ); long stack_frame = rel_offset / (long)FD_VM_STACK_FRAME_SZ; - if( !FD_VM_SBPF_DYNAMIC_STACK_FRAMES( sbpf_version ) && + if( !fd_sbpf_dynamic_stack_frames_enabled( sbpf_version ) && stack_frame>=-1L && stack_frame<=(long)FD_VM_MAX_CALL_DEPTH ) { return FD_VM_ERR_EBPF_STACK_ACCESS_VIOLATION; } @@ -381,7 +378,7 @@ fd_vm_mem_haddr( fd_vm_t const * vm, */ if( FD_UNLIKELY( region==FD_VM_STACK_REGION && !vm->direct_mapping && - !FD_VM_SBPF_DYNAMIC_STACK_FRAMES( vm->sbpf_version ) ) ) { + !fd_sbpf_dynamic_stack_frames_enabled( vm->sbpf_version ) ) ) { /* If an access starts in a gap region, that is an access violation */ if( FD_UNLIKELY( !!(vaddr & 0x1000) ) ) { return sentinel; diff --git a/src/flamenco/vm/fd_vm_tool.c b/src/flamenco/vm/fd_vm_tool.c index 328ead6c2c2..e86bde1e64d 100644 --- a/src/flamenco/vm/fd_vm_tool.c +++ b/src/flamenco/vm/fd_vm_tool.c @@ -56,7 +56,7 @@ fd_vm_tool_prog_create( fd_vm_tool_prog_t * tool_prog, /* Allocate rodata segment */ - void * rodata = malloc( elf_info.rodata_footprint ); + void * rodata = malloc( elf_info.bin_sz ); FD_TEST( rodata ); /* Allocate program buffer */ @@ -75,7 +75,7 @@ fd_vm_tool_prog_create( fd_vm_tool_prog_t * tool_prog, /* Load program */ if( FD_UNLIKELY( 0!=fd_sbpf_program_load( prog, bin_buf, bin_sz, syscalls, &config ) ) ) - FD_LOG_ERR(( "fd_sbpf_program_load() failed: %s", fd_sbpf_strerror() )); + FD_LOG_ERR(( "fd_sbpf_program_load() failed" )); tool_prog->bin_buf = bin_buf; tool_prog->prog = prog;