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