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