LCOV - code coverage report
Current view: top level - ballet/sbpf - fd_sbpf_loader.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 983 1082 90.9 %
Date: 2026-11-11 12:40:43 Functions: 28 29 96.6 %

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

Generated by: LCOV version 1.14