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 255275841 : #define FD_SBPF_MM_BYTECODE_START (0x100000000UL) /* bytecode / program region */
43 243917299 : #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 147634195 : fd_sbpf_range_contains( fd_sbpf_range_t const * range, ulong x ) {
65 147634195 : return !!(( range->lo<=x ) & ( x<range->hi ));
66 147634195 : }
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 157743035 : fd_sbpf_range_t * range ) {
79 157743035 : if( shdr->sh_type==FD_ELF_SHT_NOBITS ) {
80 0 : *range = (fd_sbpf_range_t) { .lo = 0UL, .hi = 0UL };
81 0 : return NULL;
82 157743035 : } else {
83 157743035 : *range = (fd_sbpf_range_t) { .lo = shdr->sh_offset, .hi = fd_ulong_sat_add( shdr->sh_offset, shdr->sh_size ) };
84 157743035 : return range;
85 157743035 : }
86 157743035 : }
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 16258844 : #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 99 : fd_sbpf_program_align( void ) {
127 99 : return alignof( fd_sbpf_program_t );
128 99 : }
129 :
130 : ulong
131 99 : fd_sbpf_program_footprint( fd_sbpf_elf_info_t const * info ) {
132 99 : FD_COMPILER_UNPREDICTABLE( info ); /* Make this appear as FD_FN_PURE (e.g. footprint might depend on info contents in future) */
133 99 : 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 93 : return FD_LAYOUT_FINI( FD_LAYOUT_APPEND( FD_LAYOUT_APPEND( FD_LAYOUT_INIT,
140 99 : alignof(fd_sbpf_program_t), sizeof(fd_sbpf_program_t) ),
141 99 : fd_sbpf_calldests_align(), fd_sbpf_calldests_footprint( info->calldests_max ) ), /* calldests bitmap */
142 99 : alignof(fd_sbpf_program_t) );
143 99 : }
144 :
145 : fd_sbpf_program_t *
146 : fd_sbpf_program_new( void * prog_mem,
147 : fd_sbpf_elf_info_t const * elf_info,
148 120 : void * rodata ) {
149 :
150 120 : if( FD_UNLIKELY( !prog_mem ) ) {
151 0 : FD_LOG_WARNING(( "NULL prog_mem" ));
152 0 : return NULL;
153 0 : }
154 :
155 120 : if( FD_UNLIKELY( !elf_info ) ) {
156 0 : FD_LOG_WARNING(( "NULL elf_info" ));
157 0 : return NULL;
158 0 : }
159 :
160 120 : 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 120 : 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 120 : FD_SCRATCH_ALLOC_INIT( laddr, prog_mem );
174 120 : 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 120 : *prog = (fd_sbpf_program_t) {
178 120 : .info = *elf_info,
179 120 : .rodata = rodata,
180 120 : .rodata_sz = 0UL,
181 120 : .text = (ulong *)((ulong)rodata + elf_info->text_off), /* FIXME: WHAT IF MISALIGNED */
182 120 : .entry_pc = ULONG_MAX,
183 120 : .calldests_shmem = NULL,
184 120 : .calldests = NULL,
185 120 : };
186 :
187 : /* If the text section is empty, or the program is SBPF V3+, then we
188 : do not need a calldests map. */
189 120 : ulong pc_max = elf_info->calldests_max;
190 120 : if( FD_LIKELY( ( !fd_sbpf_enable_stricter_elf_headers_enabled( elf_info->sbpf_version ) ) && pc_max!=0UL ) ) {
191 108 : prog->calldests_shmem = fd_sbpf_calldests_new(
192 108 : FD_SCRATCH_ALLOC_APPEND( laddr, fd_sbpf_calldests_align(),
193 108 : fd_sbpf_calldests_footprint( pc_max ) ),
194 108 : pc_max );
195 108 : prog->calldests = fd_sbpf_calldests_join( prog->calldests_shmem );
196 108 : }
197 :
198 120 : return prog;
199 120 : }
200 :
201 : void *
202 87 : fd_sbpf_program_delete( fd_sbpf_program_t * mem ) {
203 :
204 87 : if( FD_LIKELY( mem->calldests ) ) {
205 87 : fd_sbpf_calldests_delete( fd_sbpf_calldests_leave( mem->calldests ) );
206 87 : }
207 87 : fd_memset( mem, 0, sizeof(fd_sbpf_program_t) );
208 :
209 87 : return (void *)mem;
210 87 : }
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 195615189 : char const * cstr ) {
230 195615189 : return !!(slice_len==strlen( cstr ) && fd_memeq( slice, cstr, slice_len ));
231 195615189 : }
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 10667536 : char const * cstr ) {
241 10667536 : ulong cstr_len = strlen( cstr );
242 10667536 : return !!(slice_len>=cstr_len && fd_memeq( slice, cstr, cstr_len ));
243 10667536 : }
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 74323926 : 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 74323926 : 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 74323926 : ulong offset_in_file;
270 74323926 : 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 74323926 : ulong string_range_start = offset_in_file;
275 74323926 : ulong string_range_end = fd_ulong_min( section_header->sh_offset+section_header->sh_size, offset_in_file+maximum_length );
276 74323926 : 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 74323926 : 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 74323926 : uchar * null_terminator_ptr = memchr( (uchar const *)elf_bytes+string_range_start, 0, string_range_end-string_range_start );
287 74323926 : if( FD_UNLIKELY( null_terminator_ptr==NULL ) ) {
288 3 : return FD_SBPF_ELF_PARSER_ERR_STRING_TOO_LONG;
289 3 : }
290 :
291 74323923 : *out_slice = elf_bytes+string_range_start;
292 74323923 : *out_slice_len = (ulong)(null_terminator_ptr-*out_slice);
293 :
294 74323923 : return FD_SBPF_ELF_SUCCESS;
295 74323926 : }
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 118293180 : uint * opt_out_pc_hash ) {
331 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/program.rs#L156-L160 */
332 118293180 : uint pc_hash;
333 118293180 : uchar is_entrypoint = fd_sbpf_slice_cstr_eq( name, name_len, "entrypoint" ) ||
334 118293180 : target_pc==FD_SBPF_ENTRYPOINT_PC;
335 118293180 : if( FD_UNLIKELY( is_entrypoint ) ) {
336 919514 : 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 919514 : prog->entry_pc = target_pc;
342 :
343 : /* Optimization for this constant value */
344 919514 : pc_hash = FD_SBPF_ENTRYPOINT_HASH;
345 117373666 : } else {
346 117373666 : pc_hash = fd_pchash( (uint)target_pc );
347 117373666 : }
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 118293180 : 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 118293180 : if( FD_LIKELY( !is_entrypoint &&
363 118293180 : loader->calldests &&
364 118293180 : fd_sbpf_calldests_valid_idx( loader->calldests, target_pc ) ) ) {
365 117373666 : fd_sbpf_calldests_insert( loader->calldests, target_pc );
366 117373666 : }
367 :
368 118293180 : if( opt_out_pc_hash ) *opt_out_pc_hash = pc_hash;
369 118293180 : return FD_SBPF_ELF_SUCCESS;
370 118293180 : }
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 12 : ulong r_offset ) {
442 :
443 12 : 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 12 : 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 12 : 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 12 : fd_elf64_sym const * symbol = NULL;
459 12 : {
460 : /* Ensure the dynamic symbol table exists. */
461 12 : 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 12 : fd_elf64_shdr const * sh_dynsym = &shdrs[ info->shndx_dynsymtab ];
469 12 : fd_elf64_sym const * dynsym_table = (fd_elf64_sym const *)( elf->bin + sh_dynsym->sh_offset );
470 12 : 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 12 : ulong r_sym = FD_ELF64_R_SYM( dt_rel->r_info );
475 12 : if( FD_UNLIKELY( r_sym>=dynsym_cnt ) ) {
476 0 : return FD_SBPF_ELF_ERR_UNKNOWN_SYMBOL;
477 0 : }
478 12 : symbol = &dynsym_table[ r_sym ];
479 12 : }
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 12 : uint refd_addr = FD_LOAD( uint, &rodata[ imm_offset ] );
485 12 : 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 12 : if( addr<FD_SBPF_MM_PROGRAM_ADDR ) {
492 12 : addr = fd_ulong_sat_add( addr, FD_SBPF_MM_PROGRAM_ADDR );
493 12 : }
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 12 : ulong imm_low_offset = imm_offset;
498 12 : 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 12 : {
502 : /* Bounds check before writing to the rodata. */
503 12 : 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 12 : FD_STORE( uint, rodata+imm_low_offset, (uint)addr );
509 12 : }
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 12 : 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 12 : FD_STORE( uint, rodata+imm_high_offset, (uint)(addr>>32UL) );
521 12 : }
522 :
523 : /* ...rest of this function is a no-op because
524 : enable_symbol_and_section_labels is disabled in production. */
525 :
526 12 : return FD_SBPF_ELF_SUCCESS;
527 12 : }
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 146716913 : ulong r_offset ) {
540 :
541 146716913 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
542 146716913 : 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 146716913 : 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 146716913 : fd_sbpf_range_t text_section_range;
549 146716913 : if( fd_shdr_get_file_range( sh_text, &text_section_range ) &&
550 146716913 : 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 97200365 : ulong imm_low_offset = imm_offset;
558 97200365 : ulong imm_high_offset = fd_ulong_sat_add( r_offset,
559 97200365 : 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 97200365 : 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 97200365 : 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 97200365 : 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 97200365 : 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 97200365 : ulong refd_addr = ( (ulong)va_high<<32UL ) | va_low;
578 97200365 : 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 97200365 : if( refd_addr<FD_SBPF_MM_PROGRAM_ADDR ) {
588 97200365 : refd_addr = fd_ulong_sat_add( refd_addr, FD_SBPF_MM_PROGRAM_ADDR );
589 97200365 : }
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 97200365 : 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 97200365 : 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 97200365 : 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 97200365 : FD_STORE( uint, rodata+imm_high_offset, (uint)(refd_addr>>32UL) );
604 97200365 : } else {
605 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1216-L1228 */
606 49516548 : ulong refd_addr = 0UL;
607 :
608 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1230-L1239 */
609 49516548 : 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 49516545 : refd_addr = FD_LOAD( uint, rodata+imm_offset );
613 49516545 : 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 49516545 : 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 49516545 : FD_STORE( ulong, rodata+r_offset, refd_addr );
621 49516545 : }
622 :
623 146716910 : return FD_SBPF_ELF_SUCCESS;
624 146716913 : }
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 47683193 : fd_sbpf_loader_config_t const * config ) {
638 :
639 47683193 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
640 47683193 : 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 47683193 : 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 47683193 : fd_elf64_sym const * symbol = NULL;
648 :
649 : /* Ensure the dynamic symbol table exists. */
650 47683193 : 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 47683193 : fd_elf64_shdr const * sh_dynsym = &shdrs[ info->shndx_dynsymtab ];
658 47683193 : fd_elf64_sym const * dynsym_table = (fd_elf64_sym const *)( elf->bin + sh_dynsym->sh_offset );
659 47683193 : 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 47683193 : ulong r_sym = FD_ELF64_R_SYM( dt_rel->r_info );
664 47683193 : if( FD_UNLIKELY( r_sym>=dynsym_cnt ) ) {
665 0 : return FD_SBPF_ELF_ERR_UNKNOWN_SYMBOL;
666 0 : }
667 47683193 : 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 47683193 : if( FD_UNLIKELY( info->shndx_dynstr<0 ) ) {
682 0 : return FD_SBPF_ELF_ERR_UNKNOWN_SYMBOL;
683 0 : }
684 :
685 47683193 : uchar const * name;
686 47683193 : ulong name_len;
687 47683193 : fd_elf64_shdr const * dyn_section_names_shdr = &shdrs[ info->shndx_dynstr ];
688 47683193 : 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 47683193 : uint key = 0U;
695 47683193 : int symbol_is_function = ( FD_ELF64_ST_TYPE( symbol->st_info )==FD_ELF_STT_FUNC );
696 47683193 : {
697 47683193 : 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 917282 : fd_sbpf_range_t text_section_range = (fd_sbpf_range_t) {
700 917282 : .lo = sh_text->sh_addr,
701 917282 : .hi = fd_ulong_sat_add( sh_text->sh_addr, sh_text->sh_size ) };
702 917282 : 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 917282 : ulong target_pc = fd_ulong_sat_sub( symbol->st_value, sh_text->sh_addr ) / 8UL;
708 917282 : int err = fd_sbpf_register_function_hashed_legacy( loader, prog, name, name_len, target_pc, &key );
709 917282 : if( FD_UNLIKELY( err!=FD_SBPF_ELF_SUCCESS ) ) {
710 0 : return err;
711 0 : }
712 46765911 : } 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 46765911 : key = fd_murmur3_32(name, name_len, 0UL );
716 46765911 : if( FD_UNLIKELY( config->reject_broken_elfs &&
717 46765911 : fd_sbpf_syscalls_query( loader->syscalls, key, NULL )==NULL ) ) {
718 0 : return FD_SBPF_ELF_ERR_UNRESOLVED_SYMBOL;
719 0 : }
720 46765911 : }
721 47683193 : }
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 47683193 : 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 47683193 : FD_STORE( uint, rodata+imm_offset, key );
730 :
731 47683193 : return FD_SBPF_ELF_SUCCESS;
732 47683193 : }
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 27236319 : fd_sbpf_check_overlap( ulong a_start, ulong a_end, ulong b_start, ulong b_end ) {
882 27236319 : return !( ( a_end <= b_start || b_end <= a_start ) );
883 27236319 : }
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 919463 : ulong bin_sz ) {
892 :
893 : /* This documents the values that will be set in this function */
894 919463 : info->bin_sz = bin_sz;
895 919463 : info->phndx_dyn = -1;
896 919463 : info->shndx_dyn = -1;
897 919463 : info->shndx_symtab = -1;
898 919463 : info->shndx_strtab = -1;
899 919463 : info->shndx_dynstr = -1;
900 919463 : info->shndx_dynsymtab = -1;
901 :
902 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L149 */
903 919463 : if( FD_UNLIKELY( bin_sz<sizeof(fd_elf64_ehdr) ) ) {
904 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
905 0 : }
906 :
907 919463 : fd_elf64_ehdr ehdr = FD_LOAD( fd_elf64_ehdr, bin );
908 919463 : ulong ehdr_start = 0;
909 919463 : 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 919463 : int parse_ehdr_err =
914 919463 : ( fd_uint_load_4( ehdr.e_ident ) != FD_ELF_MAG_LE )
915 919463 : | ( ehdr.e_ident[ FD_ELF_EI_CLASS ] != FD_ELF_CLASS_64 )
916 919463 : | ( ehdr.e_ident[ FD_ELF_EI_DATA ] != FD_ELF_DATA_LE )
917 919463 : | ( ehdr.e_ident[ FD_ELF_EI_VERSION ] != 1 )
918 919463 : | ( ehdr.e_version != 1 )
919 919463 : | ( ehdr.e_ehsize != sizeof(fd_elf64_ehdr) )
920 919463 : | ( ehdr.e_phentsize != sizeof(fd_elf64_phdr) )
921 919463 : | ( ehdr.e_shentsize != sizeof(fd_elf64_shdr) )
922 919463 : | ( ehdr.e_shstrndx >= ehdr.e_shnum )
923 919463 : ;
924 919463 : 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 919463 : ulong phdr_start = ehdr.e_phoff;
931 919463 : ulong phdr_end, phdr_sz;
932 : /* Elf64::parse_program_header_table() */
933 919463 : {
934 919463 : 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 919463 : 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 919463 : 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 919463 : if( FD_UNLIKELY( phdr_sz%sizeof(fd_elf64_phdr)!=0UL ) ) {
955 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_SIZE;
956 0 : }
957 :
958 919463 : if( FD_UNLIKELY( phdr_end>bin_sz ) ) {
959 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
960 0 : }
961 :
962 919463 : if( FD_UNLIKELY( !fd_ulong_is_aligned( phdr_start, 8UL ) ) ) {
963 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_ALIGNMENT;
964 0 : }
965 919463 : }
966 :
967 : /* Section headers
968 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L167-L172 */
969 :
970 919463 : ulong shdr_start = ehdr.e_shoff;
971 919463 : ulong shdr_end, shdr_sz;
972 : /* Elf64::parse_section_header_table() */
973 919463 : {
974 919463 : 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 919463 : 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 919463 : 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 919463 : 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 919463 : 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 919463 : if( FD_UNLIKELY( shdr_end > bin_sz ) ) {
1002 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
1003 0 : }
1004 :
1005 919463 : if( FD_UNLIKELY( !fd_ulong_is_aligned( ehdr.e_shoff, 8UL ) ) ) {
1006 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_ALIGNMENT;
1007 0 : }
1008 919463 : }
1009 :
1010 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L174-L177 */
1011 919463 : fd_elf64_shdr shdr = FD_LOAD( fd_elf64_shdr, bin + ehdr.e_shoff );
1012 919463 : 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 919463 : ulong vaddr = 0UL;
1019 4583768 : for( ulong i=0; i<ehdr.e_phnum; i++ ) {
1020 3664305 : fd_elf64_phdr phdr = FD_LOAD( fd_elf64_phdr, bin + phdr_start + i*sizeof(fd_elf64_phdr) );
1021 3664305 : if( FD_UNLIKELY( phdr.p_type != FD_ELF_PT_LOAD ) ) {
1022 : /* Remember first PT_DYNAMIC program header for dynamic parsing */
1023 917012 : if( phdr.p_type==FD_ELF_PT_DYNAMIC && info->phndx_dyn == -1 ) {
1024 917012 : info->phndx_dyn = (int)i;
1025 917012 : }
1026 917012 : continue;
1027 917012 : }
1028 2747293 : if( FD_UNLIKELY( phdr.p_vaddr<vaddr ) ) {
1029 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_PROGRAM_HEADER;
1030 0 : }
1031 2747293 : ulong _offset_plus_size;
1032 2747293 : 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 2747293 : 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 2747293 : vaddr = phdr.p_vaddr;
1039 2747293 : }
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 919463 : ulong offset = 0UL;
1044 9103013 : 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 8183550 : fd_elf64_shdr shdr = FD_LOAD( fd_elf64_shdr, bin + shdr_start + i*sizeof(fd_elf64_shdr) );
1047 8183550 : 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 8183547 : if( shdr.sh_type==FD_ELF_SHT_DYNAMIC && info->shndx_dyn == -1 ) {
1053 917012 : info->shndx_dyn = (int)i;
1054 917012 : }
1055 :
1056 8183547 : ulong sh_start = shdr.sh_offset;
1057 8183547 : ulong sh_end;
1058 8183547 : 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 8183547 : 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 8183547 : 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 8183547 : 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 8183547 : if( FD_UNLIKELY( sh_start < offset ) ) {
1075 0 : return FD_SBPF_ELF_PARSER_ERR_SECTION_NOT_IN_ORDER;
1076 0 : }
1077 8183547 : offset = sh_end;
1078 8183547 : if( FD_UNLIKELY( sh_end > bin_sz ) ) {
1079 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
1080 0 : }
1081 8183547 : }
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 919463 : {
1090 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L340-L342 */
1091 919463 : 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 919463 : ulong section_names_shdr_idx = ehdr.e_shstrndx;
1097 919463 : 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 919463 : 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 9182432 : for( ulong i=0; i<ehdr.e_shnum; i++ ) {
1106 : /* Again... */
1107 8262972 : fd_elf64_shdr shdr = FD_LOAD( fd_elf64_shdr, bin + shdr_start + i*sizeof(fd_elf64_shdr) );
1108 :
1109 8262972 : uchar const * name;
1110 8262972 : ulong name_len;
1111 8262972 : 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 8262972 : 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 8262969 : 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 8262933 : } 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 8262897 : } else if( fd_sbpf_slice_cstr_eq( name, name_len, ".dynstr" ) ) {
1143 917009 : if( FD_UNLIKELY( info->shndx_dynstr != -1 ) ) {
1144 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_SECTION_HEADER;
1145 0 : }
1146 917009 : info->shndx_dynstr = (int)i;
1147 917009 : }
1148 8262969 : }
1149 919463 : }
1150 :
1151 : /* Parse dynamic
1152 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L241 */
1153 919460 : {
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 919460 : ulong dynamic_table_start = ULONG_MAX;
1162 919460 : 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 919460 : if( info->phndx_dyn >= 0 ) {
1166 917009 : fd_elf64_phdr dyn_ph = FD_LOAD( fd_elf64_phdr, bin + phdr_start + (ulong)info->phndx_dyn*sizeof(fd_elf64_phdr) );
1167 917009 : dynamic_table_start = dyn_ph.p_offset;
1168 917009 : 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 917009 : if( FD_UNLIKELY( dynamic_table_end<dynamic_table_start ||
1174 917009 : dynamic_table_end>bin_sz ||
1175 917009 : dyn_ph.p_filesz%sizeof(fd_elf64_dyn)!=0UL ||
1176 917009 : !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 917009 : }
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 919460 : 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 919460 : if( dynamic_table_start==ULONG_MAX ) {
1199 2451 : return FD_SBPF_ELF_SUCCESS;
1200 2451 : }
1201 :
1202 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L396-L407 */
1203 917009 : ulong dynamic_table[ FD_ELF_DT_NUM ] = { 0UL };
1204 917009 : ulong dyn_cnt = (dynamic_table_end - dynamic_table_start) / (ulong)sizeof(fd_elf64_dyn);
1205 9986652 : for( ulong i = 0UL; i<dyn_cnt; i++ ) {
1206 9986652 : fd_elf64_dyn dyn = FD_LOAD( fd_elf64_dyn, bin + dynamic_table_start + i*sizeof(fd_elf64_dyn) );
1207 :
1208 9986652 : if( FD_UNLIKELY( dyn.d_tag==FD_ELF_DT_NULL ) ) {
1209 917009 : break;
1210 917009 : }
1211 9069643 : if( FD_UNLIKELY( dyn.d_tag>=FD_ELF_DT_NUM ) ) {
1212 915176 : continue;
1213 915176 : }
1214 :
1215 8154467 : dynamic_table[ dyn.d_tag ] = dyn.d_un.d_val;
1216 8154467 : }
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 917009 : do {
1221 917009 : ulong vaddr = dynamic_table[ FD_ELF_DT_REL ];
1222 917009 : if( FD_UNLIKELY( vaddr==0UL ) ) {
1223 15 : break; /* from this do-while */
1224 15 : }
1225 :
1226 916994 : 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 916994 : ulong size = dynamic_table[ FD_ELF_DT_RELSZ ];
1231 916994 : 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 916994 : _Bool offset_found = 0;
1237 916994 : ulong offset;
1238 916994 : fd_elf64_phdr phdr;
1239 2744799 : for( ulong i=0; i<ehdr.e_phnum; i++ ) { /* program_header_for_vaddr */
1240 2744799 : phdr = FD_LOAD( fd_elf64_phdr, bin + phdr_start + i*sizeof(fd_elf64_phdr) );
1241 2744799 : ulong p_vaddr0 = phdr.p_vaddr;
1242 2744799 : ulong p_memsz = phdr.p_memsz;
1243 2744799 : ulong p_vaddr1;
1244 2744799 : 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 2744799 : if( p_vaddr0 <= vaddr && vaddr < p_vaddr1 ) {
1248 916994 : offset_found = 1;
1249 916994 : break;
1250 916994 : }
1251 2744799 : }
1252 916994 : if( offset_found ) {
1253 916994 : 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 916994 : 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 916994 : } 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 916994 : ulong offset_plus_size;
1274 916994 : 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 916994 : if( FD_UNLIKELY( ( size%sizeof(fd_elf64_rel)!=0UL ) ||
1281 916994 : ( offset_plus_size>bin_sz ) ||
1282 916994 : ( !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 916994 : info->dt_rel_off = (uint)offset;
1288 916994 : info->dt_rel_sz = (uint)size;
1289 916994 : } 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 917009 : do {
1293 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L452-L455 */
1294 917009 : ulong vaddr = dynamic_table[ FD_ELF_DT_SYMTAB ];
1295 917009 : if( FD_UNLIKELY( vaddr==0UL ) ) {
1296 0 : break; /* from this do-while */
1297 0 : }
1298 :
1299 917009 : fd_elf64_shdr shdr_sym = { 0 };
1300 5501985 : for( ulong i=0; i<ehdr.e_shnum; i++ ) {
1301 : /* Again... */
1302 5501985 : shdr_sym = FD_LOAD( fd_elf64_shdr, bin + shdr_start + i*sizeof(fd_elf64_shdr) );
1303 5501985 : if( shdr_sym.sh_addr == vaddr ) {
1304 917009 : info->shndx_dynsymtab = (int)i;
1305 917009 : break;
1306 917009 : }
1307 5501985 : }
1308 :
1309 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L457-L461 */
1310 917009 : 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 917009 : {
1316 917009 : 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 917009 : ulong shdr_sym_start = shdr_sym.sh_offset;
1320 917009 : 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 917009 : 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 917009 : 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 917009 : 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 917009 : if( FD_UNLIKELY( !fd_ulong_is_aligned( shdr_sym_start, 8UL ) ) ) {
1336 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_ALIGNMENT;
1337 0 : }
1338 917009 : }
1339 917009 : } while( 0 ); /* so we can break out */
1340 917009 : }
1341 :
1342 917009 : return FD_SBPF_ELF_SUCCESS;
1343 917009 : }
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 919460 : fd_elf64_shdr * text_shdr ) {
1353 :
1354 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L721-L736 */
1355 919460 : fd_elf64_ehdr ehdr = FD_LOAD( fd_elf64_ehdr, bin );
1356 919460 : 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 919460 : 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 919460 : 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 919460 : 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 919460 : 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 919460 : ulong shdr_start = ehdr.e_shoff;
1378 919460 : ulong section_names_shdr_idx = ehdr.e_shstrndx;
1379 919460 : 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 919460 : int shndx_text = -1;
1385 919460 : int writeable_err = 0;
1386 919460 : int oob_err = 0;
1387 8915332 : for( ulong i=0UL; i<ehdr.e_shnum; i++ ) {
1388 : /* Again... */
1389 7995872 : fd_elf64_shdr shdr = FD_LOAD( fd_elf64_shdr, bin + ehdr.e_shoff + i*sizeof(fd_elf64_shdr) );
1390 :
1391 7995872 : uchar const * name;
1392 7995872 : ulong name_len;
1393 7995872 : 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 7995872 : 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 7995872 : if( FD_UNLIKELY( fd_sbpf_slice_cstr_eq( name, name_len, ".text" ) ) ) {
1401 912510 : if( FD_LIKELY( shndx_text==-1 ) ) {
1402 912510 : *text_shdr = shdr; /* Store the text section header */
1403 912510 : shndx_text = (int)i;
1404 912510 : } else {
1405 0 : return FD_SBPF_ELF_ERR_NOT_ONE_TEXT_SECTION;
1406 0 : }
1407 912510 : }
1408 :
1409 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L780-L791 */
1410 7995872 : if( FD_UNLIKELY( fd_sbpf_slice_cstr_start_with( name, name_len, ".bss" ) ||
1411 7995872 : ( ( ( shdr.sh_flags & (FD_ELF_SHF_ALLOC | FD_ELF_SHF_WRITE) ) == (FD_ELF_SHF_ALLOC | FD_ELF_SHF_WRITE) ) &&
1412 7995872 : fd_sbpf_slice_cstr_start_with( name, name_len, ".data" ) &&
1413 7995872 : !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 7995872 : ulong shdr_end;
1420 7995872 : if( FD_UNLIKELY( __builtin_uaddl_overflow( shdr.sh_offset, shdr.sh_size, &shdr_end ) ||
1421 7995872 : shdr_end>bin_sz ) ) {
1422 0 : oob_err = 1;
1423 0 : }
1424 7995872 : }
1425 :
1426 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L776-L778 */
1427 919460 : 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 919460 : 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 919460 : 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 919460 : if( FD_UNLIKELY( !(
1443 919460 : text_shdr->sh_addr <= ehdr.e_entry && ehdr.e_entry < fd_ulong_sat_add( text_shdr->sh_addr, text_shdr->sh_size )
1444 919460 : ) ) ) {
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 919460 : fd_sbpf_range_t text_section_range;
1450 919460 : fd_shdr_get_file_range( text_shdr, &text_section_range );
1451 :
1452 919460 : info->text_off = (uint)text_shdr->sh_addr;
1453 919460 : info->text_sz = text_section_range.hi-text_section_range.lo;
1454 919460 : info->text_cnt = (uint)( info->text_sz/8UL );
1455 919460 : info->shndx_text = shndx_text;
1456 919460 : info->calldests_max = (fd_ulong_min( text_shdr->sh_size, bin_sz )+7UL)/8UL;
1457 :
1458 919460 : return FD_SBPF_ELF_SUCCESS;
1459 919460 : }
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 1838947 : ulong * out_slice_cnt ) {
1483 1838947 : fd_sbpf_elf_t const * elf = (fd_sbpf_elf_t const *)bin;
1484 1838947 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
1485 1838947 : fd_elf64_shdr const * section_names_shdr = &shdrs[ elf->ehdr.e_shstrndx ];
1486 :
1487 1838947 : ulong lowest_addr = ULONG_MAX;
1488 1838947 : ulong highest_addr = 0UL;
1489 1838947 : ulong ro_fill_length = 0UL; /* aggregated section length, excluding gaps */
1490 1838947 : uchar invalid_offsets = 0;
1491 1838947 : ulong slice_cnt = 0UL;
1492 :
1493 17780605 : for( uint i=0U; i<elf->ehdr.e_shnum; i++ ) {
1494 15941658 : fd_elf64_shdr const * section_header = &shdrs[ i ];
1495 :
1496 15941658 : uchar const * name;
1497 15941658 : ulong name_len;
1498 15941658 : 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 15941658 : if( FD_UNLIKELY( !fd_sbpf_slice_cstr_eq( name, name_len, ".text" ) &&
1502 15941658 : !fd_sbpf_slice_cstr_eq( name, name_len, ".rodata" ) &&
1503 15941658 : !fd_sbpf_slice_cstr_eq( name, name_len, ".data.rel.ro" ) &&
1504 15941658 : !fd_sbpf_slice_cstr_eq( name, name_len, ".eh_frame" ) ) ) {
1505 11014410 : continue;
1506 11014410 : }
1507 :
1508 4927248 : 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 5511798 : if( FD_LIKELY( !invalid_offsets ) ) {
1513 5511798 : if( FD_UNLIKELY( section_addr!=section_header->sh_offset ) ) {
1514 6 : invalid_offsets = 1;
1515 6 : }
1516 5511798 : }
1517 :
1518 4927248 : ulong vaddr_end = section_addr;
1519 5511798 : if( section_addr<FD_SBPF_MM_BYTECODE_START ) {
1520 5511798 : vaddr_end = fd_ulong_sat_add( section_addr, FD_SBPF_MM_BYTECODE_START );
1521 5511798 : }
1522 4927248 : if( FD_UNLIKELY( ( config->reject_broken_elfs && invalid_offsets ) ||
1523 4927248 : vaddr_end>FD_SBPF_MM_STACK_ADDR ) ) {
1524 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1525 0 : }
1526 :
1527 4927248 : fd_sbpf_range_t section_header_range;
1528 4927248 : fd_sbpf_range_t * range_res = fd_shdr_get_file_range( section_header, §ion_header_range );
1529 4927248 : if( FD_UNLIKELY( section_header_range.hi>bin_sz ) ) {
1530 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1531 0 : }
1532 4927248 : ulong section_data_len = section_header_range.hi-section_header_range.lo;
1533 :
1534 4927248 : lowest_addr = fd_ulong_min( lowest_addr, section_addr );
1535 4927248 : highest_addr = fd_ulong_max( highest_addr, fd_ulong_sat_add( section_addr, section_data_len ) );
1536 4927248 : ro_fill_length = fd_ulong_sat_add( ro_fill_length, section_data_len );
1537 :
1538 : /* skip empty ranges, e.g. SHT_NOBITS */
1539 4927248 : if( !range_res ) continue;
1540 :
1541 4927248 : if( slices ) slices[ slice_cnt ] = i;
1542 4927248 : slice_cnt++;
1543 4927248 : }
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 1838947 : if( FD_UNLIKELY( config->reject_broken_elfs &&
1551 1838947 : 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 1838947 : *out_highest_addr = highest_addr;
1556 1838947 : if( out_invalid_offsets ) *out_invalid_offsets = invalid_offsets;
1557 1838947 : if( out_slice_cnt ) *out_slice_cnt = slice_cnt;
1558 1838947 : return FD_SBPF_ELF_SUCCESS;
1559 1838947 : }
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 919457 : fd_sbpf_elf_info_t const * info ) {
1573 919457 : if( FD_UNLIKELY( info->shndx_text<0 ) ) return 0;
1574 919457 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
1575 919457 : fd_elf64_shdr const * sh_text = &shdrs[ info->shndx_text ];
1576 919457 : fd_sbpf_range_t text_range;
1577 919457 : fd_shdr_get_file_range( sh_text, &text_range );
1578 :
1579 919457 : fd_elf64_rel const * rels = (fd_elf64_rel const *)( elf->bin + info->dt_rel_off );
1580 919457 : uint rel_cnt = info->dt_rel_sz / sizeof(fd_elf64_rel);
1581 165342557 : for( uint i=0U; i<rel_cnt; i++ ) {
1582 164423909 : uint r_type = FD_ELF64_R_TYPE( rels[i].r_info );
1583 164423909 : ulong r_offset = rels[i].r_offset;
1584 164423909 : ulong r_end;
1585 164423909 : switch( r_type ) {
1586 6 : case FD_ELF_R_BPF_64_64: r_end = fd_ulong_sat_add( r_offset, 16UL ); break;
1587 128085147 : 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 40329998 : 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 164423909 : }
1591 164423103 : if( r_end>rodata_sz ) return 0; /* touches the tail -> not fast */
1592 164423103 : }
1593 918648 : return 1;
1594 919457 : }
1595 :
1596 : static int
1597 : fd_sbpf_lenient_text_fast_ok( fd_sbpf_elf_t const * elf,
1598 : ulong rodata_sz,
1599 919454 : fd_sbpf_elf_info_t const * info ) {
1600 919454 : if( FD_UNLIKELY( info->shndx_text<0 ) ) return 0;
1601 919454 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
1602 919454 : fd_elf64_shdr const * sh_text = &shdrs[ info->shndx_text ];
1603 919454 : return fd_ulong_sat_add( sh_text->sh_offset, sh_text->sh_size )<=rodata_sz;
1604 919454 : }
1605 :
1606 : /* First part of Agave's load_with_lenient_parser(). We split up this
1607 : function into two parts so we know how much memory we need to
1608 : allocate for the loading step. Returns an ElfError on failure and 0
1609 : on success.
1610 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L593-L638 */
1611 : static int
1612 : fd_sbpf_elf_peek_lenient( fd_sbpf_elf_info_t * info,
1613 : void const * bin,
1614 : ulong bin_sz,
1615 919463 : fd_sbpf_loader_config_t const * config ) {
1616 :
1617 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L607 */
1618 919463 : int res = fd_sbpf_lenient_elf_parse( info, bin, bin_sz );
1619 919463 : if( FD_UNLIKELY( res<0 ) ) {
1620 3 : return fd_sbpf_elf_parser_err_to_elf_err( res );
1621 3 : }
1622 :
1623 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L617 */
1624 919460 : fd_elf64_shdr text_shdr = { 0 };
1625 919460 : res = fd_sbpf_lenient_elf_validate( info, bin, bin_sz, &text_shdr );
1626 919460 : if( FD_UNLIKELY( res<0 ) ) {
1627 0 : return res;
1628 0 : }
1629 :
1630 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L620-L638 */
1631 919460 : {
1632 919460 : ulong text_section_vaddr = fd_ulong_sat_add( text_shdr.sh_addr, FD_SBPF_MM_BYTECODE_START );
1633 919460 : ulong vaddr_end = text_section_vaddr;
1634 :
1635 : /* Validate bounds and text section addrs / offsets.
1636 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L632-L638 */
1637 919460 : if( FD_UNLIKELY( ( config->reject_broken_elfs && text_shdr.sh_addr!=text_shdr.sh_offset ) ||
1638 919460 : vaddr_end>FD_SBPF_MM_STACK_ADDR ) ) {
1639 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1640 0 : }
1641 919460 : }
1642 :
1643 : /* Peek (vs load) stops here
1644 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L638 */
1645 :
1646 : /* Record load_buf_sz, the buffer the program cache allocates and the loader
1647 : assembles into. The fast (no-scratch) path is eligible when (a) the
1648 : read-only layout is computed without error, (b) every read-only section's
1649 : address equals its file offset (invalid_offsets==0), so the sections can
1650 : be assembled in place, and (c) every dynamic relocation lies fully within
1651 : the read-only image (fd_sbpf_lenient_relocs_fast_ok), and (d) the text
1652 : section's file extent lies within the read-only image
1653 : (fd_sbpf_lenient_text_fast_ok). When eligible,
1654 : load_buf_sz is the exact image size; otherwise it is bin_sz, which
1655 : fd_sbpf_loader_is_legacy_lenient reports so the loader takes the scratch
1656 : path over the full ELF. */
1657 919460 : ulong highest_addr = 0UL;
1658 919460 : uchar invalid_offsets = 0;
1659 919460 : int fast = ( fd_sbpf_lenient_ro_layout( bin, bin_sz, config, &highest_addr, &invalid_offsets, NULL, NULL )==FD_SBPF_ELF_SUCCESS ) &&
1660 919460 : ( invalid_offsets==0 ) &&
1661 919460 : fd_sbpf_lenient_relocs_fast_ok( (fd_sbpf_elf_t const *)bin, highest_addr, info ) &&
1662 919460 : fd_sbpf_lenient_text_fast_ok( (fd_sbpf_elf_t const *)bin, highest_addr, info );
1663 919460 : info->load_buf_sz = fast ? highest_addr : bin_sz;
1664 :
1665 919460 : return FD_SBPF_ELF_SUCCESS;
1666 919460 : }
1667 :
1668 : static int
1669 : fd_sbpf_program_get_sbpf_version_or_err( void const * bin,
1670 : ulong bin_sz,
1671 920141 : fd_sbpf_loader_config_t const * config ) {
1672 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L376-L381 */
1673 920141 : const ulong E_FLAGS_OFFSET = 48UL;
1674 :
1675 920141 : if( FD_UNLIKELY( bin_sz<E_FLAGS_OFFSET+sizeof(uint) ) ) {
1676 573 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1677 573 : }
1678 919568 : uint e_flags = FD_LOAD( uint, bin+E_FLAGS_OFFSET );
1679 :
1680 : /* https://github.com/anza-xyz/sbpf/blob/v0.13.0/src/elf.rs#L382-L390 */
1681 919568 : uint sbpf_version = ( e_flags < FD_SBPF_VERSION_COUNT ) ? e_flags : FD_SBPF_RESERVED;
1682 :
1683 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L399-L401 */
1684 919568 : if( FD_UNLIKELY( !( config->sbpf_min_version <= sbpf_version && sbpf_version <= config->sbpf_max_version ) ) ) {
1685 51 : return FD_SBPF_ELF_ERR_UNSUPPORTED_SBPF_VERSION;
1686 51 : }
1687 :
1688 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L403-L407 */
1689 919517 : return (int)sbpf_version;
1690 919568 : }
1691 :
1692 : int
1693 : fd_sbpf_elf_peek( fd_sbpf_elf_info_t * info,
1694 : void const * bin,
1695 : ulong bin_sz,
1696 920141 : fd_sbpf_loader_config_t const * config ) {
1697 : /* Extract sbpf_version (or error)
1698 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L376-L401 */
1699 920141 : int maybe_sbpf_version = fd_sbpf_program_get_sbpf_version_or_err( bin, bin_sz, config );
1700 920141 : if( FD_UNLIKELY( maybe_sbpf_version<0 ) ) {
1701 624 : return maybe_sbpf_version;
1702 624 : }
1703 :
1704 : /* Initialize info struct */
1705 919517 : *info = (fd_sbpf_elf_info_t) {
1706 919517 : .bin_sz = 0U,
1707 919517 : .text_off = 0U,
1708 919517 : .text_cnt = 0U,
1709 919517 : .text_sz = 0UL,
1710 919517 : .shndx_text = -1,
1711 919517 : .shndx_symtab = -1,
1712 919517 : .shndx_strtab = -1,
1713 919517 : .shndx_dyn = -1,
1714 919517 : .shndx_dynstr = -1,
1715 919517 : .shndx_dynsymtab = -1,
1716 919517 : .phndx_dyn = -1,
1717 919517 : .dt_rel_off = 0UL,
1718 919517 : .dt_rel_sz = 0UL,
1719 919517 : .sbpf_version = (uint)maybe_sbpf_version,
1720 : /* !!! Keep this in sync with -Werror=missing-field-initializers */
1721 919517 : };
1722 :
1723 : /* Invoke strict vs lenient parser. The strict parser is used for
1724 : SBPF version >= 3. The strict parser also returns an ElfParserError
1725 : while the lenient parser returns an ElfError, so we have to map
1726 : the strict parser's error code.
1727 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L403-L407 */
1728 919517 : if( FD_UNLIKELY( fd_sbpf_enable_stricter_elf_headers_enabled( info->sbpf_version ) ) ) {
1729 54 : return fd_sbpf_elf_parser_err_to_elf_err( fd_sbpf_elf_peek_strict( info, bin, bin_sz ) );
1730 54 : }
1731 919463 : return fd_sbpf_elf_peek_lenient( info, bin, bin_sz, config );
1732 919517 : }
1733 :
1734 : /* Parses and concatenates the readonly data sections. This function
1735 : also computes and sets the rodata_sz field inside the SBPF program
1736 : struct. scratch is a pointer to a scratch area with size scratch_sz,
1737 : used to allocate a temporary buffer for the parsed rodata sections
1738 : before copying it back into the rodata (recommended size is bin_sz).
1739 : Returns 0 on success and an ElfError error code on failure. On
1740 : success, the rodata and rodata_sz fields in the sbpf program struct
1741 : are updated.
1742 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L812-L987 */
1743 : static int
1744 : fd_sbpf_parse_ro_sections( fd_sbpf_program_t * prog,
1745 : void const * bin,
1746 : ulong bin_sz,
1747 : fd_sbpf_loader_config_t const * config,
1748 : void * scratch,
1749 919487 : ulong scratch_sz ) {
1750 :
1751 919487 : fd_sbpf_elf_t const * elf = (fd_sbpf_elf_t const *)bin;
1752 919487 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
1753 919487 : uchar * rodata = prog->rodata;
1754 :
1755 : /* Compute the read-only segment layout and the section-header indices of
1756 : the read-only slices. */
1757 919487 : ulong highest_addr = 0UL;
1758 919487 : ulong ro_slices_shidxs[ elf->ehdr.e_shnum ];
1759 919487 : ulong ro_slices_cnt = 0UL;
1760 919487 : int layout_err = fd_sbpf_lenient_ro_layout( bin, bin_sz, config, &highest_addr, NULL, ro_slices_shidxs, &ro_slices_cnt );
1761 919487 : if( FD_UNLIKELY( layout_err ) ) return layout_err;
1762 :
1763 : /* Note that optimize_rodata is always false.
1764 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L923-L984 */
1765 919487 : if( scratch ) { /* fallback path: assemble the ro image via a scratch buffer */
1766 : /* Readonly / non-readonly sections are mixed, so non-readonly
1767 : sections must be zeroed and the readonly sections must be copied
1768 : at their respective offsets.
1769 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L950-L983 */
1770 63 : ulong lowest_addr = 0UL;
1771 :
1772 : /* Bounds check. */
1773 63 : ulong buf_len = highest_addr;
1774 63 : if( FD_UNLIKELY( buf_len>bin_sz ) ) {
1775 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1776 0 : }
1777 :
1778 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L971-L976 */
1779 63 : if( FD_UNLIKELY( buf_len>scratch_sz ) ) {
1780 0 : FD_LOG_CRIT(( "scratch_sz is too small: %lu, required: %lu", scratch_sz, buf_len ));
1781 0 : }
1782 63 : uchar * ro_section = scratch;
1783 63 : fd_memset( ro_section, 0, buf_len );
1784 :
1785 201 : for( ulong i=0UL; i<ro_slices_cnt; i++ ) {
1786 138 : ulong sh_idx = ro_slices_shidxs[ i ];
1787 138 : fd_elf64_shdr const * shdr = &shdrs[ sh_idx ];
1788 138 : ulong section_addr = shdr->sh_addr;
1789 :
1790 : /* This was checked above and should never fail. */
1791 138 : fd_sbpf_range_t slice_range;
1792 138 : fd_shdr_get_file_range( shdr, &slice_range );
1793 138 : if( FD_UNLIKELY( slice_range.hi>bin_sz ) ) {
1794 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1795 0 : }
1796 :
1797 138 : ulong buf_offset_start = fd_ulong_sat_sub( section_addr, lowest_addr );
1798 138 : ulong slice_len = slice_range.hi-slice_range.lo;
1799 138 : if( FD_UNLIKELY( slice_len>buf_len ) ) {
1800 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1801 0 : }
1802 :
1803 138 : fd_memcpy( ro_section+buf_offset_start, rodata+slice_range.lo, slice_len );
1804 138 : }
1805 :
1806 : /* Copy the rodata section back in. */
1807 63 : prog->rodata_sz = buf_len;
1808 63 : fd_memcpy( rodata, ro_section, buf_len );
1809 919424 : } else { /* fast path: no scratch; the ro image is assembled in place */
1810 : /* The read-only image was copied into the destination buffer in place and
1811 : relocations applied there. The fast path is selected only when every
1812 : read-only section's address equals its file offset, so each section
1813 : already sits at its final position; zeroing the gaps between and around
1814 : the read-only slices produces the assembled image. The buffer is
1815 : load_buf_sz == highest_addr (fd_sbpf_elf_peek and this function compute
1816 : it via the same fd_sbpf_lenient_ro_layout walk). */
1817 919424 : ulong buf_len = highest_addr;
1818 919424 : if( FD_UNLIKELY( buf_len>bin_sz ) ) {
1819 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1820 0 : }
1821 :
1822 : /* Zero the complement of the union of the ro slices within [0,buf_len). */
1823 919424 : ulong cursor = 0UL;
1824 3672767 : for( ulong i=0UL; i<ro_slices_cnt; i++ ) {
1825 2753343 : fd_sbpf_range_t slice_range;
1826 2753343 : fd_shdr_get_file_range( &shdrs[ ro_slices_shidxs[ i ] ], &slice_range );
1827 2753343 : if( FD_UNLIKELY( slice_range.hi>bin_sz ) ) {
1828 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1829 0 : }
1830 2753343 : if( slice_range.lo>cursor ) fd_memset( rodata+cursor, 0, slice_range.lo-cursor );
1831 2753343 : cursor = fd_ulong_max( cursor, slice_range.hi );
1832 2753343 : }
1833 919424 : if( cursor<buf_len ) fd_memset( rodata+cursor, 0, buf_len-cursor );
1834 :
1835 919424 : prog->rodata_sz = buf_len;
1836 919424 : }
1837 :
1838 919487 : return FD_SBPF_ELF_SUCCESS;
1839 919487 : }
1840 :
1841 : /* Applies ELF relocations in-place. Returns 0 on success and an
1842 : ElfError error code on failure.
1843 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L990-L1331 */
1844 : static int
1845 : fd_sbpf_program_relocate( fd_sbpf_program_t * prog,
1846 : void const * bin,
1847 : ulong bin_sz,
1848 : fd_sbpf_loader_config_t const * config,
1849 : fd_sbpf_loader_t * loader,
1850 919490 : int is_fast ) {
1851 919490 : fd_sbpf_elf_info_t const * elf_info = &prog->info;
1852 919490 : fd_sbpf_elf_t const * elf = (fd_sbpf_elf_t const *)bin;
1853 919490 : uchar * rodata = prog->rodata;
1854 919490 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
1855 919490 : fd_elf64_shdr const * shtext = &shdrs[ elf_info->shndx_text ];
1856 :
1857 : /* rodata_bound is the size of the destination rodata buffer. On the fast
1858 : (no-scratch) path it is the final rodata_sz (the ELF tail is neither
1859 : copied nor allocated); on the fallback path it is bin_sz, making
1860 : everything below behave exactly as the original loader. Reads from the
1861 : original ELF image (elf->bin: symbol/string/reloc tables, which can live
1862 : in the tail) stay bounded by bin_sz; reads/writes into the rodata buffer
1863 : are bounded by rodata_bound. */
1864 919490 : ulong rodata_bound = is_fast ? elf_info->load_buf_sz : bin_sz;
1865 :
1866 : /* Copy the read-only image into the destination buffer (only the
1867 : [0,rodata_bound) prefix we actually need on the fast path). */
1868 919490 : fd_memcpy( rodata, elf->bin, rodata_bound );
1869 :
1870 : /* Fixup all program counter relative call instructions
1871 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1005-L1041 */
1872 919490 : {
1873 : /* Validate the bytes range of the text section.
1874 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1006-L1008 */
1875 919490 : fd_sbpf_range_t text_section_range;
1876 919490 : fd_shdr_get_file_range( shtext, &text_section_range );
1877 :
1878 919490 : ulong insn_cnt = (text_section_range.hi-text_section_range.lo)/8UL;
1879 919490 : if( FD_UNLIKELY( shtext->sh_size+shtext->sh_offset>rodata_bound ) ) {
1880 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1881 0 : }
1882 :
1883 919490 : uchar * ptr = rodata + shtext->sh_offset;
1884 :
1885 1793218298 : for( ulong i=0UL; i<insn_cnt; i++, ptr+=8UL ) {
1886 1792298808 : ulong insn = FD_LOAD( ulong, ptr );
1887 :
1888 : /* Check for call instruction. If immediate is UINT_MAX, assume
1889 : that compiler generated a relocation instead.
1890 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1015 */
1891 1792298808 : ulong opc = insn & 0xFF;
1892 1792298808 : int imm = (int)(insn >> 32UL);
1893 3150805010 : if( (opc!=FD_SBPF_OP_CALL_IMM) || (imm==-1) ) continue;
1894 :
1895 : /* Calculate and check the target PC
1896 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1016-L1021 */
1897 5 : long target_pc = fd_long_sat_add( fd_long_sat_add( (long)i, 1L ), imm);
1898 5 : if( FD_UNLIKELY( target_pc<0L || target_pc>=(long)insn_cnt ) ) {
1899 0 : return FD_SBPF_ELF_ERR_RELATIVE_JUMP_OUT_OF_BOUNDS;
1900 0 : }
1901 :
1902 : /* Update the calldests
1903 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1027-L1032 */
1904 5 : uint pc_hash;
1905 5 : int err = fd_sbpf_register_function_hashed_legacy( loader, prog, NULL, 0UL, (ulong)target_pc, &pc_hash );
1906 5 : if( FD_UNLIKELY( err!=FD_SBPF_ELF_SUCCESS ) ) {
1907 0 : return err;
1908 0 : }
1909 :
1910 : /* Store PC hash in text section. Check for writes outside the
1911 : text section.
1912 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1034-L1038 */
1913 5 : ulong offset = fd_ulong_sat_add( fd_ulong_sat_mul( i, 8UL ), 4UL ); // offset in text section
1914 5 : if( FD_UNLIKELY( offset+4UL>shtext->sh_size ) ) {
1915 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1916 0 : }
1917 :
1918 5 : FD_STORE( uint, ptr+4UL, pc_hash );
1919 5 : }
1920 919490 : }
1921 :
1922 : /* Fixup all the relocations in the relocation section if exists. The
1923 : dynamic relocations table was already parsed and validated in
1924 : fd_sbpf_lenient_elf_parse().
1925 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1046-L1304 */
1926 919490 : {
1927 919490 : fd_elf64_rel const * dt_rels = (fd_elf64_rel const *)( elf->bin + elf_info->dt_rel_off );
1928 919490 : uint dt_rel_cnt = elf_info->dt_rel_sz / sizeof(fd_elf64_rel);
1929 :
1930 195319605 : for( uint i=0U; i<dt_rel_cnt; i++ ) {
1931 194400118 : fd_elf64_rel const * dt_rel = &dt_rels[ i ];
1932 194400118 : ulong r_offset = dt_rel->r_offset;
1933 194400118 : uint r_type = FD_ELF64_R_TYPE( dt_rel->r_info );
1934 :
1935 : /* Relocations write into the destination buffer (rodata_bound bytes) and
1936 : read tables from the original ELF (bin_sz bytes). */
1937 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1068-L1303 */
1938 194400118 : int err;
1939 194400118 : switch( r_type ) {
1940 12 : case FD_ELF_R_BPF_64_64:
1941 12 : err = fd_sbpf_r_bpf_64_64( elf, rodata_bound, rodata, elf_info, dt_rel, r_offset );
1942 12 : break;
1943 146716913 : case FD_ELF_R_BPF_64_RELATIVE:
1944 146716913 : err = fd_sbpf_r_bpf_64_relative(elf, rodata_bound, rodata, elf_info, r_offset );
1945 146716913 : break;
1946 47683193 : case FD_ELF_R_BPF_64_32:
1947 47683193 : err = fd_sbpf_r_bpf_64_32( loader, prog, elf, bin_sz, rodata_bound, rodata, elf_info, dt_rel, r_offset, config );
1948 47683193 : break;
1949 0 : default:
1950 0 : return FD_SBPF_ELF_ERR_UNKNOWN_RELOCATION;
1951 194400118 : }
1952 :
1953 194400118 : if( FD_UNLIKELY( err!=FD_SBPF_ELF_SUCCESS ) ) {
1954 3 : return err;
1955 3 : }
1956 194400118 : }
1957 919490 : }
1958 :
1959 : /* ...rest of this function is a no-op because
1960 : enable_symbol_and_section_labels is disabled in production. */
1961 :
1962 919487 : return FD_SBPF_ELF_SUCCESS;
1963 919490 : }
1964 :
1965 : /* Second part of load_with_lenient_parser().
1966 :
1967 : This function is responsible for "loading" an sBPF program. This
1968 : means...
1969 : 1. Applies any relocations in-place to the rodata section.
1970 : 2. Registers the program entrypoint and other valid calldests.
1971 : 3. Parses and validates the rodata sections, zeroing out any gaps
1972 : between sections.
1973 :
1974 : Returns 0 on success and an ElfError error code on failure.
1975 :
1976 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L640-L689
1977 : */
1978 : static int
1979 : fd_sbpf_program_load_lenient( fd_sbpf_program_t * prog,
1980 : void const * bin,
1981 : ulong bin_sz,
1982 : fd_sbpf_loader_t * loader,
1983 : fd_sbpf_loader_config_t const * config,
1984 : void * scratch,
1985 919490 : ulong scratch_sz ) {
1986 :
1987 : /* Load (vs peek) starts here
1988 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L641 */
1989 :
1990 919490 : fd_sbpf_elf_t const * elf = (fd_sbpf_elf_t const *)bin;
1991 919490 : fd_sbpf_elf_info_t * elf_info = &prog->info;
1992 919490 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
1993 919490 : fd_elf64_shdr const * sh_text = &shdrs[ elf_info->shndx_text ];
1994 :
1995 : /* Fast (no-scratch) path is selected by the caller passing scratch==NULL,
1996 : which the program cache does for fast-eligible programs (peek set
1997 : load_buf_sz < bin_sz, i.e. !fd_sbpf_loader_is_legacy_lenient). On the
1998 : fast path the rodata buffer is sized to load_buf_sz and the read-only
1999 : image is assembled in place; otherwise we take the original
2000 : scratch-based path with a bin_sz buffer. */
2001 919490 : int is_fast = ( scratch==NULL );
2002 :
2003 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L642-L647 */
2004 919490 : int err = fd_sbpf_program_relocate( prog, bin, bin_sz, config, loader, is_fast );
2005 919490 : if( FD_UNLIKELY( err ) ) return err;
2006 :
2007 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L649-L653 */
2008 919487 : ulong offset = fd_ulong_sat_sub( elf->ehdr.e_entry, sh_text->sh_addr );
2009 919487 : if( FD_UNLIKELY( offset&0x7UL ) ) { /* offset % 8 != 0 */
2010 0 : return FD_SBPF_ELF_ERR_INVALID_ENTRYPOINT;
2011 0 : }
2012 :
2013 : /* Unregister the entrypoint from the calldests, and register the
2014 : entry_pc. Our behavior slightly diverges from Agave's because we
2015 : rely on an explicit entry_pc field within the elf_info struct
2016 : to handle the b"entrypoint" symbol, and rely on PC hash inverses
2017 : for any other CALL_IMM targets.
2018 :
2019 : Note that even though we won't use the calldests value for the
2020 : entry pc, we still need to "register" it to check for any potential
2021 : symbol collisions and report errors accordingly. We unregister it
2022 : first by setting it to ULONG_MAX.
2023 :
2024 : TODO: Add special casing for static syscalls enabled. For now, it
2025 : is not implemented.
2026 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L654-L667 */
2027 919487 : prog->entry_pc = ULONG_MAX;
2028 919487 : ulong entry_pc = offset/8UL;
2029 919487 : err = fd_sbpf_register_function_hashed_legacy(
2030 919487 : loader,
2031 919487 : prog,
2032 919487 : (uchar const *)"entrypoint",
2033 919487 : strlen( "entrypoint" ),
2034 919487 : entry_pc,
2035 919487 : NULL );
2036 919487 : if( FD_UNLIKELY( err!=FD_SBPF_ELF_SUCCESS ) ) {
2037 0 : return err;
2038 0 : }
2039 :
2040 : /* Parse the ro sections.
2041 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L669-L676 */
2042 919487 : err = fd_sbpf_parse_ro_sections( prog, bin, bin_sz, config, scratch, scratch_sz );
2043 919487 : if( FD_UNLIKELY( err!=FD_SBPF_ELF_SUCCESS ) ) {
2044 0 : return err;
2045 0 : }
2046 :
2047 919487 : return FD_SBPF_ELF_SUCCESS;
2048 919487 : }
2049 :
2050 : /* Strict ELF loading (for SBPF V3+ programs).
2051 :
2052 : SBPF V3+ programs do not require relocations or calldests, so this
2053 : function is much cheaper than fd_sbpf_program_load_lenient.
2054 :
2055 : https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L406-L590 */
2056 : static int
2057 : fd_sbpf_program_load_strict( fd_sbpf_program_t * prog,
2058 12 : void const * bin ) {
2059 12 : fd_elf64_ehdr ehdr = FD_LOAD( fd_elf64_ehdr, bin );
2060 12 : fd_elf64_phdr phdr_0 = FD_LOAD( fd_elf64_phdr, bin+sizeof(fd_elf64_ehdr) );
2061 12 : int skip_rodata = phdr_0.p_flags != FD_SBPF_PF_R;
2062 :
2063 : /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L486-L496 */
2064 12 : fd_elf64_phdr bytecode_phdr;
2065 12 : if( FD_UNLIKELY( skip_rodata ) ) {
2066 3 : prog->rodata_sz = 0UL;
2067 3 : bytecode_phdr = phdr_0;
2068 9 : } else {
2069 9 : prog->rodata_sz = phdr_0.p_memsz;
2070 9 : bytecode_phdr = FD_LOAD( fd_elf64_phdr, bin+sizeof(fd_elf64_ehdr)+sizeof(fd_elf64_phdr) );
2071 :
2072 : /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L493
2073 : https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L497
2074 : Note: memcpy merged below */
2075 : // fd_memcpy( prog->rodata, (uchar const *)bin + phdr_0.p_offset, phdr_0.p_filesz );
2076 9 : }
2077 :
2078 : /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L498-L499
2079 : Note: memcpy merged below */
2080 12 : prog->text = (ulong *)( (uchar *)prog->rodata + prog->rodata_sz );
2081 : // fd_memcpy( (uchar *)prog->text, (uchar const *)bin + bytecode_phdr.p_offset, bytecode_phdr.p_filesz );
2082 :
2083 : /* Copy the rodata and bytecode (text) segments into the destination buffer.
2084 : rodata and text are contiguous, so we can copy them in a single memcpy.
2085 : text_sz >= 8, so we can safely use memcpy. */
2086 12 : memcpy( prog->rodata,
2087 12 : (uchar const *)bin + phdr_0.p_offset,
2088 12 : prog->rodata_sz + (ulong)prog->info.text_sz );
2089 :
2090 : /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L510-L514 */
2091 12 : prog->entry_pc = fd_ulong_sat_sub( ehdr.e_entry, bytecode_phdr.p_vaddr ) / 8UL;
2092 12 : return FD_SBPF_ELF_SUCCESS;
2093 12 : }
2094 :
2095 : int
2096 : fd_sbpf_program_load( fd_sbpf_program_t * prog,
2097 : void const * bin,
2098 : ulong bin_sz,
2099 : fd_sbpf_syscalls_t * syscalls,
2100 : fd_sbpf_loader_config_t const * config,
2101 : void * scratch,
2102 919502 : ulong scratch_sz ) {
2103 919502 : fd_sbpf_loader_t loader = {
2104 919502 : .calldests = prog->calldests,
2105 919502 : .syscalls = syscalls,
2106 919502 : };
2107 :
2108 : /* Invoke strict vs lenient loader
2109 : Note: info.sbpf_version is already set by fd_sbpf_program_parse()
2110 : https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L396-L402 */
2111 919502 : if( FD_UNLIKELY( fd_sbpf_enable_stricter_elf_headers_enabled( prog->info.sbpf_version ) ) ) {
2112 12 : return fd_sbpf_program_load_strict( prog, bin );
2113 12 : }
2114 919490 : return fd_sbpf_program_load_lenient( prog, bin, bin_sz, &loader, config, scratch, scratch_sz );
2115 919502 : }
2116 :
2117 : #undef ERR
2118 : #undef FAIL
2119 : #undef REQUIRE
|