LCOV - code coverage report
Current view: top level - ballet/sbpf - fd_sbpf_loader.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 762 1010 75.4 %
Date: 2026-05-15 07:18:56 Functions: 26 26 100.0 %

          Line data    Source code
       1             : #include "fd_sbpf_loader.h"
       2             : #include "fd_sbpf_instr.h"
       3             : #include "fd_sbpf_opcodes.h"
       4             : #include "../../util/bits/fd_sat.h"
       5             : #include "../murmur3/fd_murmur3.h"
       6             : 
       7             : #include <assert.h>
       8             : #include <stdio.h>
       9             : 
      10             : /* ELF loader, part 1 **************************************************
      11             : 
      12             :    Start with a static piece of scratch memory and do basic validation
      13             :    of the file content.  Walk the section table once and remember
      14             :    sections of interest.
      15             : 
      16             :    ### Terminology
      17             : 
      18             :    This source follows common ELF naming practices.
      19             : 
      20             :      section:  a named data region present in the ELF file
      21             :      segment:  a contiguous memory region containing sections
      22             :                (not necessarily contiguous in the ELF file)
      23             : 
      24             :      physical address (paddr): Byte offset into ELF file (uchar * bin)
      25             :      virtual  address (vaddr): VM memory address */
      26             : 
      27             : /* Provide convenient access to file header and ELF content */
      28             : 
      29             : __extension__ union fd_sbpf_elf {
      30             :   fd_elf64_ehdr ehdr;
      31             :   uchar         bin[0];
      32             : };
      33             : typedef union fd_sbpf_elf fd_sbpf_elf_t;
      34             : 
      35             : /* FD_SBPF_MM_{...}_ADDR are hardcoded virtual addresses of segments
      36             :    in the sBPF virtual machine.
      37             : 
      38             :    FIXME: These should be defined elsewhere */
      39             : 
      40             : /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/ebpf.rs#L42-L43 */
      41          36 : #define FD_SBPF_MM_RODATA_START   (0x0UL)         /* readonly data */
      42             : /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/ebpf.rs#L44-L45 */
      43       38613 : #define FD_SBPF_MM_BYTECODE_START (0x100000000UL) /* bytecode / program region */
      44       25524 : #define FD_SBPF_MM_PROGRAM_ADDR   FD_SBPF_MM_BYTECODE_START
      45             : #define FD_SBPF_MM_STACK_ADDR     (0x200000000UL) /* stack */
      46             : #define FD_SBPF_MM_HEAP_ADDR      (0x300000000UL) /* heap */
      47          57 : #define FD_SBPF_MM_REGION_SZ      (0x100000000UL) /* max region size */
      48             : 
      49          36 : #define FD_SBPF_PF_X  (1U) /* executable */
      50             : #define FD_SBPF_PF_W  (2U) /* writable */
      51          48 : #define FD_SBPF_PF_R  (4U) /* readable */
      52             : #define FD_SBPF_PF_RW (FD_SBPF_PF_R|FD_SBPF_PF_W)
      53             : 
      54             : #define EXPECTED_PHDR_CNT (4U)
      55             : 
      56             : struct fd_sbpf_range {
      57             :   ulong lo;
      58             :   ulong hi;
      59             : };
      60             : typedef struct fd_sbpf_range fd_sbpf_range_t;
      61             : 
      62             : /* fd_sbpf_range_contains returns 1 if x is in the range
      63             :    [range.lo, range.hi) and 0 otherwise. */
      64             : static inline int
      65       15492 : fd_sbpf_range_contains( fd_sbpf_range_t const * range, ulong x ) {
      66       15492 :   return !!(( range->lo<=x ) & ( x<range->hi ));
      67       15492 : }
      68             : 
      69             : /* Mimics Elf64Shdr::file_range(). Returns a pointer to range (Some) if
      70             :    the section header type is not SHT_NOBITS, and sets range.{lo, hi} to
      71             :    the section header offset and offset + size, respectively. Returns
      72             :    NULL (None) otherwise, and sets both range.{lo, hi} to 0 (the default
      73             :    values for a Rust Range type).
      74             : 
      75             :    https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L87-L93 */
      76             : 
      77             : static fd_sbpf_range_t *
      78             : fd_shdr_get_file_range( fd_elf64_shdr const * shdr,
      79       30924 :                         fd_sbpf_range_t *     range ) {
      80       30924 :   if( shdr->sh_type==FD_ELF_SHT_NOBITS ) {
      81           0 :     *range = (fd_sbpf_range_t) { .lo = 0UL, .hi = 0UL };
      82           0 :     return NULL;
      83       30924 :   } else {
      84       30924 :     *range = (fd_sbpf_range_t) { .lo = shdr->sh_offset, .hi = fd_ulong_sat_add( shdr->sh_offset, shdr->sh_size ) };
      85       30924 :     return range;
      86       30924 :   }
      87       30924 : }
      88             : 
      89             : /* Converts an ElfParserError code to an ElfError code.
      90             :    https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L112-L132 */
      91             : static int
      92          57 : fd_sbpf_elf_parser_err_to_elf_err( int err ) {
      93          57 :   switch( err ) {
      94          21 :     case FD_SBPF_ELF_SUCCESS:
      95          21 :       return err;
      96           3 :     case FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS:
      97           3 :       return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
      98           6 :     case FD_SBPF_ELF_PARSER_ERR_INVALID_PROGRAM_HEADER:
      99           6 :       return FD_SBPF_ELF_ERR_INVALID_PROGRAM_HEADER;
     100          27 :     default:
     101          27 :       return FD_SBPF_ELF_ERR_FAILED_TO_PARSE;
     102          57 :   }
     103          57 : }
     104             : 
     105             : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L11-L13 */
     106       22218 : #define FD_SBPF_SECTION_NAME_SZ_MAX (16UL)
     107             : #define FD_SBPF_SYMBOL_NAME_SZ_MAX  (64UL)
     108             : 
     109             : /* ELF loader, part 2 **************************************************
     110             : 
     111             :    Prepare a copy of a subrange of the ELF content: The rodata segment.
     112             :    Mangle the copy by applying dynamic relocations.  Then, zero out
     113             :    parts of the segment that are not interesting to the loader.
     114             : 
     115             :    ### Terminology
     116             : 
     117             :    Shorthands for relocation handling:
     118             : 
     119             :      S: Symbol value (typically an ELF physical address)
     120             :      A: Implicit addend, i.e. the original value of the field that the
     121             :         relocation handler is about to write to
     122             :      V: Virtual address, i.e. the target value that the relocation
     123             :         handler is about to write into where the implicit addend was
     124             :         previously stored */
     125             : 
     126             : ulong
     127          42 : fd_sbpf_program_align( void ) {
     128          42 :   return alignof( fd_sbpf_program_t );
     129          42 : }
     130             : 
     131             : ulong
     132          42 : fd_sbpf_program_footprint( fd_sbpf_elf_info_t const * info ) {
     133          42 :   FD_COMPILER_UNPREDICTABLE( info ); /* Make this appear as FD_FN_PURE (e.g. footprint might depened on info contents in future) */
     134          42 :   if( FD_UNLIKELY( fd_sbpf_enable_stricter_elf_headers_enabled( info->sbpf_version ) ) ) {
     135             :     /* SBPF v3+ no longer needs calldests bitmap */
     136           6 :     return FD_LAYOUT_FINI( FD_LAYOUT_APPEND( FD_LAYOUT_INIT,
     137           6 :       alignof(fd_sbpf_program_t), sizeof(fd_sbpf_program_t) ),
     138           6 :       alignof(fd_sbpf_program_t) );
     139           6 :   }
     140          36 :   return FD_LAYOUT_FINI( FD_LAYOUT_APPEND( FD_LAYOUT_APPEND( FD_LAYOUT_INIT,
     141          42 :     alignof(fd_sbpf_program_t), sizeof(fd_sbpf_program_t) ),
     142          42 :     fd_sbpf_calldests_align(), fd_sbpf_calldests_footprint( info->calldests_max ) ),  /* calldests bitmap */
     143          42 :     alignof(fd_sbpf_program_t) );
     144          42 : }
     145             : 
     146             : fd_sbpf_program_t *
     147             : fd_sbpf_program_new( void *                     prog_mem,
     148             :                      fd_sbpf_elf_info_t const * elf_info,
     149          51 :                      void *                     rodata ) {
     150             : 
     151          51 :   if( FD_UNLIKELY( !prog_mem ) ) {
     152           0 :     FD_LOG_WARNING(( "NULL prog_mem" ));
     153           0 :     return NULL;
     154           0 :   }
     155             : 
     156          51 :   if( FD_UNLIKELY( !elf_info ) ) {
     157           0 :     FD_LOG_WARNING(( "NULL elf_info" ));
     158           0 :     return NULL;
     159           0 :   }
     160             : 
     161          51 :   if( FD_UNLIKELY( ((elf_info->bin_sz)>0U) & (!rodata)) ) {
     162           0 :     FD_LOG_WARNING(( "NULL rodata" ));
     163           0 :     return NULL;
     164           0 :   }
     165             : 
     166             :   /* https://github.com/solana-labs/rbpf/blob/v0.8.0/src/elf_parser/mod.rs#L99 */
     167          51 :   if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong) rodata, FD_SBPF_PROG_RODATA_ALIGN ) ) ){
     168           0 :     FD_LOG_WARNING(( "rodata is not 8-byte aligned" ));
     169           0 :     return NULL;
     170           0 :   }
     171             : 
     172             :   /* Initialize program struct */
     173             : 
     174          51 :   FD_SCRATCH_ALLOC_INIT( laddr, prog_mem );
     175          51 :   fd_sbpf_program_t * prog = FD_SCRATCH_ALLOC_APPEND( laddr, alignof(fd_sbpf_program_t), sizeof(fd_sbpf_program_t) );
     176             : 
     177             :   /* Note that entry_pc and rodata_sz get set during the loading phase. */
     178           0 :   *prog = (fd_sbpf_program_t) {
     179          51 :     .info            = *elf_info,
     180          51 :     .rodata          = rodata,
     181          51 :     .rodata_sz       = 0UL,
     182          51 :     .text            = (ulong *)((ulong)rodata + elf_info->text_off), /* FIXME: WHAT IF MISALIGNED */
     183          51 :     .entry_pc        = ULONG_MAX,
     184          51 :     .calldests_shmem = NULL,
     185          51 :     .calldests       = NULL,
     186          51 :   };
     187             : 
     188             :   /* If the text section is empty, or the program is SBPF V3+, then we
     189             :      do not need a calldests map. */
     190          51 :   ulong pc_max = elf_info->calldests_max;
     191          51 :   if( FD_LIKELY( ( !fd_sbpf_enable_stricter_elf_headers_enabled( elf_info->sbpf_version ) ) && pc_max!=0UL ) ) {
     192          39 :     prog->calldests_shmem = fd_sbpf_calldests_new(
     193          39 :           FD_SCRATCH_ALLOC_APPEND( laddr, fd_sbpf_calldests_align(),
     194          39 :                                           fd_sbpf_calldests_footprint( pc_max ) ),
     195           0 :           pc_max );
     196           0 :     prog->calldests = fd_sbpf_calldests_join( prog->calldests_shmem );
     197          39 :   }
     198             : 
     199          51 :   return prog;
     200          51 : }
     201             : 
     202             : void *
     203          30 : fd_sbpf_program_delete( fd_sbpf_program_t * mem ) {
     204             : 
     205          30 :   if( FD_LIKELY( mem->calldests ) ) {
     206          30 :     fd_sbpf_calldests_delete( fd_sbpf_calldests_leave( mem->calldests ) );
     207          30 :   }
     208          30 :   fd_memset( mem, 0, sizeof(fd_sbpf_program_t) );
     209             : 
     210          30 :   return (void *)mem;
     211          30 : }
     212             : 
     213             : /* fd_sbpf_loader_t contains various temporary state during loading. */
     214             : 
     215             : struct fd_sbpf_loader {
     216             :   /* External objects */
     217             :   ulong *              calldests; /* owned by program. NULL if calldests_max = 0 or SBPF v3+ */
     218             :   fd_sbpf_syscalls_t * syscalls;  /* owned by caller */
     219             : };
     220             : typedef struct fd_sbpf_loader fd_sbpf_loader_t;
     221             : 
     222             : /* fd_sbpf_slice_cstr_eq is a helper method for checking equality
     223             :    between a slice of memory to a null-terminated C-string.  Unlike
     224             :    strcmp, this function does not include the null-terminator in the
     225             :    comparison.  Returns 1 if the first slice_len bytes of the slice and
     226             :    cstr are equal, and 0 otherwise. */
     227             : static inline int
     228             : fd_sbpf_slice_cstr_eq( uchar const * slice,
     229             :                        ulong         slice_len,
     230       90297 :                        char const *  cstr ) {
     231       90297 :   return !!(slice_len==strlen( cstr ) && fd_memeq( slice, cstr, slice_len ));
     232       90297 : }
     233             : 
     234             : /* fd_sbpf_slice_cstr_start_with is a helper method for checking that a
     235             :    null-terminated C-string is a prefix of a slice of memory.  Returns 1
     236             :    if the first strlen(cstr) bytes of cstr is a prefix of slice, and 0
     237             :    otherwise. */
     238             : static inline int
     239             : fd_sbpf_slice_cstr_start_with( uchar const * slice,
     240             :                                ulong         slice_len,
     241       11460 :                                char const *  cstr ) {
     242       11460 :   ulong cstr_len = strlen( cstr );
     243       11460 :   return !!(slice_len>=cstr_len && fd_memeq( slice, cstr, cstr_len ));
     244       11460 : }
     245             : 
     246             : /* fd_sbpf_lenient_get_string_in_section queries a single string from a
     247             :    section which is marked as SHT_STRTAB.  Returns an ElfParserError on
     248             :    failure, and leaves *out_slice and *out_slice_len in an undefined
     249             :    state.  On success, returns 0 and sets *out_slice to a pointer into
     250             :    elf_bytes corresponding to the beginning of the string within the
     251             :    section.  *out_slice_len is set to the length of the resulting slice.
     252             :    Note that *out_slice_len does not include the null-terminator of the
     253             :    resulting string.
     254             :    https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L467-L496 */
     255             : int
     256             : fd_sbpf_lenient_get_string_in_section( uchar const *         elf_bytes,
     257             :                                        ulong                 elf_bytes_len,
     258             :                                        fd_elf64_shdr const * section_header,
     259             :                                        uint                  offset_in_section,
     260             :                                        ulong                 maximum_length,
     261             :                                        uchar const **        out_slice,
     262       38280 :                                        ulong *               out_slice_len ) {
     263             :   /* This could be checked only once outside the loop, but to keep the code the same...
     264             :      https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L474-L476 */
     265       38280 :   if( FD_UNLIKELY( section_header->sh_type!=FD_ELF_SHT_STRTAB ) ) {
     266           0 :     return FD_SBPF_ELF_PARSER_ERR_INVALID_SECTION_HEADER;
     267           0 :   }
     268             : 
     269             :   /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L477-L482 */
     270       38280 :   ulong offset_in_file;
     271       38280 :   if( FD_UNLIKELY( __builtin_uaddl_overflow( section_header->sh_offset, offset_in_section, &offset_in_file ) ) ) {
     272           0 :     return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
     273           0 :   }
     274             : 
     275       38280 :   ulong string_range_start = offset_in_file;
     276       38280 :   ulong string_range_end   = fd_ulong_min( section_header->sh_offset+section_header->sh_size, offset_in_file+maximum_length );
     277       38280 :   if( FD_UNLIKELY( string_range_end>elf_bytes_len ) ) {
     278           0 :     return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
     279           0 :   }
     280             :   /* In rust vec.get([n..n]) returns [], so this is accepted.
     281             :      vec.get([n..m]) with m<n returns None, so it throws ElfParserError::OutOfBounds. */
     282       38280 :   if( FD_UNLIKELY( string_range_end<string_range_start ) ) {
     283           0 :     return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
     284           0 :   }
     285             : 
     286             :   /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L486-L495 */
     287       38280 :   uchar * null_terminator_ptr = memchr( (uchar const *)elf_bytes+string_range_start, 0, string_range_end-string_range_start );
     288       38280 :   if( FD_UNLIKELY( null_terminator_ptr==NULL ) ) {
     289           3 :     return FD_SBPF_ELF_PARSER_ERR_STRING_TOO_LONG;
     290           3 :   }
     291             : 
     292       38277 :   *out_slice     = elf_bytes+string_range_start;
     293       38277 :   *out_slice_len = (ulong)(null_terminator_ptr-*out_slice);
     294             : 
     295       38277 :   return FD_SBPF_ELF_SUCCESS;
     296       38280 : }
     297             : 
     298             : /* Registers a target PC into the calldests function registry. Returns
     299             :    0 on success, inserts the target PC into the calldests, and sets
     300             :    *opt_out_pc_hash to murmur3_32(target_pc) (if opt_out_pc_hash is
     301             :    non-NULL). Returns FD_SBPF_ELF_ERR_SYMBOL_HASH_COLLISION on failure
     302             :    if the target PC is already in the syscalls registry and leaves
     303             :    out_pc_hash in an undefined state.
     304             : 
     305             :    An important note is that Agave's implementation uses a map to store
     306             :    key-value pairs of (murmur3_32(target_pc), target_pc) within the
     307             :    calldests. We optimize this by using a set containing
     308             :    target_pc (this is our calldests map), and then deriving
     309             :    the target PC on the fly given murmur3_32(target_pc) (provided as
     310             :    imm) in the VM by computing the inverse hash (since murmur3_32 is
     311             :    bijective for uints).
     312             : 
     313             :    Another important note is that if a key-value pair already exists in
     314             :    Agave's calldests map, they will only throw a symbol hash collision
     315             :    error if the target PC is different from the one already registered.
     316             :    We can omit this check because of the hash function's bijective
     317             :    property, since the key-value pairs are deterministically derived
     318             :    from one another.
     319             : 
     320             :    TODO: this function will have to be adapted to hash the target PC
     321             :    depending on the SBPF version (>= V3). That has not been implemented
     322             :    yet.
     323             : 
     324             :    https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/program.rs#L142-L178 */
     325             : static int
     326             : fd_sbpf_register_function_hashed_legacy( fd_sbpf_loader_t *  loader,
     327             :                                          fd_sbpf_program_t * prog,
     328             :                                          uchar const *       name,
     329             :                                          ulong               name_len,
     330             :                                          ulong               target_pc,
     331       14925 :                                          uint *              opt_out_pc_hash ) {
     332             :   /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/program.rs#L156-L160 */
     333       14925 :   uint pc_hash;
     334       14925 :   uchar is_entrypoint = fd_sbpf_slice_cstr_eq( name, name_len, "entrypoint" ) ||
     335       14925 :                         target_pc==FD_SBPF_ENTRYPOINT_PC;
     336       14925 :   if( FD_UNLIKELY( is_entrypoint ) ) {
     337        2586 :     if( FD_UNLIKELY( prog->entry_pc!=ULONG_MAX && prog->entry_pc!=target_pc  ) ) {
     338             :       /* We already registered the entrypoint to a different target PC,
     339             :          so we cannot register it again. */
     340           0 :       return FD_SBPF_ELF_ERR_SYMBOL_HASH_COLLISION;
     341           0 :     }
     342        2586 :     prog->entry_pc = target_pc;
     343             : 
     344             :     /* Optimization for this constant value */
     345        2586 :     pc_hash = FD_SBPF_ENTRYPOINT_HASH;
     346       12339 :   } else {
     347       12339 :     pc_hash = fd_pchash( (uint)target_pc );
     348       12339 :   }
     349             : 
     350             :   /* loader.get_function_registry() is their equivalent of our syscalls
     351             :      registry. Fail if the target PC is present there.
     352             : 
     353             :      https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/program.rs#L161-L163 */
     354       14925 :   if( FD_UNLIKELY( fd_sbpf_syscalls_query( loader->syscalls, pc_hash, NULL ) ) ) {
     355           0 :     return FD_SBPF_ELF_ERR_SYMBOL_HASH_COLLISION;
     356           0 :   }
     357             : 
     358             :   /* Insert the target PC into the calldests set if it's not the
     359             :      entrypoint.  Due to the nature of our calldests, we also want to
     360             :      make sure that target_pc <= calldests_max, the call destination is
     361             :      guaranteed not a valid program counter (therefore does not need to
     362             :      be registered). */
     363       14925 :   if( FD_LIKELY( !is_entrypoint &&
     364       14925 :                  loader->calldests &&
     365       14925 :                  fd_sbpf_calldests_valid_idx( loader->calldests, target_pc ) ) ) {
     366       12339 :     fd_sbpf_calldests_insert( loader->calldests, target_pc );
     367       12339 :   }
     368             : 
     369       14925 :   if( opt_out_pc_hash ) *opt_out_pc_hash = pc_hash;
     370       14925 :   return FD_SBPF_ELF_SUCCESS;
     371       14925 : }
     372             : 
     373             : /* ELF Dynamic Relocations *********************************************
     374             : 
     375             :    ### Summary
     376             : 
     377             :    The sBPF ELF loader provides a limited dynamic relocation mechanism
     378             :    to fix up Clang-generated shared objects for execution in an sBPF VM.
     379             : 
     380             :    The relocation types themselves violate the eBPF and ELF specs in
     381             :    various ways.  In short, the relocation table (via DT_REL) is used to
     382             :    shift program code from zero-based addressing to the MM_PROGRAM
     383             :    segment in the VM memory map (at 0x1_0000_0000).
     384             : 
     385             :    As part of the Solana VM protocol it abides by strict determinism
     386             :    requirements.  This sadly means that we will have to replicate all
     387             :    edge cases and bugs in the Solana Labs ELF loader.
     388             : 
     389             :    Three relocation types are currently supported:
     390             :      - R_BPF_64_64: Sets an absolute address of a symbol as the
     391             :        64-bit immediate field of an lddw instruction
     392             :      - R_BPF_64_RELATIVE: Adds MM_PROGRAM_START (0x1_0000_0000) to ...
     393             :        a) ... the 64-bit imm field of an lddw instruction (if in text)
     394             :        b) ... a 64-bit integer (if not in text section)
     395             :      - R_BPF_64_32: Sets the 32-bit immediate field of a call
     396             :        instruction to ...
     397             :        a) the ID of a local function (Murmur3 hash of function PC address)
     398             :        b) the ID of a syscall
     399             : 
     400             :    Obviously invalid relocations (e.g. out-of-bounds of ELF file or
     401             :    unsupported reloc type) raise an error.
     402             :    Relocations that would corrupt ELF data structures are silently
     403             :    ignored (using the fd_sbpf_reloc_mask mechanism).
     404             : 
     405             :    ### History
     406             : 
     407             :    The use of relocations is technically redundant, as the Solana VM
     408             :    memory map has been hardcoded in program runtime v1 (so far the only
     409             :    runtime).  However, virtually all deployed programs as of April 2023
     410             :    are position-independent shared objects and make heavy use of such
     411             :    relocations.
     412             : 
     413             :    Relocations in the Solana VM have a complicated history.  Over the
     414             :    course of years, multiple protocol bugs have been added and fixed.
     415             :    The ELF loader needs to handle all these edge cases to avoid breaking
     416             :    "userspace".  I.e. any deployed programs which might be immutable
     417             :    must continue to function.
     418             : 
     419             :    While this complex logic will probably stick around for the next few
     420             :    years, the Solana protocol is getting increasingly restrictive for
     421             :    newly deployed ELFs.  Another proposed change is upgrading to
     422             :    position-dependent binaries without any dynamic relocations. */
     423             : 
     424             : /* R_BPF_64_64 relocates an absolute address into the extended imm field
     425             :    of an lddw-form instruction.  (Two instruction slots, low 32 bits in
     426             :    first immediate field, high 32 bits in second immediate field)
     427             : 
     428             :     Bits  0..32    32..64   64..96   96..128
     429             :          [ ... ] [ IMM_LO ] [ ... ] [ IMM_HI ]
     430             : 
     431             :     Returns 0 on success and writes the imm offset to the rodata.
     432             :     Returns the error code on failure.
     433             : 
     434             :     https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1069-L1141 */
     435             : 
     436             : static int
     437             : fd_sbpf_r_bpf_64_64( fd_sbpf_elf_t const *      elf,
     438             :                      ulong                      elf_sz,
     439             :                      uchar                    * rodata,
     440             :                      fd_sbpf_elf_info_t const * info,
     441             :                      fd_elf64_rel       const * dt_rel,
     442           6 :                      ulong                      r_offset ) {
     443             : 
     444           6 :   fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
     445             : 
     446             :   /* Note that the sbpf_version variable is ALWAYS V0 (see Agave's code
     447             :      to understand why).
     448             :      https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1070-L1080 */
     449           6 :   ulong imm_offset = fd_ulong_sat_add( r_offset, 4UL /* BYTE_OFFSET_IMMEDIATE */ );
     450             : 
     451             :   /* Bounds check.
     452             :      https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1084-L1086 */
     453           6 :   if( FD_UNLIKELY( fd_ulong_sat_add( imm_offset, 4UL /* BYTE_LENGTH_IMMEDIATE */ )>elf_sz ) ) {
     454           0 :     return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
     455           0 :   }
     456             : 
     457             :   /* Get the symbol entry from the dynamic symbol table.
     458             :      https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1089-L1092 */
     459           6 :   fd_elf64_sym const * symbol = NULL;
     460           6 :   {
     461             :     /* Ensure the dynamic symbol table exists. */
     462           6 :     if( FD_UNLIKELY( info->shndx_dynsymtab<0 ) ) {
     463           0 :       return FD_SBPF_ELF_ERR_UNKNOWN_SYMBOL;
     464           0 :     }
     465             : 
     466             :     /* Get the dynamic symbol table section header. The section header
     467             :        was already validated in fd_sbpf_lenient_elf_parse() so we can
     468             :        directly get the symbol table. */
     469           6 :     fd_elf64_shdr const * sh_dynsym    = &shdrs[ info->shndx_dynsymtab ];
     470           6 :     fd_elf64_sym const *  dynsym_table = (fd_elf64_sym const *)( elf->bin + sh_dynsym->sh_offset );
     471           6 :     ulong                 dynsym_cnt   = (ulong)(sh_dynsym->sh_size / sizeof(fd_elf64_sym));
     472             : 
     473             :     /* The symbol table index is stored in the lower 4 bytes of r_info.
     474             :        Check the bounds of the symbol table index. */
     475           6 :     ulong r_sym = FD_ELF64_R_SYM( dt_rel->r_info );
     476           6 :     if( FD_UNLIKELY( r_sym>=dynsym_cnt ) ) {
     477           0 :       return FD_SBPF_ELF_ERR_UNKNOWN_SYMBOL;
     478           0 :     }
     479           6 :     symbol = &dynsym_table[ r_sym ];
     480           6 :   }
     481             : 
     482             :   /* Use the relative address as an offset to derive the relocated
     483             :      address.
     484             :      https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1094-L1096 */
     485           6 :   uint  refd_addr = FD_LOAD( uint, &rodata[ imm_offset ] );
     486           6 :   ulong addr      = fd_ulong_sat_add( symbol->st_value, refd_addr );
     487             : 
     488             :   /* We need to normalize the address into the VM's memory space, which
     489             :      is rooted at 0x1_0000_0000 (the program ro-data region). If the
     490             :      linker hasn't normalized the addresses already, we treat addr as
     491             :      a relative offset into the program ro-data region. */
     492           6 :   if( addr<FD_SBPF_MM_PROGRAM_ADDR ) {
     493           6 :     addr = fd_ulong_sat_add( addr, FD_SBPF_MM_PROGRAM_ADDR );
     494           6 :   }
     495             : 
     496             :   /* Again, no need to check the sbpf_version because it's always V0.
     497             :      https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1106-L1140 */
     498           6 :   ulong imm_low_offset  = imm_offset;
     499           6 :   ulong imm_high_offset = fd_ulong_sat_add( imm_low_offset, 8UL /* INSN_SIZE */ );
     500             : 
     501             :   /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1116-L1122 */
     502           6 :   {
     503             :     /* Bounds check before writing to the rodata. */
     504           6 :     if( FD_UNLIKELY( fd_ulong_sat_add( imm_low_offset, 4UL /* BYTE_LENGTH_IMMEDIATE */ )>elf_sz ) ) {
     505           0 :       return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
     506           0 :     }
     507             : 
     508             :     /* Write back */
     509           6 :     FD_STORE( uint, rodata+imm_low_offset, (uint)addr );
     510           6 :   }
     511             : 
     512             :   /* Same as above, but for the imm high offset.
     513             :      https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1125-L1134 */
     514           0 :   {
     515             :     /* Bounds check before writing to the rodata. */
     516           6 :     if( FD_UNLIKELY( fd_ulong_sat_add( imm_high_offset, 4UL /* BYTE_LENGTH_IMMEDIATE */ )>elf_sz ) ) {
     517           0 :       return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
     518           0 :     }
     519             : 
     520             :     /* Write back */
     521           6 :     FD_STORE( uint, rodata+imm_high_offset, (uint)(addr>>32UL) );
     522           6 :   }
     523             : 
     524             :   /* ...rest of this function is a no-op because
     525             :      enable_symbol_and_section_labels is disabled in production. */
     526             : 
     527           6 :   return FD_SBPF_ELF_SUCCESS;
     528           6 : }
     529             : 
     530             : /* R_BPF_64_RELATIVE is almost entirely Solana specific. Returns 0 on
     531             :    success and an ElfError on failure.
     532             : 
     533             :    https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1142-L1247 */
     534             : 
     535             : static int
     536             : fd_sbpf_r_bpf_64_relative( fd_sbpf_elf_t const *      elf,
     537             :                            ulong                      elf_sz,
     538             :                            uchar                    * rodata,
     539             :                            fd_sbpf_elf_info_t const * info,
     540       15294 :                            ulong                      r_offset ) {
     541             : 
     542       15294 :   fd_elf64_shdr const * shdrs   = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
     543       15294 :   fd_elf64_shdr const * sh_text = &shdrs[ info->shndx_text ];
     544             : 
     545             :   /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1147-L1148 */
     546       15294 :   ulong imm_offset = fd_ulong_sat_add( r_offset, 4UL /* BYTE_OFFSET_IMMEDIATE */ );
     547             : 
     548             :   /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1150-L1246 */
     549       15294 :   fd_sbpf_range_t text_section_range;
     550       15294 :   if( fd_shdr_get_file_range( sh_text, &text_section_range ) &&
     551       15294 :       fd_sbpf_range_contains( &text_section_range, r_offset ) ) {
     552             : 
     553             :     /* We are relocating a lddw (load double word) instruction which
     554             :        spans two instruction slots. The address top be relocated is
     555             :        split in two halves in the two imms of the instruction slots.
     556             : 
     557             :        https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1159-L1162 */
     558       10218 :     ulong imm_low_offset  = imm_offset;
     559       10218 :     ulong imm_high_offset = fd_ulong_sat_add( r_offset,
     560       10218 :                                               4UL /* BYTE_OFFSET_IMMEDIATE */ + 8UL /* INSN_SIZE */ );
     561             : 
     562             :     /* Read the low side of the address. Perform a bounds check first.
     563             :        https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1164-L1171 */
     564       10218 :     if( FD_UNLIKELY( fd_ulong_sat_add( imm_low_offset, 4UL /* BYTE_LENGTH_IMMEDIATE */ )>elf_sz ) ) {
     565           0 :       return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
     566           0 :     }
     567       10218 :     uint va_low = FD_LOAD( uint, rodata+imm_low_offset );
     568             : 
     569             :     /* Read the high side of the address. Perform a bounds check first.
     570             :        https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1174-L1180 */
     571       10218 :     if( FD_UNLIKELY( fd_ulong_sat_add( imm_high_offset, 4UL /* BYTE_LENGTH_IMMEDIATE */ )>elf_sz ) ) {
     572           0 :       return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
     573           0 :     }
     574       10218 :     uint va_high = FD_LOAD( uint, rodata+imm_high_offset );
     575             : 
     576             :     /* Put the address back together.
     577             :        https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1182-L1187 */
     578       10218 :     ulong refd_addr = ( (ulong)va_high<<32UL ) | va_low;
     579       10218 :     if( FD_UNLIKELY( refd_addr==0UL ) ) {
     580           0 :       return FD_SBPF_ELF_ERR_INVALID_VIRTUAL_ADDRESS;
     581           0 :     }
     582             : 
     583             :     /* We need to normalize the address into the VM's memory space, which
     584             :        is rooted at 0x1_0000_0000 (the program ro-data region). If the
     585             :        linker hasn't normalized the addresses already, we treat addr as
     586             :        a relative offset into the program ro-data region.
     587             :        https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1189-L1193 */
     588       10218 :     if( refd_addr<FD_SBPF_MM_PROGRAM_ADDR ) {
     589       10218 :       refd_addr = fd_ulong_sat_add( refd_addr, FD_SBPF_MM_PROGRAM_ADDR );
     590       10218 :     }
     591             : 
     592             :     /* Write back the low half. Perform a bounds check first.
     593             :        https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1195-L1202 */
     594       10218 :     if( FD_UNLIKELY( fd_ulong_sat_add( imm_low_offset, 4UL /* BYTE_LENGTH_IMMEDIATE */ )>elf_sz ) ) {
     595           0 :       return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
     596           0 :     }
     597       10218 :     FD_STORE( uint, rodata+imm_low_offset, (uint)refd_addr );
     598             : 
     599             :     /* Write back the high half. Perform a bounds check first.
     600             :        https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1205-L1214 */
     601       10218 :     if( FD_UNLIKELY( fd_ulong_sat_add( imm_high_offset, 4UL /* BYTE_LENGTH_IMMEDIATE */ )>elf_sz ) ) {
     602           0 :       return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
     603           0 :     }
     604       10218 :     FD_STORE( uint, rodata+imm_high_offset, (uint)(refd_addr>>32UL) );
     605       10218 :   } else {
     606             :     /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1216-L1228 */
     607        5076 :     ulong refd_addr = 0UL;
     608             : 
     609             :     /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1230-L1239 */
     610        5076 :     if( FD_UNLIKELY( fd_ulong_sat_add( imm_offset, 4UL /* BYTE_LENGTH_IMMEDIATE */ )>elf_sz ) ) {
     611           0 :       return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
     612           0 :     }
     613        5076 :     refd_addr = FD_LOAD( uint, rodata+imm_offset );
     614        5076 :     refd_addr = fd_ulong_sat_add( refd_addr, FD_SBPF_MM_PROGRAM_ADDR );
     615             : 
     616             :     /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1242-L1245 */
     617        5076 :     if( FD_UNLIKELY( fd_ulong_sat_add( r_offset, sizeof(ulong) )>elf_sz ) ) {
     618           0 :       return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
     619           0 :     }
     620             : 
     621        5076 :     FD_STORE( ulong, rodata+r_offset, refd_addr );
     622        5076 :   }
     623             : 
     624       15294 :   return FD_SBPF_ELF_SUCCESS;
     625       15294 : }
     626             : 
     627             : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1248-L1301 */
     628             : static int
     629             : fd_sbpf_r_bpf_64_32( fd_sbpf_loader_t *              loader,
     630             :                      fd_sbpf_program_t *             prog,
     631             :                      fd_sbpf_elf_t const *           elf,
     632             :                      ulong                           elf_sz,
     633             :                      uchar *                         rodata,
     634             :                      fd_sbpf_elf_info_t const *      info,
     635             :                      fd_elf64_rel const *            dt_rel,
     636             :                      ulong                           r_offset,
     637        5070 :                      fd_sbpf_loader_config_t const * config ) {
     638             : 
     639        5070 :   fd_elf64_shdr const * shdrs   = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
     640        5070 :   fd_elf64_shdr const * sh_text = &shdrs[ info->shndx_text ];
     641             : 
     642             :   /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1253-L1254 */
     643        5070 :   ulong imm_offset = fd_ulong_sat_add( r_offset, 4UL /* BYTE_OFFSET_IMMEDIATE */ );
     644             : 
     645             :   /* Get the symbol entry from the dynamic symbol table.
     646             :      https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1256-L1259 */
     647        5070 :   fd_elf64_sym const * symbol = NULL;
     648             : 
     649             :   /* Ensure the dynamic symbol table exists. */
     650        5070 :   if( FD_UNLIKELY( info->shndx_dynsymtab<0 ) ) {
     651           0 :     return FD_SBPF_ELF_ERR_UNKNOWN_SYMBOL;
     652           0 :   }
     653             : 
     654             :   /* Get the dynamic symbol table section header. The section header
     655             :      was already validated in fd_sbpf_lenient_elf_parse() so we can
     656             :      directly get the symbol table. */
     657        5070 :   fd_elf64_shdr const * sh_dynsym    = &shdrs[ info->shndx_dynsymtab ];
     658        5070 :   fd_elf64_sym const *  dynsym_table = (fd_elf64_sym const *)( elf->bin + sh_dynsym->sh_offset );
     659        5070 :   ulong                 dynsym_cnt   = (ulong)(sh_dynsym->sh_size / sizeof(fd_elf64_sym));
     660             : 
     661             :   /* The symbol table index is stored in the lower 4 bytes of r_info.
     662             :      Check the bounds of the symbol table index. */
     663        5070 :   ulong r_sym = FD_ELF64_R_SYM( dt_rel->r_info );
     664        5070 :   if( FD_UNLIKELY( r_sym>=dynsym_cnt ) ) {
     665           0 :     return FD_SBPF_ELF_ERR_UNKNOWN_SYMBOL;
     666           0 :   }
     667        5070 :   symbol = &dynsym_table[ r_sym ];
     668             : 
     669             :   /* Verify symbol name.
     670             :      https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1261-L1263
     671             : 
     672             :      First check if the dynamic string table exists:
     673             :      If the dynamic string table does not exist then dynamic_symbol_name()
     674             :      will throw an error because
     675             : 
     676             :      self.dynamic_symbol_names_section_header
     677             :        .ok_or(ElfParserError::NoDynamicStringTable)?
     678             : 
     679             :      will throw an error which, will be mapped to UnknownSymbol
     680             :      https://github.com/anza-xyz/sbpf/blob/main/src/elf_parser/mod.rs#L528-L536 */
     681        5070 :   if( FD_UNLIKELY( info->shndx_dynstr<0 ) ) {
     682           0 :     return FD_SBPF_ELF_ERR_UNKNOWN_SYMBOL;
     683           0 :   }
     684             : 
     685        5070 :   uchar const *         name;
     686        5070 :   ulong                 name_len;
     687        5070 :   fd_elf64_shdr const * dyn_section_names_shdr = &shdrs[ info->shndx_dynstr ];
     688        5070 :   if( FD_UNLIKELY( fd_sbpf_lenient_get_string_in_section( elf->bin, elf_sz, dyn_section_names_shdr, symbol->st_name, FD_SBPF_SYMBOL_NAME_SZ_MAX, &name, &name_len ) ) ) {
     689           0 :     return FD_SBPF_ELF_ERR_UNKNOWN_SYMBOL;
     690           0 :   }
     691             : 
     692             :   /* If the symbol is defined, this is a bpf-to-bpf call.
     693             :      https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1265-L1295 */
     694        5070 :   uint key = 0U;
     695        5070 :   int symbol_is_function = ( FD_ELF64_ST_TYPE( symbol->st_info )==FD_ELF_STT_FUNC );
     696        5070 :   {
     697        5070 :     if( symbol_is_function && symbol->st_value!=0UL ) {
     698             :       /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1267-L1269 */
     699         198 :       fd_sbpf_range_t text_section_range = (fd_sbpf_range_t) {
     700         198 :           .lo = sh_text->sh_addr,
     701         198 :           .hi = fd_ulong_sat_add( sh_text->sh_addr, sh_text->sh_size ) };
     702         198 :       if( FD_UNLIKELY( !fd_sbpf_range_contains( &text_section_range, symbol->st_value ) ) ) {
     703           0 :         return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
     704           0 :       }
     705             : 
     706             :       /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1270-L1279 */
     707         198 :       ulong target_pc = fd_ulong_sat_sub( symbol->st_value, sh_text->sh_addr ) / 8UL;
     708         198 :       int err = fd_sbpf_register_function_hashed_legacy( loader, prog, name, name_len, target_pc, &key );
     709         198 :       if( FD_UNLIKELY( err!=FD_SBPF_ELF_SUCCESS ) ) {
     710           0 :         return err;
     711           0 :       }
     712        4872 :     } else {
     713             :       /* Else, it's a syscall. Ensure that the syscall can be resolved.
     714             :          https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1281-L1294 */
     715        4872 :       key = fd_murmur3_32(name, name_len, 0UL );
     716        4872 :       if( FD_UNLIKELY( config->reject_broken_elfs &&
     717        4872 :                        fd_sbpf_syscalls_query( loader->syscalls, key, NULL )==NULL ) ) {
     718           0 :         return FD_SBPF_ELF_ERR_UNRESOLVED_SYMBOL;
     719           0 :       }
     720        4872 :     }
     721        5070 :   }
     722             : 
     723             :   /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1297-L1300 */
     724        5070 :   if( FD_UNLIKELY( fd_ulong_sat_add( imm_offset, 4UL /* BYTE_LENGTH_IMMEDIATE */ )>elf_sz ) ) {
     725           0 :     return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
     726           0 :   }
     727             : 
     728        5070 :   FD_STORE( uint, rodata+imm_offset, key );
     729             : 
     730        5070 :   return FD_SBPF_ELF_SUCCESS;
     731        5070 : }
     732             : 
     733             : static int
     734             : fd_sbpf_elf_peek_strict( fd_sbpf_elf_info_t * info,
     735             :                          void const *         bin,
     736          54 :                          ulong                bin_sz ) {
     737             : 
     738             :   /* Parse file header */
     739             : 
     740             :   /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L418
     741             :      (Agave does some extra checks on alignment, but they don't seem necessary) */
     742          54 :   if( FD_UNLIKELY( bin_sz<sizeof(fd_elf64_ehdr) ) ) {
     743           3 :     return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
     744           3 :   }
     745             : 
     746          51 :   fd_elf64_ehdr ehdr = FD_LOAD( fd_elf64_ehdr, bin );
     747             : 
     748             :   /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L419-L422 */
     749          51 :   ulong program_header_table_end = fd_ulong_sat_add( sizeof(fd_elf64_ehdr), fd_ulong_sat_mul( ehdr.e_phnum, sizeof(fd_elf64_phdr) ) );
     750             : 
     751             :   /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L423-L446 */
     752          51 :   int parse_ehdr_err =
     753          51 :       ( fd_uint_load_4( ehdr.e_ident )    != FD_ELF_MAG_LE         )
     754          51 :     | ( ehdr.e_ident[ FD_ELF_EI_CLASS   ] != FD_ELF_CLASS_64       )
     755          51 :     | ( ehdr.e_ident[ FD_ELF_EI_DATA    ] != FD_ELF_DATA_LE        )
     756          51 :     | ( ehdr.e_ident[ FD_ELF_EI_VERSION ] != 1                     )
     757          51 :     | ( ehdr.e_ident[ FD_ELF_EI_OSABI   ] != FD_ELF_OSABI_NONE     )
     758             :     // The 7 padding bytes [9, 16) must be 0. Byte 8 (EI_ABIVERSION) is also 0, so check [8, 16).
     759          51 :     | ( fd_ulong_load_8( ehdr.e_ident+8 ) != 0UL                   )
     760             :     // | ( ehdr.e_type )                                              /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L430 */
     761          51 :     | ( ehdr.e_machine                    != FD_ELF_EM_BPF         )  /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L431 */
     762          51 :     | ( ehdr.e_version                    != 1                     )
     763             :     // | ( ehdr.e_entry )
     764          51 :     | ( ehdr.e_phoff                      != sizeof(fd_elf64_ehdr) )
     765             :     // | ( ehdr.e_shoff )
     766             :     // | ( ehdr.e_flags )
     767          51 :     | ( ehdr.e_ehsize                     != sizeof(fd_elf64_ehdr) )
     768          51 :     | ( ehdr.e_phentsize                  != sizeof(fd_elf64_phdr) )
     769          51 :     | ( ehdr.e_phnum                      == 0                     ) /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L439 */
     770          51 :     | ( program_header_table_end          >  bin_sz                ) /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L440 */
     771             :     // | ( ehdr.e_shentsize )                                        /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L441 */
     772             :     // | ( ehdr.e_shnum )                                            /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L442 */
     773             :     // | ( ehdr.e_shstrndx )                                         /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L443 */
     774          51 :   ;
     775          51 :   if( FD_UNLIKELY( parse_ehdr_err ) ) {
     776          15 :     return FD_SBPF_ELF_PARSER_ERR_INVALID_FILE_HEADER;
     777          15 :   }
     778             : 
     779             :   /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L452-L453
     780             :      Note: program_header_table_end = sizeof(ehdr) + e_phnum * sizeof(phdr),
     781             :      all inputs are small so saturating arithmetic is unnecessary.
     782             :      This means that the modulus is always zero and the code is unreachable.
     783             :      Commented out so we can reach 100% coverage. */
     784             :   // if( FD_UNLIKELY( (program_header_table_end-sizeof(fd_elf64_ehdr))%sizeof(fd_elf64_phdr) ) ) {
     785             :   //   return FD_SBPF_ELF_PARSER_ERR_INVALID_SIZE;
     786             :   // }
     787             : 
     788             :   /* Parse program headers (expecting up to 2 segments: rodata + bytecode)
     789             :      https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L448-L484 */
     790             : 
     791         201 :   #define STRICT_EXPECTED_PHDR_CNT (2U)
     792          36 :   ulong expected_p_vaddr[ STRICT_EXPECTED_PHDR_CNT ] = { FD_SBPF_MM_RODATA_START, FD_SBPF_MM_BYTECODE_START };
     793          36 :   uint  expected_p_flags[ STRICT_EXPECTED_PHDR_CNT ] = { FD_SBPF_PF_R,            FD_SBPF_PF_X              };
     794             : 
     795             :   /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L455-L463
     796             :      If the first PH is not marked as readonly, expect the rodata
     797             :      segment to be skipped. */
     798          36 :   fd_elf64_phdr phdr0 = FD_LOAD( fd_elf64_phdr, bin + sizeof(fd_elf64_ehdr) );
     799          36 :   int skip_rodata = ( phdr0.p_flags != expected_p_flags[ 0 ] );
     800          36 :   uint ph_start = skip_rodata ? 1U : 0U;
     801             : 
     802          36 :   if( FD_UNLIKELY( !skip_rodata && ehdr.e_phnum < 2 ) ) {
     803             :     /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L461-L463 */
     804           3 :     return FD_SBPF_ELF_PARSER_ERR_INVALID_FILE_HEADER;
     805           3 :   }
     806             : 
     807             :   /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L464 */
     808          33 :   ulong expected_offset = program_header_table_end;
     809          33 :   fd_elf64_phdr bytecode_phdr = {0};
     810             : 
     811          33 :   uint ph_count = fd_uint_min( ehdr.e_phnum, STRICT_EXPECTED_PHDR_CNT );
     812          84 :   for( uint ei=ph_start, pi=0; ei<STRICT_EXPECTED_PHDR_CNT && pi<ph_count; ei++, pi++ ) {
     813          57 :     fd_elf64_phdr phdr_i = FD_LOAD( fd_elf64_phdr, bin + sizeof(fd_elf64_ehdr) + pi*sizeof(fd_elf64_phdr) );
     814             : 
     815             :     /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L468-L479 */
     816          57 :     int parse_phdr_err =
     817          57 :         ( phdr_i.p_type         != FD_ELF_PT_LOAD              )
     818          57 :       | ( phdr_i.p_flags        != expected_p_flags[ ei ]      )
     819          57 :       | ( phdr_i.p_offset       != expected_offset             ) /* exact sequential: https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L470 */
     820          57 :       | ( phdr_i.p_offset       >= bin_sz                      )
     821          57 :       | ( phdr_i.p_offset % 8UL != 0UL                         )
     822          57 :       | ( phdr_i.p_vaddr        != expected_p_vaddr[ ei ]      )
     823          57 :       | ( phdr_i.p_paddr        != expected_p_vaddr[ ei ]      )
     824          57 :       | ( phdr_i.p_filesz       != phdr_i.p_memsz              ) /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L475 */
     825          57 :       | ( phdr_i.p_filesz       >  bin_sz - phdr_i.p_offset    )
     826          57 :       | ( phdr_i.p_filesz % 8UL != 0UL                         )
     827          57 :       | ( phdr_i.p_memsz        >= FD_SBPF_MM_REGION_SZ        )
     828          57 :     ;
     829          57 :     if( FD_UNLIKELY( parse_phdr_err ) ) {
     830           6 :       return FD_SBPF_ELF_PARSER_ERR_INVALID_PROGRAM_HEADER;
     831           6 :     }
     832             : 
     833             :     /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L483 */
     834          51 :     expected_offset = fd_ulong_sat_add( expected_offset, phdr_i.p_filesz );
     835          51 :     if( ei == 1 ) { bytecode_phdr = phdr_i; }
     836          51 :   }
     837             : 
     838             :   /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L486-L496
     839             :      Determine bytecode_header based on skip_rodata */
     840          27 :   if( skip_rodata ) {
     841           6 :     bytecode_phdr = phdr0;
     842           6 :   }
     843          27 :   #undef STRICT_EXPECTED_PHDR_CNT
     844             : 
     845             :   /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L501-L508 */
     846          27 :   ulong vm_range_start = bytecode_phdr.p_vaddr;
     847          27 :   ulong vm_range_end = bytecode_phdr.p_vaddr + bytecode_phdr.p_memsz;
     848          27 :   ulong entry_chk = ehdr.e_entry + 7UL;
     849          27 :   int parse_e_entry_err =
     850          27 :      !( vm_range_start <= entry_chk && entry_chk < vm_range_end ) /* rust contains includes min, excludes max*/
     851          27 :     | ( ehdr.e_entry % 8UL != 0UL                               )
     852          27 :   ;
     853          27 :   if( FD_UNLIKELY( parse_e_entry_err ) ) {
     854           6 :     return FD_SBPF_ELF_PARSER_ERR_INVALID_FILE_HEADER;
     855           6 :   }
     856             : 
     857             :   /* entry_pc is computed later in fd_sbpf_program_load.
     858             :      https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L510-L514 */
     859             : 
     860             :   /* config.enable_symbol_and_section_labels is false in production,
     861             :      so there's nothing else to do.
     862             :      https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L516-L518 */
     863             : 
     864          21 :   info->bin_sz   = bin_sz;
     865          21 :   info->text_off = skip_rodata ? 0U : (uint)phdr0.p_memsz;
     866          21 :   info->text_sz  = (uint)bytecode_phdr.p_memsz;
     867          21 :   info->text_cnt = (uint)( bytecode_phdr.p_memsz / 8UL );
     868             : 
     869          21 :   return FD_SBPF_ELF_SUCCESS;
     870          27 : }
     871             : 
     872             : static inline int
     873       41184 : fd_sbpf_check_overlap( ulong a_start, ulong a_end, ulong b_start, ulong b_end ) {
     874       41184 :   return !( ( a_end <= b_start || b_end <= a_start ) );
     875       41184 : }
     876             : 
     877             : /* Mirrors Elf64::parse() in Agave. Returns an ElfParserError code on
     878             :    failure and 0 on success.
     879             :    https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L148 */
     880             : int
     881             : fd_sbpf_lenient_elf_parse( fd_sbpf_elf_info_t * info,
     882             :                            void const *         bin,
     883        2592 :                            ulong                bin_sz ) {
     884             : 
     885             :   /* This documents the values that will be set in this function */
     886        2592 :   info->bin_sz          = bin_sz;
     887        2592 :   info->phndx_dyn       = -1;
     888        2592 :   info->shndx_dyn       = -1;
     889        2592 :   info->shndx_symtab    = -1;
     890        2592 :   info->shndx_strtab    = -1;
     891        2592 :   info->shndx_dynstr    = -1;
     892        2592 :   info->shndx_dynsymtab = -1;
     893             : 
     894             :   /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L149 */
     895        2592 :   if( FD_UNLIKELY( bin_sz<sizeof(fd_elf64_ehdr) ) ) {
     896           0 :     return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
     897           0 :   }
     898             : 
     899        2592 :   fd_elf64_ehdr ehdr = FD_LOAD( fd_elf64_ehdr, bin );
     900        2592 :   ulong ehdr_start = 0;
     901        2592 :   ulong ehdr_end = sizeof(fd_elf64_ehdr);
     902             : 
     903             :   /* ELF header
     904             :      https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L151-L162 */
     905        2592 :   int parse_ehdr_err =
     906        2592 :       ( fd_uint_load_4( ehdr.e_ident )    != FD_ELF_MAG_LE         )
     907        2592 :     | ( ehdr.e_ident[ FD_ELF_EI_CLASS   ] != FD_ELF_CLASS_64       )
     908        2592 :     | ( ehdr.e_ident[ FD_ELF_EI_DATA    ] != FD_ELF_DATA_LE        )
     909        2592 :     | ( ehdr.e_ident[ FD_ELF_EI_VERSION ] != 1                     )
     910        2592 :     | ( ehdr.e_version                    != 1                     )
     911        2592 :     | ( ehdr.e_ehsize                     != sizeof(fd_elf64_ehdr) )
     912        2592 :     | ( ehdr.e_phentsize                  != sizeof(fd_elf64_phdr) )
     913        2592 :     | ( ehdr.e_shentsize                  != sizeof(fd_elf64_shdr) )
     914        2592 :     | ( ehdr.e_shstrndx                   >= ehdr.e_shnum          )
     915        2592 :   ;
     916        2592 :   if( FD_UNLIKELY( parse_ehdr_err ) ) {
     917           0 :     return FD_SBPF_ELF_PARSER_ERR_INVALID_FILE_HEADER;
     918           0 :   }
     919             : 
     920             :   /* Program headers
     921             :      https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L164-L165 */
     922        2592 :   ulong phdr_start = ehdr.e_phoff;
     923        2592 :   ulong phdr_end, phdr_sz;
     924             :   /* Elf64::parse_program_header_table() */
     925        2592 :   {
     926        2592 :     if( FD_UNLIKELY( __builtin_umull_overflow( ehdr.e_phnum, sizeof(fd_elf64_phdr), &phdr_sz ) ) ) {
     927           0 :       return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
     928           0 :     }
     929             : 
     930        2592 :     if( FD_UNLIKELY( __builtin_uaddl_overflow( ehdr.e_phoff, phdr_sz, &phdr_end ) ) ) {
     931             :       /* ArithmeticOverflow -> ElfParserError::OutOfBounds
     932             :         https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L671-L675 */
     933           0 :       return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
     934           0 :     }
     935             : 
     936             :     /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L301 */
     937        2592 :     if( FD_UNLIKELY( fd_sbpf_check_overlap( ehdr_start, ehdr_end, phdr_start, phdr_end ) ) ) {
     938           0 :       return FD_SBPF_ELF_PARSER_ERR_OVERLAP;
     939           0 :     }
     940             : 
     941             :     /* Ensure program header table range lies within the file, like
     942             :        slice_from_bytes. Unfortunately the checks have to be split up
     943             :        because Agave throws different error codes depending on which
     944             :        condition fails...
     945             :        https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L302-L303 */
     946        2592 :     if( FD_UNLIKELY( phdr_sz%sizeof(fd_elf64_phdr)!=0UL ) ) {
     947           0 :       return FD_SBPF_ELF_PARSER_ERR_INVALID_SIZE;
     948           0 :     }
     949             : 
     950        2592 :     if( FD_UNLIKELY( phdr_end>bin_sz ) ) {
     951           0 :       return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
     952           0 :     }
     953             : 
     954        2592 :     if( FD_UNLIKELY( !fd_ulong_is_aligned( phdr_start, 8UL ) ) ) {
     955           0 :       return FD_SBPF_ELF_PARSER_ERR_INVALID_ALIGNMENT;
     956           0 :     }
     957        2592 :   }
     958             : 
     959             :   /* Section headers
     960             :      https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L167-L172 */
     961             : 
     962        2592 :   ulong shdr_start = ehdr.e_shoff;
     963        2592 :   ulong shdr_end, shdr_sz;
     964             :   /* Elf64::parse_section_header_table() */
     965        2592 :   {
     966        2592 :     if( FD_UNLIKELY( __builtin_umull_overflow( ehdr.e_shnum, sizeof(fd_elf64_shdr), &shdr_sz ) ) ) {
     967           0 :       return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
     968           0 :     }
     969             : 
     970             :     /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L314-L317 */
     971        2592 :     if( FD_UNLIKELY( __builtin_uaddl_overflow( ehdr.e_shoff, shdr_sz, &shdr_end ) ) ) {
     972             :       /* ArithmeticOverflow -> ElfParserError::OutOfBounds
     973             :         https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L671-L675 */
     974           0 :       return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
     975           0 :     }
     976             : 
     977             :     /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L318 */
     978        2592 :     if( FD_UNLIKELY( fd_sbpf_check_overlap( ehdr_start, ehdr_end, shdr_start, shdr_end ) ) ) {
     979           0 :       return FD_SBPF_ELF_PARSER_ERR_OVERLAP;
     980           0 :     }
     981             : 
     982             :     /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L319 */
     983        2592 :     if( FD_UNLIKELY( fd_sbpf_check_overlap( phdr_start, phdr_end, shdr_start, shdr_end ) ) ) {
     984           0 :       return FD_SBPF_ELF_PARSER_ERR_OVERLAP;
     985           0 :     }
     986             : 
     987             :     /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L321 */
     988        2592 :     if( FD_UNLIKELY( (shdr_end-ehdr.e_shoff)%sizeof(fd_elf64_shdr) ) ) {
     989           0 :       return FD_SBPF_ELF_PARSER_ERR_INVALID_SIZE;
     990           0 :     }
     991             : 
     992             :     /* Ensure section header table range lies within the file, like slice_from_bytes */
     993        2592 :     if( FD_UNLIKELY( shdr_end > bin_sz ) ) {
     994           0 :       return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
     995           0 :     }
     996             : 
     997        2592 :     if( FD_UNLIKELY( !fd_ulong_is_aligned( ehdr.e_shoff, 8UL ) ) ) {
     998           0 :       return FD_SBPF_ELF_PARSER_ERR_INVALID_ALIGNMENT;
     999           0 :     }
    1000        2592 :   }
    1001             : 
    1002             :   /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L174-L177 */
    1003        2592 :   fd_elf64_shdr shdr = FD_LOAD( fd_elf64_shdr, bin + ehdr.e_shoff );
    1004        2592 :   if( FD_UNLIKELY( shdr.sh_type != FD_ELF_SHT_NULL ) ) {
    1005           0 :     return FD_SBPF_ELF_PARSER_ERR_INVALID_SECTION_HEADER;
    1006           0 :   }
    1007             : 
    1008             :   /* Parse each program header
    1009             :      https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L179-L196 */
    1010        2592 :   ulong vaddr = 0UL;
    1011        5580 :   for( ulong i=0; i<ehdr.e_phnum; i++ ) {
    1012        2988 :     fd_elf64_phdr phdr = FD_LOAD( fd_elf64_phdr, bin + phdr_start + i*sizeof(fd_elf64_phdr) );
    1013        2988 :     if( FD_UNLIKELY( phdr.p_type != FD_ELF_PT_LOAD ) ) {
    1014             :       /* Remember first PT_DYNAMIC program header for dynamic parsing */
    1015         141 :       if( phdr.p_type==FD_ELF_PT_DYNAMIC && info->phndx_dyn == -1 ) {
    1016         141 :         info->phndx_dyn = (int)i;
    1017         141 :       }
    1018         141 :       continue;
    1019         141 :     }
    1020        2847 :     if( FD_UNLIKELY( phdr.p_vaddr<vaddr ) ) {
    1021           0 :       return FD_SBPF_ELF_PARSER_ERR_INVALID_PROGRAM_HEADER;
    1022           0 :     }
    1023        2847 :     ulong _offset_plus_size;
    1024        2847 :     if( FD_UNLIKELY( __builtin_uaddl_overflow( phdr.p_offset, phdr.p_filesz, &_offset_plus_size ) ) ) {
    1025           0 :       return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
    1026           0 :     }
    1027        2847 :     if( FD_UNLIKELY( phdr.p_offset + phdr.p_filesz > bin_sz ) ) {
    1028           0 :       return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
    1029           0 :     }
    1030        2847 :     vaddr = phdr.p_vaddr;
    1031        2847 :   }
    1032             : 
    1033             :   /* Parse each section header
    1034             :      https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L198-L216 */
    1035        2592 :   ulong offset = 0UL;
    1036       13731 :   for( ulong i=0; i<ehdr.e_shnum; i++ ) {
    1037             :     /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L200-L205 */
    1038       11139 :     fd_elf64_shdr shdr = FD_LOAD( fd_elf64_shdr, bin + shdr_start + i*sizeof(fd_elf64_shdr) );
    1039       11139 :     if( FD_UNLIKELY( shdr.sh_type==FD_ELF_SHT_NOBITS ) ) {
    1040           3 :       continue;
    1041           3 :     }
    1042             : 
    1043             :     /* Remember first SHT_DYNAMIC section header for dynamic parsing */
    1044       11136 :     if( shdr.sh_type==FD_ELF_SHT_DYNAMIC && info->shndx_dyn == -1 ) {
    1045         141 :       info->shndx_dyn = (int)i;
    1046         141 :     }
    1047             : 
    1048       11136 :     ulong sh_start = shdr.sh_offset;
    1049       11136 :     ulong sh_end;
    1050       11136 :     if( FD_UNLIKELY( __builtin_uaddl_overflow( shdr.sh_offset, shdr.sh_size, &sh_end ) ) ) {
    1051           0 :       return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
    1052           0 :     }
    1053             : 
    1054             :     /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L206-L208 */
    1055       11136 :     if( FD_UNLIKELY( fd_sbpf_check_overlap( sh_start, sh_end, ehdr_start, ehdr_end ) ) ) {
    1056           0 :       return FD_SBPF_ELF_PARSER_ERR_OVERLAP;
    1057           0 :     }
    1058       11136 :     if( FD_UNLIKELY( fd_sbpf_check_overlap( sh_start, sh_end, phdr_start, phdr_end ) ) ) {
    1059           0 :       return FD_SBPF_ELF_PARSER_ERR_OVERLAP;
    1060           0 :     }
    1061       11136 :     if( FD_UNLIKELY( fd_sbpf_check_overlap( sh_start, sh_end, shdr_start, shdr_end ) ) ) {
    1062           0 :       return FD_SBPF_ELF_PARSER_ERR_OVERLAP;
    1063           0 :     }
    1064             : 
    1065             :     /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L209-L215 */
    1066       11136 :     if( FD_UNLIKELY( sh_start < offset ) ) {
    1067           0 :       return FD_SBPF_ELF_PARSER_ERR_SECTION_NOT_IN_ORDER;
    1068           0 :     }
    1069       11136 :     offset = sh_end;
    1070       11136 :     if( FD_UNLIKELY( sh_end > bin_sz ) ) {
    1071           0 :       return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
    1072           0 :     }
    1073       11136 :   }
    1074             : 
    1075             :   /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L218-L224
    1076             :      section_header_table.get() returning ok is equivalent to ehdr.e_shstrndx < ehdr.e_shnum,
    1077             :      and this is already checked above. So, nothing to do here. */
    1078             : 
    1079             :   /* Parse sections
    1080             :      https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L240 */
    1081        2592 :   {
    1082             :     /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L340-L342 */
    1083        2592 :     if( FD_UNLIKELY( ehdr.e_shstrndx == 0 ) ) {
    1084           0 :       return FD_SBPF_ELF_PARSER_ERR_NO_SECTION_NAME_STRING_TABLE;
    1085           0 :     }
    1086             : 
    1087             :     /* Use section name string table to identify well-known sections */
    1088        2592 :     ulong section_names_shdr_idx = ehdr.e_shstrndx;
    1089        2592 :     fd_elf64_shdr section_names_shdr = FD_LOAD( fd_elf64_shdr, bin + shdr_start + section_names_shdr_idx*sizeof(fd_elf64_shdr) );
    1090             :     /* Agave repeats the following validation all the times, we can do it once here
    1091             :        https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L474-L476 */
    1092        2592 :     if( FD_UNLIKELY( section_names_shdr.sh_type != FD_ELF_SHT_STRTAB ) ) {
    1093           0 :       return FD_SBPF_ELF_PARSER_ERR_INVALID_SECTION_HEADER;
    1094           0 :     }
    1095             : 
    1096             :     /* Iterate sections and record indices for .text, .symtab, .strtab, .dyn, .dynstr */
    1097       13707 :     for( ulong i=0; i<ehdr.e_shnum; i++ ) {
    1098             :       /* Again... */
    1099       11118 :       fd_elf64_shdr shdr = FD_LOAD( fd_elf64_shdr, bin + shdr_start + i*sizeof(fd_elf64_shdr) );
    1100             : 
    1101       11118 :       uchar const * name;
    1102       11118 :       ulong         name_len;
    1103       11118 :       int res = fd_sbpf_lenient_get_string_in_section( bin, bin_sz, &section_names_shdr, shdr.sh_name, FD_SBPF_SECTION_NAME_SZ_MAX, &name, &name_len );
    1104       11118 :       if( FD_UNLIKELY( res < 0 ) ) {
    1105           3 :         return res;
    1106           3 :       }
    1107             : 
    1108             :       /* Store the first section by name:
    1109             :          https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L350-L355
    1110             :          The rust code expands in:
    1111             :             match section_name {
    1112             :                 b".symtab" => {
    1113             :                     if self.symbol_section_header.is_some() {
    1114             :                         return Err(ElfParserError::InvalidSectionHeader);
    1115             :                     }
    1116             :                     self.symbol_section_header = Some(section_header);
    1117             :                 }
    1118             :                 ...
    1119             :                 _ => {}
    1120             :             }
    1121             :          Note that the number of bytes compared should not include the
    1122             :          null-terminator.
    1123             :         */
    1124       11115 :       if( fd_sbpf_slice_cstr_eq( name, name_len, ".symtab" ) ) {
    1125          33 :         if( FD_UNLIKELY( info->shndx_symtab != -1 ) ) {
    1126           0 :           return FD_SBPF_ELF_PARSER_ERR_INVALID_SECTION_HEADER;
    1127           0 :         }
    1128          33 :         info->shndx_symtab = (int)i;
    1129       11082 :       } else if( fd_sbpf_slice_cstr_eq( name, name_len, ".strtab" ) ) {
    1130          33 :         if( FD_UNLIKELY( info->shndx_strtab != -1 ) ) {
    1131           0 :           return FD_SBPF_ELF_PARSER_ERR_INVALID_SECTION_HEADER;
    1132           0 :         }
    1133          33 :         info->shndx_strtab = (int)i;
    1134       11049 :       } else if( fd_sbpf_slice_cstr_eq( name, name_len, ".dynstr" ) ) {
    1135         138 :         if( FD_UNLIKELY( info->shndx_dynstr != -1 ) ) {
    1136           0 :           return FD_SBPF_ELF_PARSER_ERR_INVALID_SECTION_HEADER;
    1137           0 :         }
    1138         138 :         info->shndx_dynstr = (int)i;
    1139         138 :       }
    1140       11115 :     }
    1141        2592 :   }
    1142             : 
    1143             :   /* Parse dynamic
    1144             :      https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L241 */
    1145        2589 :   {
    1146             :     /* Try PT_DYNAMIC first; if invalid or absent, fall back to SHT_DYNAMIC.
    1147             :        Note that only the first PT_DYNAMIC and SHT_DYNAMIC are used because of Rust iter().find().
    1148             :        Mirrors Rust logic:
    1149             :          - Try PT_DYNAMIC: https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L364-L372
    1150             :          - 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
    1151             :        If neither exists, return OK (static file). If SHT_DYNAMIC exists but is invalid, error. */
    1152             : 
    1153        2589 :     ulong dynamic_table_start = ULONG_MAX;
    1154        2589 :     ulong dynamic_table_end = ULONG_MAX;
    1155             : 
    1156             :     /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L364-L372 */
    1157        2589 :     if( info->phndx_dyn >= 0 ) {
    1158         138 :       fd_elf64_phdr dyn_ph = FD_LOAD( fd_elf64_phdr, bin + phdr_start + (ulong)info->phndx_dyn*sizeof(fd_elf64_phdr) );
    1159         138 :       dynamic_table_start = dyn_ph.p_offset;
    1160         138 :       dynamic_table_end = dyn_ph.p_offset + dyn_ph.p_filesz;
    1161             : 
    1162             :       /* slice_from_program_header also checks that the size of the
    1163             :          slice is a multiple of the type size and that the alignment is
    1164             :          correct. */
    1165         138 :       if( FD_UNLIKELY( dynamic_table_end<dynamic_table_start ||
    1166         138 :                        dynamic_table_end>bin_sz ||
    1167         138 :                        dyn_ph.p_filesz%sizeof(fd_elf64_dyn)!=0UL ||
    1168         138 :                        !fd_ulong_is_aligned( dynamic_table_start, 8UL ) ) ) {
    1169             :         /* skip - try SHT_DYNAMIC instead */
    1170           0 :         dynamic_table_start = ULONG_MAX;
    1171           0 :         dynamic_table_end = ULONG_MAX;
    1172           0 :       }
    1173         138 :     }
    1174             : 
    1175             :     /* If PT_DYNAMIC did not validate, try SHT_DYNAMIC
    1176             :        https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L376-L387 */
    1177        2589 :     if( dynamic_table_start==ULONG_MAX && info->shndx_dyn >= 0 ) {
    1178           0 :       fd_elf64_shdr dyn_sh = FD_LOAD( fd_elf64_shdr, bin + shdr_start + (ulong)info->shndx_dyn*sizeof(fd_elf64_shdr) );
    1179           0 :       dynamic_table_start = dyn_sh.sh_offset;
    1180           0 :       if( FD_UNLIKELY( ( __builtin_uaddl_overflow( dyn_sh.sh_offset, dyn_sh.sh_size, &dynamic_table_end ) ) || /* checked_add */
    1181           0 :                        ( dyn_sh.sh_size % sizeof(fd_elf64_dyn) != 0UL ) || /* slice_from_bytes InvalidSize */
    1182           0 :                        ( dynamic_table_end > bin_sz )                   || /* slice_from_bytes OutOfBounds */
    1183           0 :                        !fd_ulong_is_aligned( dynamic_table_start, 8UL )    /* slice_from_bytes InvalidAlignment */ ) ) {
    1184             :         /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L382-L385 */
    1185           0 :         return FD_SBPF_ELF_PARSER_ERR_INVALID_DYNAMIC_SECTION_TABLE;
    1186           0 :       }
    1187           0 :     }
    1188             : 
    1189             :     /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L393 */
    1190        2589 :     if( dynamic_table_start==ULONG_MAX ) {
    1191        2451 :       return FD_SBPF_ELF_SUCCESS;
    1192        2451 :     }
    1193             : 
    1194             :     /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L396-L407 */
    1195         138 :     ulong dynamic_table[ FD_ELF_DT_NUM ] = { 0UL };
    1196         138 :     ulong dyn_cnt = (dynamic_table_end - dynamic_table_start) / (ulong)sizeof(fd_elf64_dyn);
    1197        1473 :     for( ulong i = 0UL; i<dyn_cnt; i++ ) {
    1198        1473 :       fd_elf64_dyn dyn = FD_LOAD( fd_elf64_dyn, bin + dynamic_table_start + i*sizeof(fd_elf64_dyn) );
    1199             : 
    1200        1473 :       if( FD_UNLIKELY( dyn.d_tag==FD_ELF_DT_NULL ) ) {
    1201         138 :         break;
    1202         138 :       }
    1203        1335 :       if( FD_UNLIKELY( dyn.d_tag>=FD_ELF_DT_NUM ) ) {
    1204         129 :         continue;
    1205         129 :       }
    1206             : 
    1207        1206 :       dynamic_table[ dyn.d_tag ] = dyn.d_un.d_val;
    1208        1206 :     }
    1209             : 
    1210             :     /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L409
    1211             :        solana_sbpf::elf_parser::Elf64::parse_dynamic_relocations */
    1212         138 :     do {
    1213         138 :       ulong vaddr = dynamic_table[ FD_ELF_DT_REL ];
    1214         138 :       if( FD_UNLIKELY( vaddr==0UL ) ) {
    1215          15 :         break; /* from this do-while */
    1216          15 :       }
    1217             : 
    1218         123 :       if ( FD_UNLIKELY( dynamic_table[ FD_ELF_DT_RELENT ] != sizeof(fd_elf64_rel) ) ) {
    1219           0 :         return FD_SBPF_ELF_PARSER_ERR_INVALID_DYNAMIC_SECTION_TABLE;
    1220           0 :       }
    1221             : 
    1222         123 :       ulong size = dynamic_table[ FD_ELF_DT_RELSZ ];
    1223         123 :       if( FD_UNLIKELY( size==0UL ) ) {
    1224           0 :         return FD_SBPF_ELF_PARSER_ERR_INVALID_DYNAMIC_SECTION_TABLE;
    1225           0 :       }
    1226             : 
    1227             :       /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L430-L444 */
    1228         123 :       _Bool         offset_found = 0;
    1229         123 :       ulong         offset;
    1230         123 :       fd_elf64_phdr phdr;
    1231         360 :       for( ulong i=0; i<ehdr.e_phnum; i++ ) {  /* program_header_for_vaddr */
    1232         360 :         phdr = FD_LOAD( fd_elf64_phdr, bin + phdr_start + i*sizeof(fd_elf64_phdr) );
    1233         360 :         ulong p_vaddr0 = phdr.p_vaddr;
    1234         360 :         ulong p_memsz  = phdr.p_memsz;
    1235         360 :         ulong p_vaddr1;
    1236         360 :         if( FD_UNLIKELY( __builtin_uaddl_overflow( p_vaddr0, p_memsz, &p_vaddr1 ) ) ) {
    1237           0 :           return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
    1238           0 :         }
    1239         360 :         if( p_vaddr0 <= vaddr && vaddr < p_vaddr1 ) {
    1240         123 :           offset_found = 1;
    1241         123 :           break;
    1242         123 :         }
    1243         360 :       }
    1244         123 :       if( offset_found ) {
    1245         123 :         if( FD_UNLIKELY( __builtin_usubl_overflow( vaddr, phdr.p_vaddr, &offset ) ) ) {
    1246           0 :           return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
    1247           0 :         }
    1248         123 :         if( FD_UNLIKELY( __builtin_uaddl_overflow( offset, phdr.p_offset, &offset ) ) ) {
    1249           0 :           return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
    1250           0 :         }
    1251         123 :       } else {
    1252           0 :         for( ulong i=0; i<ehdr.e_shnum; i++ ) {  /* section_header_table.iter().find(...) */
    1253           0 :           fd_elf64_shdr shdr = FD_LOAD( fd_elf64_shdr, bin + shdr_start + i*sizeof(fd_elf64_shdr) );
    1254           0 :           if( shdr.sh_addr == vaddr ) {
    1255           0 :             offset       = shdr.sh_offset;
    1256           0 :             offset_found = 1;
    1257           0 :             break;
    1258           0 :           }
    1259           0 :         }
    1260           0 :         if( FD_UNLIKELY( !offset_found ) ) {
    1261           0 :           return FD_SBPF_ELF_PARSER_ERR_INVALID_DYNAMIC_SECTION_TABLE;
    1262           0 :         }
    1263           0 :       }
    1264             :       /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L446-L448 */
    1265         123 :       ulong offset_plus_size;
    1266         123 :       if( FD_UNLIKELY( __builtin_uaddl_overflow( offset, size, &offset_plus_size ) ) ) {
    1267           0 :         return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
    1268           0 :       }
    1269             : 
    1270             :       /* slice_from_bytes checks that size is a multiple of the type
    1271             :          size and that the alignment of the bytes + offset is correct. */
    1272         123 :       if( FD_UNLIKELY( ( size%sizeof(fd_elf64_rel)!=0UL ) ||
    1273         123 :                        ( offset_plus_size>bin_sz ) ||
    1274         123 :                        ( !fd_ulong_is_aligned( offset, 8UL ) ) ) ) {
    1275           0 :         return FD_SBPF_ELF_PARSER_ERR_INVALID_DYNAMIC_SECTION_TABLE;
    1276           0 :       }
    1277             : 
    1278             :       /* Save the dynamic relocation table info */
    1279         123 :       info->dt_rel_off = (uint)offset;
    1280         123 :       info->dt_rel_sz  = (uint)size;
    1281         123 :     } while( 0 ); /* so we can break out */
    1282             : 
    1283             :     /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L410 */
    1284         138 :     do {
    1285             :       /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L452-L455 */
    1286         138 :       ulong vaddr = dynamic_table[ FD_ELF_DT_SYMTAB ];
    1287         138 :       if( FD_UNLIKELY( vaddr==0UL ) ) {
    1288           0 :         break; /* from this do-while */
    1289           0 :       }
    1290             : 
    1291         138 :       fd_elf64_shdr shdr_sym = { 0 };
    1292         768 :       for( ulong i=0; i<ehdr.e_shnum; i++ ) {
    1293             :         /* Again... */
    1294         768 :         shdr_sym = FD_LOAD( fd_elf64_shdr, bin + shdr_start + i*sizeof(fd_elf64_shdr) );
    1295         768 :         if( shdr_sym.sh_addr == vaddr ) {
    1296         138 :           info->shndx_dynsymtab = (int)i;
    1297         138 :           break;
    1298         138 :         }
    1299         768 :       }
    1300             : 
    1301             :       /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L457-L461 */
    1302         138 :       if( FD_UNLIKELY( info->shndx_dynsymtab==-1 ) ) {
    1303           0 :         return FD_SBPF_ELF_PARSER_ERR_INVALID_DYNAMIC_SECTION_TABLE;
    1304           0 :       }
    1305             : 
    1306             :       /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L463-L464 */
    1307         138 :       {
    1308         138 :         if( FD_UNLIKELY( shdr_sym.sh_type != FD_ELF_SHT_SYMTAB && shdr_sym.sh_type != FD_ELF_SHT_DYNSYM ) ) {
    1309           0 :           return FD_SBPF_ELF_PARSER_ERR_INVALID_SECTION_HEADER;
    1310           0 :         }
    1311         138 :         ulong shdr_sym_start = shdr_sym.sh_offset;
    1312         138 :         ulong shdr_sym_end;
    1313             :         /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L574
    1314             :            https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L671 */
    1315         138 :         if( FD_UNLIKELY( __builtin_uaddl_overflow( shdr_sym.sh_offset, shdr_sym.sh_size, &shdr_sym_end ) ) ) {
    1316           0 :           return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
    1317           0 :         }
    1318             :         /* slice_from_bytes InvalidSize */
    1319         138 :         if( FD_UNLIKELY( shdr_sym.sh_size%sizeof(fd_elf64_sym) ) ) {
    1320           0 :           return FD_SBPF_ELF_PARSER_ERR_INVALID_SIZE;
    1321           0 :         }
    1322             :         /* slice_from_bytes OutOfBounds */
    1323         138 :         if( FD_UNLIKELY( shdr_sym_end>bin_sz ) ) {
    1324           0 :           return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
    1325           0 :         }
    1326             :         /* slice_from_bytes InvalidAlignment */
    1327         138 :         if( FD_UNLIKELY( !fd_ulong_is_aligned( shdr_sym_start, 8UL ) ) ) {
    1328           0 :           return FD_SBPF_ELF_PARSER_ERR_INVALID_ALIGNMENT;
    1329           0 :         }
    1330         138 :       }
    1331         138 :     } while( 0 ); /* so we can break out */
    1332         138 :   }
    1333             : 
    1334         138 :   return FD_SBPF_ELF_SUCCESS;
    1335         138 : }
    1336             : 
    1337             : /* Performs validation checks on the ELF. Returns an ElfError on failure
    1338             :    and 0 on success.
    1339             :    https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L719-L809 */
    1340             : static int
    1341             : fd_sbpf_lenient_elf_validate( fd_sbpf_elf_info_t * info,
    1342             :                               void const *         bin,
    1343             :                               ulong                bin_sz,
    1344        2589 :                               fd_elf64_shdr *      text_shdr ) {
    1345             : 
    1346             :   /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L721-L736 */
    1347        2589 :   fd_elf64_ehdr ehdr = FD_LOAD( fd_elf64_ehdr, bin );
    1348        2589 :   if( FD_UNLIKELY( ehdr.e_ident[ FD_ELF_EI_CLASS ] != FD_ELF_CLASS_64 ) ) {
    1349           0 :     return FD_SBPF_ELF_ERR_WRONG_CLASS;
    1350           0 :   }
    1351        2589 :   if( FD_UNLIKELY( ehdr.e_ident[ FD_ELF_EI_DATA  ] != FD_ELF_DATA_LE ) ) {
    1352           0 :     return FD_SBPF_ELF_ERR_WRONG_ENDIANNESS;
    1353           0 :   }
    1354        2589 :   if( FD_UNLIKELY( ehdr.e_ident[ FD_ELF_EI_OSABI ] != FD_ELF_OSABI_NONE ) ) {
    1355           0 :     return FD_SBPF_ELF_ERR_WRONG_ABI;
    1356           0 :   }
    1357        2589 :   if( FD_UNLIKELY( ehdr.e_machine != FD_ELF_EM_BPF && ehdr.e_machine != FD_ELF_EM_SBPF ) ) {
    1358           0 :     return FD_SBPF_ELF_ERR_WRONG_MACHINE;
    1359           0 :   }
    1360        2589 :   if( FD_UNLIKELY( ehdr.e_type != FD_ELF_ET_DYN ) ) {
    1361           0 :     return FD_SBPF_ELF_ERR_WRONG_TYPE;
    1362           0 :   }
    1363             : 
    1364             :   /* This code doesn't do anything:
    1365             :      1. version is already checked at the very beginning of elf_peek
    1366             :      2. the if condition is never true because sbpf_version is always v0
    1367             :      https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L738-L763 */
    1368             : 
    1369        2589 :   ulong shdr_start = ehdr.e_shoff;
    1370        2589 :   ulong section_names_shdr_idx = ehdr.e_shstrndx;
    1371        2589 :   fd_elf64_shdr section_names_shdr = FD_LOAD( fd_elf64_shdr, bin + shdr_start + section_names_shdr_idx*sizeof(fd_elf64_shdr) );
    1372             : 
    1373             :   /* We do a single iteration over the section header table, collect all info
    1374             :      we need and return the errors later to match Agave. */
    1375             : 
    1376        2589 :   int shndx_text    = -1;
    1377        2589 :   int writeable_err = 0;
    1378        2589 :   int oob_err       = 0;
    1379       13689 :   for( ulong i=0UL; i<ehdr.e_shnum; i++ ) {
    1380             :     /* Again... */
    1381       11100 :     fd_elf64_shdr shdr = FD_LOAD( fd_elf64_shdr, bin + ehdr.e_shoff + i*sizeof(fd_elf64_shdr) );
    1382             : 
    1383       11100 :     uchar const * name;
    1384       11100 :     ulong         name_len;
    1385       11100 :     int res = fd_sbpf_lenient_get_string_in_section( bin, bin_sz, &section_names_shdr, shdr.sh_name, FD_SBPF_SECTION_NAME_SZ_MAX, &name, &name_len );
    1386       11100 :     if( FD_UNLIKELY( res ) ) {
    1387             :       /* this can never fail because it was checked above, but safer to keep it */
    1388           0 :       return fd_sbpf_elf_parser_err_to_elf_err( res );
    1389           0 :     }
    1390             : 
    1391             :     /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L765-L775 */
    1392       11100 :     if( FD_UNLIKELY( fd_sbpf_slice_cstr_eq( name, name_len, ".text" ) ) ) {
    1393        2589 :       if( FD_LIKELY( shndx_text==-1 ) ) {
    1394        2589 :         *text_shdr = shdr;  /* Store the text section header */
    1395        2589 :         shndx_text = (int)i;
    1396        2589 :       } else {
    1397           0 :         return FD_SBPF_ELF_ERR_NOT_ONE_TEXT_SECTION;
    1398           0 :       }
    1399        2589 :     }
    1400             : 
    1401             :     /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L780-L791 */
    1402       11100 :     if( FD_UNLIKELY( fd_sbpf_slice_cstr_start_with( name, name_len, ".bss" ) ||
    1403       11100 :                      ( ( ( shdr.sh_flags & (FD_ELF_SHF_ALLOC | FD_ELF_SHF_WRITE) ) == (FD_ELF_SHF_ALLOC | FD_ELF_SHF_WRITE) ) &&
    1404       11100 :                            fd_sbpf_slice_cstr_start_with( name, name_len, ".data" ) &&
    1405       11100 :                            !fd_sbpf_slice_cstr_start_with( name, name_len, ".data.rel" ) ) ) ) {
    1406             :       /* to match Agave return error we can't fail here */
    1407           0 :       writeable_err = 1;
    1408           0 :     }
    1409             : 
    1410             :     /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L793-L802 */
    1411       11100 :     ulong shdr_end;
    1412       11100 :     if( FD_UNLIKELY( __builtin_uaddl_overflow( shdr.sh_offset, shdr.sh_size, &shdr_end ) ||
    1413       11100 :                      shdr_end>bin_sz ) ) {
    1414           0 :       oob_err = 1;
    1415           0 :     }
    1416       11100 :   }
    1417             : 
    1418             :   /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L776-L778 */
    1419        2589 :   if( FD_UNLIKELY( shndx_text==-1 ) ) {
    1420           0 :     return FD_SBPF_ELF_ERR_NOT_ONE_TEXT_SECTION;
    1421           0 :   }
    1422             : 
    1423             :   /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L786-L788 */
    1424        2589 :   if( FD_UNLIKELY( writeable_err ) ) {
    1425           0 :     return FD_SBPF_ELF_ERR_WRITABLE_SECTION_NOT_SUPPORTED;
    1426           0 :   }
    1427             : 
    1428             :   /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L798 */
    1429        2589 :   if( FD_UNLIKELY( oob_err ) ) {
    1430           0 :     return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
    1431           0 :   }
    1432             : 
    1433             :   /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L804-L806 */
    1434        2589 :   if( FD_UNLIKELY( !(
    1435        2589 :     text_shdr->sh_addr <= ehdr.e_entry && ehdr.e_entry < fd_ulong_sat_add( text_shdr->sh_addr, text_shdr->sh_size )
    1436        2589 :   ) ) ) {
    1437           0 :     return FD_SBPF_ELF_ERR_ENTRYPOINT_OUT_OF_BOUNDS;
    1438           0 :   }
    1439             : 
    1440             :   /* Get text section file ranges to calculate the size. */
    1441        2589 :   fd_sbpf_range_t text_section_range;
    1442        2589 :   fd_shdr_get_file_range( text_shdr, &text_section_range );
    1443             : 
    1444        2589 :   info->text_off      = (uint)text_shdr->sh_addr;
    1445        2589 :   info->text_sz       = text_section_range.hi-text_section_range.lo;
    1446        2589 :   info->text_cnt      = (uint)( info->text_sz/8UL );
    1447        2589 :   info->shndx_text    = shndx_text;
    1448        2589 :   info->calldests_max = (fd_ulong_min( text_shdr->sh_size, bin_sz )+7UL)/8UL;
    1449             : 
    1450        2589 :   return FD_SBPF_ELF_SUCCESS;
    1451        2589 : }
    1452             : 
    1453             : /* First part of Agave's load_with_lenient_parser(). We split up this
    1454             :    function into two parts so we know how much memory we need to
    1455             :    allocate for the loading step. Returns an ElfError on failure and 0
    1456             :    on success.
    1457             :    https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L593-L638 */
    1458             : static int
    1459             : fd_sbpf_elf_peek_lenient( fd_sbpf_elf_info_t *            info,
    1460             :                           void const *                    bin,
    1461             :                           ulong                           bin_sz,
    1462        2592 :                           fd_sbpf_loader_config_t const * config ) {
    1463             : 
    1464             :   /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L607 */
    1465        2592 :   int res = fd_sbpf_lenient_elf_parse( info, bin, bin_sz );
    1466        2592 :   if( FD_UNLIKELY( res<0 ) ) {
    1467           3 :     return fd_sbpf_elf_parser_err_to_elf_err( res );
    1468           3 :   }
    1469             : 
    1470             :   /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L617 */
    1471        2589 :   fd_elf64_shdr text_shdr = { 0 };
    1472        2589 :   res = fd_sbpf_lenient_elf_validate( info, bin, bin_sz, &text_shdr );
    1473        2589 :   if( FD_UNLIKELY( res<0 ) ) {
    1474           0 :     return res;
    1475           0 :   }
    1476             : 
    1477             :   /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L620-L638 */
    1478        2589 :   {
    1479        2589 :     ulong text_section_vaddr = fd_ulong_sat_add( text_shdr.sh_addr, FD_SBPF_MM_BYTECODE_START );
    1480        2589 :     ulong vaddr_end          = text_section_vaddr;
    1481             : 
    1482             :     /* Validate bounds and text section addrs / offsets.
    1483             :        https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L632-L638 */
    1484        2589 :     if( FD_UNLIKELY( ( config->reject_broken_elfs && text_shdr.sh_addr!=text_shdr.sh_offset ) ||
    1485        2589 :                        vaddr_end>FD_SBPF_MM_STACK_ADDR ) ) {
    1486           0 :       return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
    1487           0 :     }
    1488        2589 :   }
    1489             : 
    1490             :   /* Peek (vs load) stops here
    1491             :      https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L638 */
    1492             : 
    1493        2589 :   return FD_SBPF_ELF_SUCCESS;
    1494        2589 : }
    1495             : 
    1496             : static int
    1497             : fd_sbpf_program_get_sbpf_version_or_err( void const *                    bin,
    1498             :                                          ulong                           bin_sz,
    1499        2697 :                                          fd_sbpf_loader_config_t const * config ) {
    1500             :   /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L376-L381 */
    1501        2697 :   const ulong E_FLAGS_OFFSET = 48UL;
    1502             : 
    1503        2697 :   if( FD_UNLIKELY( bin_sz<E_FLAGS_OFFSET+sizeof(uint) ) ) {
    1504           0 :     return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
    1505           0 :   }
    1506        2697 :   uint e_flags = FD_LOAD( uint, bin+E_FLAGS_OFFSET );
    1507             : 
    1508             :   /* https://github.com/anza-xyz/sbpf/blob/v0.13.0/src/elf.rs#L382-L390 */
    1509        2697 :   uint sbpf_version = ( e_flags < FD_SBPF_VERSION_COUNT ) ? e_flags : FD_SBPF_RESERVED;
    1510             : 
    1511             :   /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L399-L401 */
    1512        2697 :   if( FD_UNLIKELY( !( config->sbpf_min_version <= sbpf_version && sbpf_version <= config->sbpf_max_version ) ) ) {
    1513          51 :     return FD_SBPF_ELF_ERR_UNSUPPORTED_SBPF_VERSION;
    1514          51 :   }
    1515             : 
    1516             :   /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L403-L407 */
    1517        2646 :   return (int)sbpf_version;
    1518        2697 : }
    1519             : 
    1520             : int
    1521             : fd_sbpf_elf_peek( fd_sbpf_elf_info_t *            info,
    1522             :                   void const *                    bin,
    1523             :                   ulong                           bin_sz,
    1524        2697 :                   fd_sbpf_loader_config_t const * config ) {
    1525             :   /* Extract sbpf_version (or error)
    1526             :      https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L376-L401 */
    1527        2697 :   int maybe_sbpf_version = fd_sbpf_program_get_sbpf_version_or_err( bin, bin_sz, config );
    1528        2697 :   if( FD_UNLIKELY( maybe_sbpf_version<0 ) ) {
    1529          51 :     return maybe_sbpf_version;
    1530          51 :   }
    1531             : 
    1532             :   /* Initialize info struct */
    1533        2646 :   *info = (fd_sbpf_elf_info_t) {
    1534        2646 :     .bin_sz          = 0U,
    1535        2646 :     .text_off        = 0U,
    1536        2646 :     .text_cnt        = 0U,
    1537        2646 :     .text_sz         = 0UL,
    1538        2646 :     .shndx_text      = -1,
    1539        2646 :     .shndx_symtab    = -1,
    1540        2646 :     .shndx_strtab    = -1,
    1541        2646 :     .shndx_dyn       = -1,
    1542        2646 :     .shndx_dynstr    = -1,
    1543        2646 :     .shndx_dynsymtab = -1,
    1544        2646 :     .phndx_dyn       = -1,
    1545        2646 :     .dt_rel_off      = 0UL,
    1546        2646 :     .dt_rel_sz       = 0UL,
    1547        2646 :     .sbpf_version    = (uint)maybe_sbpf_version,
    1548             :     /* !!! Keep this in sync with -Werror=missing-field-initializers */
    1549        2646 :   };
    1550             : 
    1551             :   /* Invoke strict vs lenient parser. The strict parser is used for
    1552             :      SBPF version >= 3. The strict parser also returns an ElfParserError
    1553             :      while the lenient parser returns an ElfError, so we have to map
    1554             :      the strict parser's error code.
    1555             :      https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L403-L407 */
    1556        2646 :   if( FD_UNLIKELY( fd_sbpf_enable_stricter_elf_headers_enabled( info->sbpf_version ) ) ) {
    1557          54 :     return fd_sbpf_elf_parser_err_to_elf_err( fd_sbpf_elf_peek_strict( info, bin, bin_sz ) );
    1558          54 :   }
    1559        2592 :   return fd_sbpf_elf_peek_lenient( info, bin, bin_sz, config );
    1560        2646 : }
    1561             : 
    1562             : /* Parses and concatenates the readonly data sections.  This function
    1563             :    also computes and sets the rodata_sz field inside the SBPF program
    1564             :    struct.  scratch is a pointer to a scratch area with size scratch_sz,
    1565             :    used to allocate a temporary buffer for the parsed rodata sections
    1566             :    before copying it back into the rodata (recommended size is bin_sz).
    1567             :    Returns 0 on success and an ElfError error code on failure.  On
    1568             :    success, the rodata and rodata_sz fields in the sbpf program struct
    1569             :    are updated.
    1570             :    https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L812-L987 */
    1571             : static int
    1572             : fd_sbpf_parse_ro_sections( fd_sbpf_program_t *             prog,
    1573             :                            void const *                    bin,
    1574             :                            ulong                           bin_sz,
    1575             :                            fd_sbpf_loader_config_t const * config,
    1576             :                            void *                          scratch,
    1577        2577 :                            ulong                           scratch_sz ) {
    1578             : 
    1579        2577 :   fd_sbpf_elf_t const * elf                = (fd_sbpf_elf_t const *)bin;
    1580        2577 :   fd_elf64_shdr const * shdrs              = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
    1581        2577 :   fd_elf64_shdr const * section_names_shdr = &shdrs[ elf->ehdr.e_shstrndx ];
    1582        2577 :   uchar *               rodata             = prog->rodata;
    1583             : 
    1584             :   /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L818-L834 */
    1585        2577 :   ulong lowest_addr          = ULONG_MAX; /* Lowest section address */
    1586        2577 :   ulong highest_addr         = 0UL;       /* Highest section address */
    1587        2577 :   ulong ro_fill_length       = 0UL;       /* Aggregated section length, excluding gaps between sections */
    1588        2577 :   uchar invalid_offsets      = 0;         /* Whether the section has invalid offsets */
    1589             : 
    1590             :   /* Store the section header indices of ro slices to fill later. */
    1591        2577 :   ulong ro_slices_shidxs[ elf->ehdr.e_shnum ];
    1592        2577 :   ulong ro_slices_cnt = 0UL;
    1593             : 
    1594             :   /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L837-L909 */
    1595       13569 :   for( uint i=0U; i<elf->ehdr.e_shnum; i++ ) {
    1596       10992 :     fd_elf64_shdr const * section_header = &shdrs[ i ];
    1597             : 
    1598             :     /* Match the section name.
    1599             :        https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L838-L845 */
    1600       10992 :     uchar const * name;
    1601       10992 :     ulong         name_len;
    1602       10992 :     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, &name_len ) ) ) {
    1603           0 :       continue;
    1604           0 :     }
    1605             : 
    1606       10992 :     if( FD_UNLIKELY( !fd_sbpf_slice_cstr_eq( name, name_len, ".text" ) &&
    1607       10992 :                      !fd_sbpf_slice_cstr_eq( name, name_len, ".rodata" ) &&
    1608       10992 :                      !fd_sbpf_slice_cstr_eq( name, name_len, ".data.rel.ro" ) &&
    1609       10992 :                      !fd_sbpf_slice_cstr_eq( name, name_len, ".eh_frame" ) ) ) {
    1610        5760 :       continue;
    1611        5760 :     }
    1612             : 
    1613        5232 :     ulong section_addr = section_header->sh_addr;
    1614             : 
    1615             :     /* Handling for the section header offsets. If ELF vaddrs are
    1616             :        enabled, the section header addresses are allowed to be > the
    1617             :        section header offsets, as long as address - offset is constant
    1618             :        across all sections. Otherwise, the section header addresses
    1619             :        and offsets must match.
    1620             :        https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L865-L884 */
    1621        5232 :     if( FD_LIKELY( !invalid_offsets ) ) {
    1622             :       /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L866-L880 */
    1623        5232 :       if( FD_UNLIKELY( section_addr!=section_header->sh_offset ) ) {
    1624           3 :         invalid_offsets = 1;
    1625           3 :       }
    1626        5232 :     }
    1627             : 
    1628             :     /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L886-L897 */
    1629        5232 :     ulong vaddr_end = section_addr;
    1630        5232 :     if( section_addr<FD_SBPF_MM_BYTECODE_START ) {
    1631        5232 :       vaddr_end = fd_ulong_sat_add( section_addr, FD_SBPF_MM_BYTECODE_START );
    1632        5232 :     }
    1633             : 
    1634        5232 :     if( FD_UNLIKELY( ( config->reject_broken_elfs && invalid_offsets ) ||
    1635        5232 :                        vaddr_end>FD_SBPF_MM_STACK_ADDR ) ) {
    1636           0 :       return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
    1637           0 :     }
    1638             : 
    1639             :     /* Append the ro slices vector and update the lowest / highest addr
    1640             :        and ro_fill_length variables. Agave stores three fields in the
    1641             :        ro slices array that can all be derived from the section header,
    1642             :        so we just need to store the indices.
    1643             : 
    1644             :        The call to fd_shdr_get_file_range() is allowed to fail (Agave's
    1645             :        unwrap_or_default() call returns a range of 0..0 in this case).
    1646             :        https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L899-L908 */
    1647        5232 :     fd_sbpf_range_t section_header_range;
    1648        5232 :     fd_shdr_get_file_range( section_header, &section_header_range );
    1649        5232 :     if( FD_UNLIKELY( section_header_range.hi>bin_sz ) ) {
    1650           0 :       return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
    1651           0 :     }
    1652        5232 :     ulong section_data_len = section_header_range.hi-section_header_range.lo;
    1653             : 
    1654        5232 :     lowest_addr    = fd_ulong_min( lowest_addr, section_addr );
    1655        5232 :     highest_addr   = fd_ulong_max( highest_addr, fd_ulong_sat_add( section_addr, section_data_len ) );
    1656        5232 :     ro_fill_length = fd_ulong_sat_add( ro_fill_length, section_data_len );
    1657        5232 :     ro_slices_shidxs[ ro_slices_cnt++ ] = i;
    1658        5232 :   }
    1659             : 
    1660             :   /* This checks that the ro sections are not overlapping. This check
    1661             :      is incomplete, however, because it does not account for the
    1662             :      existence of gaps between sections in calculations.
    1663             :      https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L910-L913 */
    1664        2577 :   if( FD_UNLIKELY( config->reject_broken_elfs &&
    1665        2577 :                    fd_ulong_sat_add( lowest_addr, ro_fill_length )>highest_addr ) ) {
    1666           0 :     return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
    1667           0 :   }
    1668             : 
    1669             :   /* Note that optimize_rodata is always false.
    1670             :      https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L923-L984 */
    1671        2577 :  {
    1672             :     /* Readonly / non-readonly sections are mixed, so non-readonly
    1673             :        sections must be zeroed and the readonly sections must be copied
    1674             :        at their respective offsets.
    1675             :        https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L950-L983 */
    1676        2577 :     lowest_addr = 0UL;
    1677             : 
    1678             :     /* Bounds check. */
    1679        2577 :     ulong buf_len = highest_addr;
    1680        2577 :     if( FD_UNLIKELY( buf_len>bin_sz ) ) {
    1681           0 :       return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
    1682           0 :     }
    1683             : 
    1684             :     /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L971-L976 */
    1685        2577 :     if( FD_UNLIKELY( buf_len>scratch_sz ) ) {
    1686           0 :       FD_LOG_CRIT(( "scratch_sz is too small: %lu, required: %lu", scratch_sz, buf_len ));
    1687           0 :     }
    1688        2577 :     uchar * ro_section = scratch;
    1689        2577 :     fd_memset( ro_section, 0, buf_len );
    1690             : 
    1691        7809 :     for( ulong i=0UL; i<ro_slices_cnt; i++ ) {
    1692        5232 :       ulong sh_idx                       = ro_slices_shidxs[ i ];
    1693        5232 :       fd_elf64_shdr const * shdr         = &shdrs[ sh_idx ];
    1694        5232 :       ulong                 section_addr = shdr->sh_addr;
    1695             : 
    1696             :       /* This was checked above and should never fail. */
    1697        5232 :       fd_sbpf_range_t slice_range;
    1698        5232 :       fd_shdr_get_file_range( shdr, &slice_range );
    1699        5232 :       if( FD_UNLIKELY( slice_range.hi>bin_sz ) ) {
    1700           0 :         return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
    1701           0 :       }
    1702             : 
    1703        5232 :       ulong buf_offset_start = fd_ulong_sat_sub( section_addr, lowest_addr );
    1704        5232 :       ulong slice_len        = slice_range.hi-slice_range.lo;
    1705        5232 :       if( FD_UNLIKELY( slice_len>buf_len ) ) {
    1706           0 :         return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
    1707           0 :       }
    1708             : 
    1709        5232 :       fd_memcpy( ro_section+buf_offset_start, rodata+slice_range.lo, slice_len );
    1710        5232 :     }
    1711             : 
    1712             :     /* Copy the rodata section back in. */
    1713        2577 :     prog->rodata_sz = buf_len;
    1714        2577 :     fd_memcpy( rodata, ro_section, buf_len );
    1715        2577 :   }
    1716             : 
    1717        2577 :   return FD_SBPF_ELF_SUCCESS;
    1718        2577 : }
    1719             : 
    1720             : /* Applies ELF relocations in-place. Returns 0 on success and an
    1721             :    ElfError error code on failure.
    1722             :    https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L990-L1331 */
    1723             : static int
    1724             : fd_sbpf_program_relocate( fd_sbpf_program_t *             prog,
    1725             :                           void const *                    bin,
    1726             :                           ulong                           bin_sz,
    1727             :                           fd_sbpf_loader_config_t const * config,
    1728        2577 :                           fd_sbpf_loader_t *              loader ) {
    1729        2577 :   fd_sbpf_elf_info_t const * elf_info = &prog->info;
    1730        2577 :   fd_sbpf_elf_t const *      elf      = (fd_sbpf_elf_t const *)bin;
    1731        2577 :   uchar *                    rodata   = prog->rodata;
    1732        2577 :   fd_elf64_shdr const *      shdrs    = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
    1733        2577 :   fd_elf64_shdr const *      shtext   = &shdrs[ elf_info->shndx_text ];
    1734             : 
    1735             :   /* Copy rodata segment */
    1736        2577 :   fd_memcpy( rodata, elf->bin, elf_info->bin_sz );
    1737             : 
    1738             :   /* Fixup all program counter relative call instructions
    1739             :      https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1005-L1041 */
    1740        2577 :   {
    1741             :     /* Validate the bytes range of the text section.
    1742             :        https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1006-L1008 */
    1743        2577 :     fd_sbpf_range_t text_section_range;
    1744        2577 :     fd_shdr_get_file_range( shtext, &text_section_range );
    1745             : 
    1746        2577 :     ulong insn_cnt = (text_section_range.hi-text_section_range.lo)/8UL;
    1747        2577 :     if( FD_UNLIKELY( shtext->sh_size+shtext->sh_offset>bin_sz ) ) {
    1748           0 :       return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
    1749           0 :     }
    1750             : 
    1751        2577 :     uchar * ptr = rodata + shtext->sh_offset;
    1752             : 
    1753      365454 :     for( ulong i=0UL; i<insn_cnt; i++, ptr+=8UL ) {
    1754      362877 :       ulong insn = FD_LOAD( ulong, ptr );
    1755             : 
    1756             :       /* Check for call instruction.  If immediate is UINT_MAX, assume
    1757             :          that compiler generated a relocation instead.
    1758             :          https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1015 */
    1759      362877 :       ulong opc  = insn & 0xFF;
    1760      362877 :       int   imm  = (int)(insn >> 32UL);
    1761      362877 :       if( (opc!=FD_SBPF_OP_CALL_IMM) || (imm==-1) ) continue;
    1762             : 
    1763             :       /* Calculate and check the target PC
    1764             :          https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1016-L1021 */
    1765       12150 :       long target_pc = fd_long_sat_add( fd_long_sat_add( (long)i, 1L ), imm);
    1766       12150 :       if( FD_UNLIKELY( target_pc<0L || target_pc>=(long)insn_cnt ) ) {
    1767           0 :         return FD_SBPF_ELF_ERR_RELATIVE_JUMP_OUT_OF_BOUNDS;
    1768           0 :       }
    1769             : 
    1770             :       /* Update the calldests
    1771             :          https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1027-L1032 */
    1772       12150 :       uint pc_hash;
    1773       12150 :       int err = fd_sbpf_register_function_hashed_legacy( loader, prog, NULL, 0UL, (ulong)target_pc, &pc_hash );
    1774       12150 :       if( FD_UNLIKELY( err!=FD_SBPF_ELF_SUCCESS ) ) {
    1775           0 :         return err;
    1776           0 :       }
    1777             : 
    1778             :       /* Store PC hash in text section. Check for writes outside the
    1779             :          text section.
    1780             :          https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1034-L1038 */
    1781       12150 :       ulong offset = fd_ulong_sat_add( fd_ulong_sat_mul( i, 8UL ), 4UL ); // offset in text section
    1782       12150 :       if( FD_UNLIKELY( offset+4UL>shtext->sh_size ) ) {
    1783           0 :         return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
    1784           0 :       }
    1785             : 
    1786       12150 :       FD_STORE( uint, ptr+4UL, pc_hash );
    1787       12150 :     }
    1788        2577 :   }
    1789             : 
    1790             :   /* Fixup all the relocations in the relocation section if exists. The
    1791             :      dynamic relocations table was already parsed and validated in
    1792             :      fd_sbpf_lenient_elf_parse().
    1793             :      https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1046-L1304 */
    1794        2577 :   {
    1795        2577 :     fd_elf64_rel const *  dt_rels    = (fd_elf64_rel const *)( elf->bin + elf_info->dt_rel_off );
    1796        2577 :     uint                  dt_rel_cnt = elf_info->dt_rel_sz / sizeof(fd_elf64_rel);
    1797             : 
    1798       22947 :     for( uint i=0U; i<dt_rel_cnt; i++ ) {
    1799       20370 :       fd_elf64_rel const * dt_rel   = &dt_rels[ i ];
    1800       20370 :       ulong                r_offset = dt_rel->r_offset;
    1801             : 
    1802             :       /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1068-L1303 */
    1803       20370 :       int err;
    1804       20370 :       switch( FD_ELF64_R_TYPE( dt_rel->r_info ) ) {
    1805           6 :         case FD_ELF_R_BPF_64_64:
    1806           6 :           err = fd_sbpf_r_bpf_64_64( elf, bin_sz, rodata, elf_info, dt_rel, r_offset );
    1807           6 :           break;
    1808       15294 :         case FD_ELF_R_BPF_64_RELATIVE:
    1809       15294 :           err = fd_sbpf_r_bpf_64_relative(elf, bin_sz, rodata, elf_info, r_offset );
    1810       15294 :           break;
    1811        5070 :         case FD_ELF_R_BPF_64_32:
    1812        5070 :           err = fd_sbpf_r_bpf_64_32( loader, prog, elf, bin_sz, rodata, elf_info, dt_rel, r_offset, config );
    1813        5070 :           break;
    1814           0 :         default:
    1815           0 :           return FD_SBPF_ELF_ERR_UNKNOWN_RELOCATION;
    1816       20370 :       }
    1817             : 
    1818       20370 :       if( FD_UNLIKELY( err!=FD_SBPF_ELF_SUCCESS ) ) {
    1819           0 :         return err;
    1820           0 :       }
    1821       20370 :     }
    1822        2577 :   }
    1823             : 
    1824             :   /* ...rest of this function is a no-op because
    1825             :      enable_symbol_and_section_labels is disabled in production. */
    1826             : 
    1827        2577 :   return FD_SBPF_ELF_SUCCESS;
    1828        2577 : }
    1829             : 
    1830             : /* Second part of load_with_lenient_parser().
    1831             : 
    1832             :    This function is responsible for "loading" an sBPF program. This
    1833             :    means...
    1834             :    1. Applies any relocations in-place to the rodata section.
    1835             :    2. Registers the program entrypoint and other valid calldests.
    1836             :    3. Parses and validates the rodata sections, zeroing out any gaps
    1837             :       between sections.
    1838             : 
    1839             :    Returns 0 on success and an ElfError error code on failure.
    1840             : 
    1841             :    https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L640-L689
    1842             :  */
    1843             : static int
    1844             : fd_sbpf_program_load_lenient( fd_sbpf_program_t *             prog,
    1845             :                               void const *                    bin,
    1846             :                               ulong                           bin_sz,
    1847             :                               fd_sbpf_loader_t *              loader,
    1848             :                               fd_sbpf_loader_config_t const * config,
    1849             :                               void *                          scratch,
    1850        2577 :                               ulong                           scratch_sz ) {
    1851             : 
    1852             :   /* Load (vs peek) starts here
    1853             :      https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L641 */
    1854             : 
    1855        2577 :   fd_sbpf_elf_t const * elf      = (fd_sbpf_elf_t const *)bin;
    1856        2577 :   fd_sbpf_elf_info_t *  elf_info = &prog->info;
    1857        2577 :   fd_elf64_shdr const * shdrs    = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
    1858        2577 :   fd_elf64_shdr const * sh_text  = &shdrs[ elf_info->shndx_text ];
    1859             : 
    1860             :   /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L642-L647 */
    1861        2577 :   int err = fd_sbpf_program_relocate( prog, bin, bin_sz, config, loader );
    1862        2577 :   if( FD_UNLIKELY( err ) ) return err;
    1863             : 
    1864             :   /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L649-L653 */
    1865        2577 :   ulong offset = fd_ulong_sat_sub( elf->ehdr.e_entry, sh_text->sh_addr );
    1866        2577 :   if( FD_UNLIKELY( offset&0x7UL ) ) { /* offset % 8 != 0 */
    1867           0 :     return FD_SBPF_ELF_ERR_INVALID_ENTRYPOINT;
    1868           0 :   }
    1869             : 
    1870             :   /* Unregister the entrypoint from the calldests, and register the
    1871             :      entry_pc. Our behavior slightly diverges from Agave's because we
    1872             :      rely on an explicit entry_pc field within the elf_info struct
    1873             :      to handle the b"entrypoint" symbol, and rely on PC hash inverses
    1874             :      for any other CALL_IMM targets.
    1875             : 
    1876             :      Note that even though we won't use the calldests value for the
    1877             :      entry pc, we still need to "register" it to check for any potential
    1878             :      symbol collisions and report errors accordingly. We unregister it
    1879             :      first by setting it to ULONG_MAX.
    1880             : 
    1881             :      TODO: Add special casing for static syscalls enabled. For now, it
    1882             :      is not implemented.
    1883             :      https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L654-L667 */
    1884        2577 :   prog->entry_pc = ULONG_MAX;
    1885        2577 :   ulong entry_pc = offset/8UL;
    1886        2577 :   err = fd_sbpf_register_function_hashed_legacy(
    1887        2577 :       loader,
    1888        2577 :       prog,
    1889        2577 :       (uchar const *)"entrypoint",
    1890        2577 :       strlen( "entrypoint" ),
    1891        2577 :       entry_pc,
    1892        2577 :       NULL );
    1893        2577 :   if( FD_UNLIKELY( err!=FD_SBPF_ELF_SUCCESS ) ) {
    1894           0 :     return err;
    1895           0 :   }
    1896             : 
    1897             :   /* Parse the ro sections.
    1898             :      https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L669-L676 */
    1899        2577 :   err = fd_sbpf_parse_ro_sections( prog, bin, bin_sz, config, scratch, scratch_sz );
    1900        2577 :   if( FD_UNLIKELY( err!=FD_SBPF_ELF_SUCCESS ) ) {
    1901           0 :     return err;
    1902           0 :   }
    1903             : 
    1904        2577 :   return FD_SBPF_ELF_SUCCESS;
    1905        2577 : }
    1906             : 
    1907             : /* Strict ELF loading (for SBPF V3+ programs).
    1908             : 
    1909             :    SBPF V3+ programs do not require relocations or calldests, so this
    1910             :    function is much cheaper than fd_sbpf_program_load_lenient.
    1911             : 
    1912             :    https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L406-L590 */
    1913             : static int
    1914             : fd_sbpf_program_load_strict( fd_sbpf_program_t * prog,
    1915          12 :                              void const *        bin ) {
    1916          12 :   fd_elf64_ehdr ehdr   = FD_LOAD( fd_elf64_ehdr, bin );
    1917          12 :   fd_elf64_phdr phdr_0 = FD_LOAD( fd_elf64_phdr, bin+sizeof(fd_elf64_ehdr) );
    1918          12 :   int skip_rodata      = phdr_0.p_flags != FD_SBPF_PF_R;
    1919             : 
    1920             :   /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L486-L496 */
    1921          12 :   fd_elf64_phdr bytecode_phdr;
    1922          12 :   if( FD_UNLIKELY( skip_rodata ) ) {
    1923           3 :     prog->rodata_sz = 0UL;
    1924           3 :     bytecode_phdr   = phdr_0;
    1925           9 :   } else {
    1926           9 :     prog->rodata_sz = phdr_0.p_memsz;
    1927           9 :     bytecode_phdr   = FD_LOAD( fd_elf64_phdr, bin+sizeof(fd_elf64_ehdr)+sizeof(fd_elf64_phdr) );
    1928             : 
    1929             :     /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L493
    1930             :        https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L497 */
    1931           9 :     fd_memcpy( prog->rodata, (uchar const *)bin + phdr_0.p_offset, phdr_0.p_filesz );
    1932           9 :   }
    1933             : 
    1934             :   /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L498-L499 */
    1935          12 :   prog->text = (ulong *)( (uchar *)prog->rodata + prog->rodata_sz );
    1936          12 :   fd_memcpy( (uchar *)prog->text, (uchar const *)bin + bytecode_phdr.p_offset, bytecode_phdr.p_filesz );
    1937             : 
    1938             :   /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L510-L514 */
    1939          12 :   prog->entry_pc = fd_ulong_sat_sub( ehdr.e_entry, bytecode_phdr.p_vaddr ) / 8UL;
    1940          12 :   return FD_SBPF_ELF_SUCCESS;
    1941          12 : }
    1942             : 
    1943             : int
    1944             : fd_sbpf_program_load( fd_sbpf_program_t *             prog,
    1945             :                       void const *                    bin,
    1946             :                       ulong                           bin_sz,
    1947             :                       fd_sbpf_syscalls_t *            syscalls,
    1948             :                       fd_sbpf_loader_config_t const * config,
    1949             :                       void *                          scratch,
    1950        2589 :                       ulong                           scratch_sz ) {
    1951        2589 :   fd_sbpf_loader_t loader = {
    1952        2589 :     .calldests = prog->calldests,
    1953        2589 :     .syscalls  = syscalls,
    1954        2589 :   };
    1955             : 
    1956             :   /* Invoke strict vs lenient loader
    1957             :      Note: info.sbpf_version is already set by fd_sbpf_program_parse()
    1958             :      https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L396-L402 */
    1959        2589 :   if( FD_UNLIKELY( fd_sbpf_enable_stricter_elf_headers_enabled( prog->info.sbpf_version ) ) ) {
    1960          12 :     return fd_sbpf_program_load_strict( prog, bin );
    1961          12 :   }
    1962        2577 :   return fd_sbpf_program_load_lenient( prog, bin, bin_sz, &loader, config, scratch, scratch_sz );
    1963        2589 : }
    1964             : 
    1965             : #undef ERR
    1966             : #undef FAIL
    1967             : #undef REQUIRE

Generated by: LCOV version 1.14