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, §ion_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, §ion_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, §ion_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
|