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/fd_util.h"
5 : #include "../../util/bits/fd_sat.h"
6 : #include "../murmur3/fd_murmur3.h"
7 :
8 : #include <assert.h>
9 : #include <stdio.h>
10 :
11 : /* ELF loader, part 1 **************************************************
12 :
13 : Start with a static piece of scratch memory and do basic validation
14 : of the file content. Walk the section table once and remember
15 : sections of interest.
16 :
17 : ### Terminology
18 :
19 : This source follows common ELF naming practices.
20 :
21 : section: a named data region present in the ELF file
22 : segment: a contiguous memory region containing sections
23 : (not necessarily contiguous in the ELF file)
24 :
25 : physical address (paddr): Byte offset into ELF file (uchar * bin)
26 : virtual address (vaddr): VM memory address */
27 :
28 : /* Provide convenient access to file header and ELF content */
29 :
30 : __extension__ union fd_sbpf_elf {
31 : fd_elf64_ehdr ehdr;
32 : uchar bin[0];
33 : };
34 : typedef union fd_sbpf_elf fd_sbpf_elf_t;
35 :
36 : /* FD_SBPF_MM_{...}_ADDR are hardcoded virtual addresses of segments
37 : in the sBPF virtual machine.
38 :
39 : FIXME: These should be defined elsewhere */
40 :
41 9 : #define FD_SBPF_MM_BYTECODE_ADDR (0x0UL) /* bytecode */
42 405 : #define FD_SBPF_MM_RODATA_ADDR (0x100000000UL) /* readonly program data */
43 9108 : #define FD_SBPF_MM_PROGRAM_ADDR (0x100000000UL) /* readonly program data */
44 9 : #define FD_SBPF_MM_STACK_ADDR (0x200000000UL) /* stack */
45 9 : #define FD_SBPF_MM_HEAP_ADDR (0x300000000UL) /* heap */
46 36 : #define FD_SBPF_MM_REGION_SZ (0x100000000UL) /* max region size */
47 :
48 9 : #define FD_SBPF_PF_X (1U) /* executable */
49 54 : #define FD_SBPF_PF_W (2U) /* writable */
50 27 : #define FD_SBPF_PF_R (4U) /* readable */
51 18 : #define FD_SBPF_PF_RW (FD_SBPF_PF_R|FD_SBPF_PF_W)
52 :
53 54 : #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 5580 : fd_sbpf_range_contains( fd_sbpf_range_t const * range, ulong x ) {
65 5580 : return !!(( range->lo<=x ) & ( x<range->hi ));
66 5580 : }
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 5907 : fd_sbpf_range_t * range ) {
79 5907 : if( shdr->sh_type==FD_ELF_SHT_NOBITS ) {
80 0 : *range = (fd_sbpf_range_t) { .lo = 0UL, .hi = 0UL };
81 0 : return NULL;
82 5907 : } else {
83 5907 : *range = (fd_sbpf_range_t) { .lo = shdr->sh_offset, .hi = fd_ulong_sat_add( shdr->sh_offset, shdr->sh_size ) };
84 5907 : return range;
85 5907 : }
86 5907 : }
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 12 : fd_sbpf_elf_parser_err_to_elf_err( int err ) {
92 12 : switch( err ) {
93 9 : case FD_SBPF_ELF_SUCCESS:
94 9 : return err;
95 0 : case FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS:
96 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
97 0 : case FD_SBPF_ELF_PARSER_ERR_INVALID_PROGRAM_HEADER:
98 0 : return FD_SBPF_ELF_ERR_INVALID_PROGRAM_HEADER;
99 3 : default:
100 3 : return FD_SBPF_ELF_ERR_FAILED_TO_PARSE;
101 12 : }
102 12 : }
103 :
104 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L11-L13 */
105 1656 : #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 36 : fd_sbpf_program_align( void ) {
127 36 : return alignof( fd_sbpf_program_t );
128 36 : }
129 :
130 : ulong
131 36 : fd_sbpf_program_footprint( fd_sbpf_elf_info_t const * info ) {
132 36 : FD_COMPILER_UNPREDICTABLE( info ); /* Make this appear as FD_FN_PURE (e.g. footprint might depened on info contents in future) */
133 36 : if( FD_UNLIKELY( fd_sbpf_enable_stricter_elf_headers_enabled( info->sbpf_version ) ) ) {
134 : /* SBPF v3+ no longer neeeds calldests bitmap */
135 0 : return FD_LAYOUT_FINI( FD_LAYOUT_APPEND( FD_LAYOUT_INIT,
136 0 : alignof(fd_sbpf_program_t), sizeof(fd_sbpf_program_t) ),
137 0 : alignof(fd_sbpf_program_t) );
138 0 : }
139 36 : return FD_LAYOUT_FINI( FD_LAYOUT_APPEND( FD_LAYOUT_APPEND( FD_LAYOUT_INIT,
140 36 : alignof(fd_sbpf_program_t), sizeof(fd_sbpf_program_t) ),
141 36 : fd_sbpf_calldests_align(), fd_sbpf_calldests_footprint( info->text_cnt ) ), /* calldests bitmap */
142 36 : alignof(fd_sbpf_program_t) );
143 36 : }
144 :
145 : fd_sbpf_program_t *
146 : fd_sbpf_program_new( void * prog_mem,
147 : fd_sbpf_elf_info_t const * elf_info,
148 36 : void * rodata ) {
149 :
150 36 : if( FD_UNLIKELY( !prog_mem ) ) {
151 0 : FD_LOG_WARNING(( "NULL prog_mem" ));
152 0 : return NULL;
153 0 : }
154 :
155 36 : if( FD_UNLIKELY( !elf_info ) ) {
156 0 : FD_LOG_WARNING(( "NULL elf_info" ));
157 0 : return NULL;
158 0 : }
159 :
160 36 : 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 36 : 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 36 : FD_SCRATCH_ALLOC_INIT( laddr, prog_mem );
174 36 : 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 36 : .info = *elf_info,
179 36 : .rodata = rodata,
180 36 : .rodata_sz = 0UL,
181 36 : .text = (ulong *)((ulong)rodata + elf_info->text_off), /* FIXME: WHAT IF MISALIGNED */
182 36 : .entry_pc = ULONG_MAX,
183 36 : };
184 :
185 : /* If the text section is empty, then we do not need a calldests map. */
186 36 : ulong pc_max = elf_info->text_cnt;
187 36 : if( FD_UNLIKELY( fd_sbpf_enable_stricter_elf_headers_enabled( elf_info->sbpf_version ) || pc_max==0UL ) ) {
188 : /* No calldests map in SBPF v3+ or if text_cnt is 0. */
189 3 : prog->calldests_shmem = NULL;
190 3 : prog->calldests = NULL;
191 33 : } else {
192 : /* Initialize calldests map. */
193 33 : prog->calldests_shmem = fd_sbpf_calldests_new(
194 33 : FD_SCRATCH_ALLOC_APPEND( laddr, fd_sbpf_calldests_align(),
195 33 : fd_sbpf_calldests_footprint( pc_max ) ),
196 0 : pc_max );
197 0 : prog->calldests = fd_sbpf_calldests_join( prog->calldests_shmem );
198 33 : }
199 :
200 36 : return prog;
201 36 : }
202 :
203 : void *
204 30 : fd_sbpf_program_delete( fd_sbpf_program_t * mem ) {
205 :
206 30 : if( FD_LIKELY( mem->calldests ) ) {
207 30 : fd_sbpf_calldests_delete( fd_sbpf_calldests_leave( mem->calldests ) );
208 30 : }
209 30 : fd_memset( mem, 0, sizeof(fd_sbpf_program_t) );
210 :
211 30 : return (void *)mem;
212 30 : }
213 :
214 : /* fd_sbpf_loader_t contains various temporary state during loading. */
215 :
216 : struct fd_sbpf_loader {
217 : /* External objects */
218 : ulong * calldests; /* owned by program. NULL if text_cnt = 0 or SBPF v3+ */
219 : fd_sbpf_syscalls_t * syscalls; /* owned by caller */
220 : };
221 : typedef struct fd_sbpf_loader fd_sbpf_loader_t;
222 :
223 : /* fd_sbpf_slice_cstr_eq is a helper method for checking equality
224 : between a slice of memory to a null-terminated C-string. Unlike
225 : strcmp, this function does not include the null-terminator in the
226 : comparison. Returns 1 if the first slice_len bytes of the slice and
227 : cstr are equal, and 0 otherwise. */
228 : static inline int
229 : fd_sbpf_slice_cstr_eq( uchar const * slice,
230 : ulong slice_len,
231 10254 : char const * cstr ) {
232 10254 : return !!(slice_len==strlen( cstr ) && fd_memeq( slice, cstr, slice_len ));
233 10254 : }
234 :
235 : /* fd_sbpf_slice_cstr_start_with is a helper method for checking that a
236 : null-terminated C-string is a prefix of a slice of memory. Returns 1
237 : if the first strlen(cstr) bytes of cstr is a prefix of slice, and 0
238 : otherwise. */
239 : static inline int
240 : fd_sbpf_slice_cstr_start_with( uchar const * slice,
241 : ulong slice_len,
242 1011 : char const * cstr ) {
243 1011 : ulong cstr_len = strlen( cstr );
244 1011 : return !!(slice_len>=cstr_len && fd_memeq( slice, cstr, cstr_len ));
245 1011 : }
246 :
247 : /* fd_sbpf_lenient_get_string_in_section queries a single string from a
248 : section which is marked as SHT_STRTAB. Returns an ElfParserError on
249 : failure, and leaves *out_slice and *out_slice_len in an undefined
250 : state. On success, returns 0 and sets *out_slice to a pointer into
251 : elf_bytes corresponding to the beginning of the string within the
252 : section. *out_slice_len is set to the length of the resulting slice.
253 : Note that *out_slice_len does not include the null-terminator of the
254 : resulting string.
255 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L467-L496 */
256 : int
257 : fd_sbpf_lenient_get_string_in_section( uchar const * elf_bytes,
258 : ulong elf_bytes_len,
259 : fd_elf64_shdr const * section_header,
260 : uint offset_in_section,
261 : ulong maximum_length,
262 : uchar const ** out_slice,
263 4206 : ulong * out_slice_len ) {
264 : /* This could be checked only once outside the loop, but to keep the code the same...
265 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L474-L476 */
266 4206 : if( FD_UNLIKELY( section_header->sh_type!=FD_ELF_SHT_STRTAB ) ) {
267 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_SECTION_HEADER;
268 0 : }
269 :
270 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L477-L482 */
271 4206 : ulong offset_in_file;
272 4206 : if( FD_UNLIKELY( __builtin_uaddl_overflow( section_header->sh_offset, offset_in_section, &offset_in_file ) ) ) {
273 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
274 0 : }
275 :
276 4206 : ulong string_range_start = offset_in_file;
277 4206 : ulong string_range_end = fd_ulong_min( section_header->sh_offset+section_header->sh_size, offset_in_file+maximum_length );
278 4206 : if( FD_UNLIKELY( string_range_end>elf_bytes_len ) ) {
279 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
280 0 : }
281 : /* In rust vec.get([n..n]) returns [], so this is accepted.
282 : vec.get([n..m]) with m<n returns None, so it throws ElfParserError::OutOfBounds. */
283 4206 : if( FD_UNLIKELY( string_range_end<string_range_start ) ) {
284 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
285 0 : }
286 :
287 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L486-L495 */
288 4206 : uchar * null_terminator_ptr = memchr( (uchar const *)elf_bytes+string_range_start, 0, string_range_end-string_range_start );
289 4206 : if( FD_UNLIKELY( null_terminator_ptr==NULL ) ) {
290 3 : return FD_SBPF_ELF_PARSER_ERR_STRING_TOO_LONG;
291 3 : }
292 :
293 4203 : *out_slice = elf_bytes+string_range_start;
294 4203 : *out_slice_len = (ulong)(null_terminator_ptr-*out_slice);
295 :
296 4203 : return FD_SBPF_ELF_SUCCESS;
297 4206 : }
298 :
299 : /* Registers a target PC into the calldests function registry. Returns
300 : 0 on success, inserts the target PC into the calldests, and sets
301 : *opt_out_pc_hash to murmur3_32(target_pc) (if opt_out_pc_hash is
302 : non-NULL). Returns FD_SBPF_ELF_ERR_SYMBOL_HASH_COLLISION on failure
303 : if the target PC is already in the syscalls registry and leaves
304 : out_pc_hash in an undefined state.
305 :
306 : An important note is that Agave's implementation uses a map to store
307 : key-value pairs of (murmur3_32(target_pc), target_pc) within the
308 : calldests. We optimize this by using a set containing
309 : target_pc (this is our calldests map), and then deriving
310 : the target PC on the fly given murmur3_32(target_pc) (provided as
311 : imm) in the VM by computing the inverse hash (since murmur3_32 is
312 : bijective for uints).
313 :
314 : Another important note is that if a key-value pair already exists in
315 : Agave's calldests map, they will only throw a symbol hash collision
316 : error if the target PC is different from the one already registered.
317 : We can omit this check because of the hash function's bijective
318 : property, since the key-value pairs are deterministically derived
319 : from one another.
320 :
321 : TODO: this function will have to be adapted to hash the target PC
322 : depending on the SBPF version (>= V3). That has not been implemented
323 : yet.
324 :
325 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/program.rs#L142-L178 */
326 : static int
327 : fd_sbpf_register_function_hashed_legacy( fd_sbpf_loader_t * loader,
328 : fd_sbpf_program_t * prog,
329 : uchar const * name,
330 : ulong name_len,
331 : ulong target_pc,
332 4743 : uint * opt_out_pc_hash ) {
333 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/program.rs#L156-L160 */
334 4743 : uint pc_hash;
335 4743 : uchar is_entrypoint = fd_sbpf_slice_cstr_eq( name, name_len, "entrypoint" ) ||
336 4743 : target_pc==FD_SBPF_ENTRYPOINT_PC;
337 4743 : if( FD_UNLIKELY( is_entrypoint ) ) {
338 78 : if( FD_UNLIKELY( prog->entry_pc!=ULONG_MAX && prog->entry_pc!=target_pc ) ) {
339 : /* We already registered the entrypoint to a different target PC,
340 : so we cannot register it again. */
341 0 : return FD_SBPF_ELF_ERR_SYMBOL_HASH_COLLISION;
342 0 : }
343 78 : prog->entry_pc = target_pc;
344 :
345 : /* Optimization for this constant value */
346 78 : pc_hash = FD_SBPF_ENTRYPOINT_HASH;
347 4665 : } else {
348 4665 : pc_hash = fd_pchash( (uint)target_pc );
349 4665 : }
350 :
351 : /* loader.get_function_registry() is their equivalent of our syscalls
352 : registry. Fail if the target PC is present there.
353 :
354 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/program.rs#L161-L163 */
355 4743 : if( FD_UNLIKELY( fd_sbpf_syscalls_query( loader->syscalls, pc_hash, NULL ) ) ) {
356 0 : return FD_SBPF_ELF_ERR_SYMBOL_HASH_COLLISION;
357 0 : }
358 :
359 : /* Insert the target PC into the calldests set if it's not the
360 : entrypoint. Due to the nature of our calldests, we also want to
361 : make sure that target_pc <= text_cnt, otherwise the insertion is
362 : UB. It's fine to skip inserting these entries because the calldests
363 : are write-only in the SBPF loader and only queried from the VM. */
364 4743 : if( FD_LIKELY( !is_entrypoint &&
365 4743 : loader->calldests &&
366 4743 : fd_sbpf_calldests_valid_idx( loader->calldests, target_pc ) ) ) {
367 4665 : fd_sbpf_calldests_insert( loader->calldests, target_pc );
368 4665 : }
369 :
370 4743 : if( opt_out_pc_hash ) *opt_out_pc_hash = pc_hash;
371 4743 : return FD_SBPF_ELF_SUCCESS;
372 4743 : }
373 :
374 : /* ELF Dynamic Relocations *********************************************
375 :
376 : ### Summary
377 :
378 : The sBPF ELF loader provides a limited dynamic relocation mechanism
379 : to fix up Clang-generated shared objects for execution in an sBPF VM.
380 :
381 : The relocation types themselves violate the eBPF and ELF specs in
382 : various ways. In short, the relocation table (via DT_REL) is used to
383 : shift program code from zero-based addressing to the MM_PROGRAM
384 : segment in the VM memory map (at 0x1_0000_0000).
385 :
386 : As part of the Solana VM protocol it abides by strict determinism
387 : requirements. This sadly means that we will have to replicate all
388 : edge cases and bugs in the Solana Labs ELF loader.
389 :
390 : Three relocation types are currently supported:
391 : - R_BPF_64_64: Sets an absolute address of a symbol as the
392 : 64-bit immediate field of an lddw instruction
393 : - R_BPF_64_RELATIVE: Adds MM_PROGRAM_START (0x1_0000_0000) to ...
394 : a) ... the 64-bit imm field of an lddw instruction (if in text)
395 : b) ... a 64-bit integer (if not in text section)
396 : - R_BPF_64_32: Sets the 32-bit immediate field of a call
397 : instruction to ...
398 : a) the ID of a local function (Murmur3 hash of function PC address)
399 : b) the ID of a syscall
400 :
401 : Obviously invalid relocations (e.g. out-of-bounds of ELF file or
402 : unsupported reloc type) raise an error.
403 : Relocations that would corrupt ELF data structures are silently
404 : ignored (using the fd_sbpf_reloc_mask mechanism).
405 :
406 : ### History
407 :
408 : The use of relocations is technically redundant, as the Solana VM
409 : memory map has been hardcoded in program runtime v1 (so far the only
410 : runtime). However, virtually all deployed programs as of April 2023
411 : are position-independent shared objects and make heavy use of such
412 : relocations.
413 :
414 : Relocations in the Solana VM have a complicated history. Over the
415 : course of years, multiple protocol bugs have been added and fixed.
416 : The ELF loader needs to handle all these edge cases to avoid breaking
417 : "userspace". I.e. any deployed programs which might be immutable
418 : must continue to function.
419 :
420 : While this complex logic will probably stick around for the next few
421 : years, the Solana protocol is getting increasingly restrictive for
422 : newly deployed ELFs. Another proposed change is upgrading to
423 : position-dependent binaries without any dynamic relocations. */
424 :
425 : /* R_BPF_64_64 relocates an absolute address into the extended imm field
426 : of an lddw-form instruction. (Two instruction slots, low 32 bits in
427 : first immediate field, high 32 bits in second immediate field)
428 :
429 : Bits 0..32 32..64 64..96 96..128
430 : [ ... ] [ IMM_LO ] [ ... ] [ IMM_HI ]
431 :
432 : Returns 0 on success and writes the imm offset to the rodata.
433 : Returns the error code on failure.
434 :
435 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1069-L1141 */
436 :
437 : static int
438 : fd_sbpf_r_bpf_64_64( fd_sbpf_elf_t const * elf,
439 : ulong elf_sz,
440 : uchar * rodata,
441 : fd_sbpf_elf_info_t const * info,
442 : fd_elf64_rel const * dt_rel,
443 6 : ulong r_offset ) {
444 :
445 6 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
446 :
447 : /* Note that the sbpf_version variable is ALWAYS V0 (see Agave's code
448 : to understand why).
449 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1070-L1080 */
450 6 : ulong imm_offset = fd_ulong_sat_add( r_offset, 4UL /* BYTE_OFFSET_IMMEDIATE */ );
451 :
452 : /* Bounds check.
453 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1084-L1086 */
454 6 : if( FD_UNLIKELY( fd_ulong_sat_add( imm_offset, 4UL /* BYTE_LENGTH_IMMEDIATE */ )>elf_sz ) ) {
455 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
456 0 : }
457 :
458 : /* Get the symbol entry from the dynamic symbol table.
459 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1089-L1092 */
460 6 : fd_elf64_sym const * symbol = NULL;
461 6 : {
462 : /* Ensure the dynamic symbol table exists. */
463 6 : if( FD_UNLIKELY( info->shndx_dynsymtab<0 ) ) {
464 0 : return FD_SBPF_ELF_ERR_UNKNOWN_SYMBOL;
465 0 : }
466 :
467 : /* Get the dynamic symbol table section header. The section header
468 : was already validated in fd_sbpf_lenient_elf_parse() so we can
469 : directly get the symbol table. */
470 6 : fd_elf64_shdr const * sh_dynsym = &shdrs[ info->shndx_dynsymtab ];
471 6 : fd_elf64_sym const * dynsym_table = (fd_elf64_sym const *)( elf->bin + sh_dynsym->sh_offset );
472 6 : ulong dynsym_cnt = (ulong)(sh_dynsym->sh_size / sizeof(fd_elf64_sym));
473 :
474 : /* The symbol table index is stored in the lower 4 bytes of r_info.
475 : Check the bounds of the symbol table index. */
476 6 : ulong r_sym = FD_ELF64_R_SYM( dt_rel->r_info );
477 6 : if( FD_UNLIKELY( r_sym>=dynsym_cnt ) ) {
478 0 : return FD_SBPF_ELF_ERR_UNKNOWN_SYMBOL;
479 0 : }
480 6 : symbol = &dynsym_table[ r_sym ];
481 6 : }
482 :
483 : /* Use the relative address as an offset to derive the relocated
484 : address.
485 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1094-L1096 */
486 6 : uint refd_addr = FD_LOAD( uint, &rodata[ imm_offset ] );
487 6 : ulong addr = fd_ulong_sat_add( symbol->st_value, refd_addr );
488 :
489 : /* We need to normalize the address into the VM's memory space, which
490 : is rooted at 0x1_0000_0000 (the program ro-data region). If the
491 : linker hasn't normalized the addresses already, we treat addr as
492 : a relative offset into the program ro-data region. */
493 6 : if( addr<FD_SBPF_MM_PROGRAM_ADDR ) {
494 6 : addr = fd_ulong_sat_add( addr, FD_SBPF_MM_PROGRAM_ADDR );
495 6 : }
496 :
497 : /* Again, no need to check the sbpf_version because it's always V0.
498 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1106-L1140 */
499 6 : ulong imm_low_offset = imm_offset;
500 6 : ulong imm_high_offset = fd_ulong_sat_add( imm_low_offset, 8UL /* INSN_SIZE */ );
501 :
502 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1116-L1122 */
503 6 : {
504 : /* Bounds check before writing to the rodata. */
505 6 : if( FD_UNLIKELY( fd_ulong_sat_add( imm_low_offset, 4UL /* BYTE_LENGTH_IMMEDIATE */ )>elf_sz ) ) {
506 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
507 0 : }
508 :
509 : /* Write back */
510 6 : FD_STORE( uint, rodata+imm_low_offset, (uint)addr );
511 6 : }
512 :
513 : /* Same as above, but for the imm high offset.
514 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1125-L1134 */
515 0 : {
516 : /* Bounds check before writing to the rodata. */
517 6 : if( FD_UNLIKELY( fd_ulong_sat_add( imm_high_offset, 4UL /* BYTE_LENGTH_IMMEDIATE */ )>elf_sz ) ) {
518 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
519 0 : }
520 :
521 : /* Write back */
522 6 : FD_STORE( uint, rodata+imm_high_offset, (uint)(addr>>32UL) );
523 6 : }
524 :
525 : /* ...rest of this function is a no-op because
526 : enable_symbol_and_section_labels is disabled in production. */
527 :
528 6 : return FD_SBPF_ELF_SUCCESS;
529 6 : }
530 :
531 : /* R_BPF_64_RELATIVE is almost entirely Solana specific. Returns 0 on
532 : success and an ElfError on failure.
533 :
534 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1142-L1247 */
535 :
536 : static int
537 : fd_sbpf_r_bpf_64_relative( fd_sbpf_elf_t const * elf,
538 : ulong elf_sz,
539 : uchar * rodata,
540 : fd_sbpf_elf_info_t const * info,
541 5442 : ulong r_offset ) {
542 :
543 5442 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
544 5442 : fd_elf64_shdr const * sh_text = &shdrs[ info->shndx_text ];
545 :
546 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1147-L1148 */
547 5442 : ulong imm_offset = fd_ulong_sat_add( r_offset, 4UL /* BYTE_OFFSET_IMMEDIATE */ );
548 :
549 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1150-L1246 */
550 5442 : fd_sbpf_range_t text_section_range;
551 5442 : if( fd_shdr_get_file_range( sh_text, &text_section_range ) &&
552 5442 : fd_sbpf_range_contains( &text_section_range, r_offset ) ) {
553 :
554 : /* We are relocating a lddw (load double word) instruction which
555 : spans two instruction slots. The address top be relocated is
556 : split in two halves in the two imms of the instruction slots.
557 :
558 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1159-L1162 */
559 3654 : ulong imm_low_offset = imm_offset;
560 3654 : ulong imm_high_offset = fd_ulong_sat_add( r_offset,
561 3654 : 4UL /* BYTE_OFFSET_IMMEDIATE */ + 8UL /* INSN_SIZE */ );
562 :
563 : /* Read the low side of the address. Perform a bounds check first.
564 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1164-L1171 */
565 3654 : if( FD_UNLIKELY( fd_ulong_sat_add( imm_low_offset, 4UL /* BYTE_LENGTH_IMMEDIATE */ )>elf_sz ) ) {
566 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
567 0 : }
568 3654 : uint va_low = FD_LOAD( uint, rodata+imm_low_offset );
569 :
570 : /* Read the high side of the address. Perform a bounds check first.
571 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1174-L1180 */
572 3654 : if( FD_UNLIKELY( fd_ulong_sat_add( imm_high_offset, 4UL /* BYTE_LENGTH_IMMEDIATE */ )>elf_sz ) ) {
573 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
574 0 : }
575 3654 : uint va_high = FD_LOAD( uint, rodata+imm_high_offset );
576 :
577 : /* Put the address back together.
578 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1182-L1187 */
579 3654 : ulong refd_addr = ( (ulong)va_high<<32UL ) | va_low;
580 3654 : if( FD_UNLIKELY( refd_addr==0UL ) ) {
581 0 : return FD_SBPF_ELF_ERR_INVALID_VIRTUAL_ADDRESS;
582 0 : }
583 :
584 : /* We need to normalize the address into the VM's memory space, which
585 : is rooted at 0x1_0000_0000 (the program ro-data region). If the
586 : linker hasn't normalized the addresses already, we treat addr as
587 : a relative offset into the program ro-data region.
588 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1189-L1193 */
589 3654 : if( refd_addr<FD_SBPF_MM_PROGRAM_ADDR ) {
590 3654 : refd_addr = fd_ulong_sat_add( refd_addr, FD_SBPF_MM_PROGRAM_ADDR );
591 3654 : }
592 :
593 : /* Write back the low half. Perform a bounds check first.
594 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1195-L1202 */
595 3654 : if( FD_UNLIKELY( fd_ulong_sat_add( imm_low_offset, 4UL /* BYTE_LENGTH_IMMEDIATE */ )>elf_sz ) ) {
596 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
597 0 : }
598 3654 : FD_STORE( uint, rodata+imm_low_offset, (uint)refd_addr );
599 :
600 : /* Write back the high half. Perform a bounds check first.
601 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1205-L1214 */
602 3654 : if( FD_UNLIKELY( fd_ulong_sat_add( imm_high_offset, 4UL /* BYTE_LENGTH_IMMEDIATE */ )>elf_sz ) ) {
603 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
604 0 : }
605 3654 : FD_STORE( uint, rodata+imm_high_offset, (uint)(refd_addr>>32UL) );
606 3654 : } else {
607 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1216-L1228 */
608 1788 : ulong refd_addr = 0UL;
609 :
610 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1230-L1239 */
611 1788 : if( FD_UNLIKELY( fd_ulong_sat_add( r_offset, 4UL /* BYTE_LENGTH_IMMEDIATE */ )>elf_sz ) ) {
612 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
613 0 : }
614 1788 : refd_addr = FD_LOAD( uint, rodata+imm_offset );
615 1788 : refd_addr = fd_ulong_sat_add( refd_addr, FD_SBPF_MM_PROGRAM_ADDR );
616 :
617 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1242-L1245 */
618 1788 : if( FD_UNLIKELY( fd_ulong_sat_add( r_offset, sizeof(ulong) )>elf_sz ) ) {
619 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
620 0 : }
621 :
622 1788 : FD_STORE( ulong, rodata+r_offset, refd_addr );
623 1788 : }
624 :
625 5442 : return FD_SBPF_ELF_SUCCESS;
626 5442 : }
627 :
628 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1248-L1301 */
629 : static int
630 : fd_sbpf_r_bpf_64_32( fd_sbpf_loader_t * loader,
631 : fd_sbpf_program_t * prog,
632 : fd_sbpf_elf_t const * elf,
633 : ulong elf_sz,
634 : uchar * rodata,
635 : fd_sbpf_elf_info_t const * info,
636 : fd_elf64_rel const * dt_rel,
637 : ulong r_offset,
638 1890 : fd_sbpf_loader_config_t const * config ) {
639 :
640 1890 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
641 1890 : fd_elf64_shdr const * sh_text = &shdrs[ info->shndx_text ];
642 1890 : fd_elf64_shdr const * dyn_section_names_shdr = &shdrs[ info->shndx_dynstr ];
643 :
644 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1253-L1254 */
645 1890 : ulong imm_offset = fd_ulong_sat_add( r_offset, 4UL /* BYTE_OFFSET_IMMEDIATE */ );
646 :
647 : /* Get the symbol entry from the dynamic symbol table.
648 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1256-L1259 */
649 1890 : fd_elf64_sym const * symbol = NULL;
650 :
651 : /* Ensure the dynamic symbol table exists. */
652 1890 : if( FD_UNLIKELY( info->shndx_dynsymtab<0 ) ) {
653 0 : return FD_SBPF_ELF_ERR_UNKNOWN_SYMBOL;
654 0 : }
655 :
656 : /* Get the dynamic symbol table section header. The section header
657 : was already validated in fd_sbpf_lenient_elf_parse() so we can
658 : directly get the symbol table. */
659 1890 : fd_elf64_shdr const * sh_dynsym = &shdrs[ info->shndx_dynsymtab ];
660 1890 : fd_elf64_sym const * dynsym_table = (fd_elf64_sym const *)( elf->bin + sh_dynsym->sh_offset );
661 1890 : ulong dynsym_cnt = (ulong)(sh_dynsym->sh_size / sizeof(fd_elf64_sym));
662 :
663 : /* The symbol table index is stored in the lower 4 bytes of r_info.
664 : Check the bounds of the symbol table index. */
665 1890 : ulong r_sym = FD_ELF64_R_SYM( dt_rel->r_info );
666 1890 : if( FD_UNLIKELY( r_sym>=dynsym_cnt ) ) {
667 0 : return FD_SBPF_ELF_ERR_UNKNOWN_SYMBOL;
668 0 : }
669 1890 : symbol = &dynsym_table[ r_sym ];
670 :
671 : /* Verify symbol name.
672 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1261-L1263 */
673 1890 : uchar const * name;
674 1890 : ulong name_len;
675 1890 : 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 ) ) ) {
676 0 : return FD_SBPF_ELF_ERR_UNKNOWN_SYMBOL;
677 0 : }
678 :
679 : /* If the symbol is defined, this is a bpf-to-bpf call.
680 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1265-L1295 */
681 1890 : uint key = 0U;
682 1890 : int symbol_is_function = ( FD_ELF64_ST_TYPE( symbol->st_info )==FD_ELF_STT_FUNC );
683 1890 : {
684 1890 : if( symbol_is_function && symbol->st_value!=0UL ) {
685 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1267-L1269 */
686 138 : fd_sbpf_range_t text_section_range = (fd_sbpf_range_t) {
687 138 : .lo = sh_text->sh_addr,
688 138 : .hi = fd_ulong_sat_add( sh_text->sh_addr, sh_text->sh_size ) };
689 138 : if( FD_UNLIKELY( !fd_sbpf_range_contains( &text_section_range, symbol->st_value ) ) ) {
690 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
691 0 : }
692 :
693 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1270-L1279 */
694 138 : ulong target_pc = fd_ulong_sat_sub( symbol->st_value, sh_text->sh_addr ) / 8UL;
695 138 : int err = fd_sbpf_register_function_hashed_legacy( loader, prog, name, name_len, target_pc, &key );
696 138 : if( FD_UNLIKELY( err!=FD_SBPF_ELF_SUCCESS ) ) {
697 0 : return err;
698 0 : }
699 1752 : } else {
700 : /* Else, it's a syscall. Ensure that the syscall can be resolved.
701 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1281-L1294 */
702 1752 : key = fd_murmur3_32(name, name_len, 0UL );
703 1752 : if( FD_UNLIKELY( config->reject_broken_elfs &&
704 1752 : fd_sbpf_syscalls_query( loader->syscalls, key, NULL )==NULL ) ) {
705 0 : return FD_SBPF_ELF_ERR_UNRESOLVED_SYMBOL;
706 0 : }
707 1752 : }
708 1890 : }
709 :
710 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1297-L1300 */
711 1890 : if( FD_UNLIKELY( fd_ulong_sat_add( imm_offset, 4UL /* BYTE_LENGTH_IMMEDIATE */ )>elf_sz ) ) {
712 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
713 0 : }
714 :
715 1890 : FD_STORE( uint, rodata+imm_offset, key );
716 :
717 1890 : return FD_SBPF_ELF_SUCCESS;
718 1890 : }
719 :
720 : static int
721 : fd_sbpf_elf_peek_strict( fd_sbpf_elf_info_t * info,
722 : void const * bin,
723 9 : ulong bin_sz ) {
724 :
725 : /* Parse file header */
726 :
727 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L425
728 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L278
729 : (Agave does some extra checks on alignment, but they don't seem necessary) */
730 9 : if( FD_UNLIKELY( bin_sz<sizeof(fd_elf64_ehdr) ) ) {
731 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
732 0 : }
733 :
734 9 : fd_elf64_ehdr ehdr = FD_LOAD( fd_elf64_ehdr, bin );
735 :
736 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L430-L453 */
737 9 : ulong program_header_table_end = fd_ulong_sat_add( sizeof(fd_elf64_ehdr), fd_ulong_sat_mul( ehdr.e_phnum, sizeof(fd_elf64_phdr) ) );
738 :
739 9 : int parse_ehdr_err =
740 9 : ( fd_uint_load_4( ehdr.e_ident ) != FD_ELF_MAG_LE )
741 9 : | ( ehdr.e_ident[ FD_ELF_EI_CLASS ] != FD_ELF_CLASS_64 )
742 9 : | ( ehdr.e_ident[ FD_ELF_EI_DATA ] != FD_ELF_DATA_LE )
743 9 : | ( ehdr.e_ident[ FD_ELF_EI_VERSION ] != 1 )
744 9 : | ( ehdr.e_ident[ FD_ELF_EI_OSABI ] != FD_ELF_OSABI_NONE )
745 9 : | ( fd_ulong_load_8( ehdr.e_ident+8 ) != 0UL )
746 9 : | ( ehdr.e_type != FD_ELF_ET_DYN )
747 9 : | ( ehdr.e_machine != FD_ELF_EM_SBPF )
748 9 : | ( ehdr.e_version != 1 )
749 : // | ( ehdr.e_entry )
750 9 : | ( ehdr.e_phoff != sizeof(fd_elf64_ehdr) )
751 : // | ( ehdr.e_shoff )
752 : // | ( ehdr.e_flags )
753 9 : | ( ehdr.e_ehsize != sizeof(fd_elf64_ehdr) )
754 9 : | ( ehdr.e_phentsize != sizeof(fd_elf64_phdr) )
755 9 : | ( ehdr.e_phnum < EXPECTED_PHDR_CNT ) /* SIMD-0189 says < instead of != */
756 9 : | ( program_header_table_end >= bin_sz )
757 9 : | ( ehdr.e_shentsize != sizeof(fd_elf64_shdr) )
758 : // | ( ehdr.e_shnum )
759 9 : | ( ehdr.e_shstrndx >= ehdr.e_shnum )
760 9 : ;
761 9 : if( FD_UNLIKELY( parse_ehdr_err ) ) {
762 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_FILE_HEADER;
763 0 : }
764 :
765 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L462 */
766 9 : if( FD_UNLIKELY( (program_header_table_end-sizeof(fd_elf64_ehdr))%sizeof(fd_elf64_phdr) ) ) {
767 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_SIZE;
768 0 : }
769 9 : if( FD_UNLIKELY( program_header_table_end>bin_sz ) ) {
770 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
771 0 : }
772 : /* This is always true ... */
773 : // if( FD_UNLIKELY( !fd_ulong_is_aligned( sizeof(fd_elf64_ehdr), 8UL ) ) ) {
774 : // return FD_SBPF_ELF_PARSER_ERR_INVALID_ALIGNMENT;
775 : // }
776 :
777 : /* Parse program headers (expecting 4 segments) */
778 :
779 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L455-L487
780 : Note: Agave iterates with a zip, i.e. it cuts the loop to 4, even
781 : though the number of phdrs is allowed to be higher. */
782 9 : ulong expected_p_vaddr[ EXPECTED_PHDR_CNT ] = { FD_SBPF_MM_BYTECODE_ADDR, FD_SBPF_MM_RODATA_ADDR, FD_SBPF_MM_STACK_ADDR, FD_SBPF_MM_HEAP_ADDR };
783 9 : uint expected_p_flags[ EXPECTED_PHDR_CNT ] = { FD_SBPF_PF_X, FD_SBPF_PF_R, FD_SBPF_PF_RW, FD_SBPF_PF_RW };
784 9 : fd_elf64_phdr phdr[ EXPECTED_PHDR_CNT ];
785 45 : for( uint i=0; i<EXPECTED_PHDR_CNT; i++ ) {
786 36 : ulong phdr_off = sizeof(fd_elf64_ehdr) + i*sizeof(fd_elf64_phdr);
787 36 : phdr[ i ] = FD_LOAD( fd_elf64_phdr, bin+phdr_off );
788 :
789 36 : ulong p_filesz = ( expected_p_flags[ i ] & FD_SBPF_PF_W ) ? 0UL : phdr[ i ].p_memsz;
790 36 : int parse_phdr_err =
791 36 : ( phdr[ i ].p_type != FD_ELF_PT_LOAD )
792 36 : | ( phdr[ i ].p_flags != expected_p_flags[ i ] )
793 36 : | ( phdr[ i ].p_offset < program_header_table_end )
794 36 : | ( phdr[ i ].p_offset >= bin_sz )
795 36 : | ( phdr[ i ].p_offset % 8UL != 0UL )
796 36 : | ( phdr[ i ].p_vaddr != expected_p_vaddr[ i ] )
797 36 : | ( phdr[ i ].p_paddr != expected_p_vaddr[ i ] )
798 36 : | ( phdr[ i ].p_filesz != p_filesz )
799 36 : | ( phdr[ i ].p_filesz > bin_sz - phdr[ i ].p_offset )
800 36 : | ( phdr[ i ].p_filesz % 8UL != 0UL )
801 36 : | ( phdr[ i ].p_memsz >= FD_SBPF_MM_REGION_SZ )
802 36 : ;
803 36 : if( FD_UNLIKELY( parse_phdr_err ) ) {
804 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_PROGRAM_HEADER;
805 0 : }
806 36 : }
807 :
808 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L489-L506 */
809 9 : ulong vm_range_start = phdr[ 0 ].p_vaddr;
810 9 : ulong vm_range_end = phdr[ 0 ].p_vaddr + phdr[ 0 ].p_memsz;
811 9 : ulong entry_chk = ehdr.e_entry + 7UL;
812 9 : int parse_e_entry_err =
813 9 : !( vm_range_start <= entry_chk && entry_chk < vm_range_end ) /* rust contains includes min, excludes max*/
814 9 : | ( ehdr.e_entry % 8UL != 0UL )
815 9 : ;
816 9 : if( FD_UNLIKELY( parse_e_entry_err ) ) {
817 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_FILE_HEADER;
818 0 : }
819 :
820 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L507-L515 */
821 9 : ulong entry_pc = ( ehdr.e_entry - phdr[ 0 ].p_vaddr ) / 8UL;
822 9 : ulong insn = fd_ulong_load_8( (uchar const *) bin + phdr[ 0 ].p_offset + entry_pc*8UL );
823 : /* Entrypoint must be a valid function start (ADD64_IMM with dst=r10)
824 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/ebpf.rs#L588 */
825 9 : if( FD_UNLIKELY( !fd_sbpf_is_function_start( fd_sbpf_instr( insn ) ) ) ) {
826 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_FILE_HEADER;
827 0 : }
828 :
829 : /* config.enable_symbol_and_section_labels is false in production,
830 : so there's nothing else to do.
831 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L519 */
832 :
833 9 : info->bin_sz = bin_sz;
834 9 : info->text_off = (uint)phdr[ 0 ].p_offset;
835 9 : info->text_sz = (uint)phdr[ 0 ].p_memsz;
836 9 : info->text_cnt = (uint)( phdr[ 0 ].p_memsz / 8UL );
837 :
838 9 : return FD_SBPF_ELF_SUCCESS;
839 9 : }
840 :
841 : static inline int
842 2817 : fd_sbpf_check_overlap( ulong a_start, ulong a_end, ulong b_start, ulong b_end ) {
843 2817 : return !( ( a_end <= b_start || b_end <= a_start ) );
844 2817 : }
845 :
846 : /* Mirrors Elf64::parse() in Agave. Returns an ElfParserError code on
847 : failure and 0 on success.
848 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L148 */
849 : int
850 : fd_sbpf_lenient_elf_parse( fd_sbpf_elf_info_t * info,
851 : void const * bin,
852 90 : ulong bin_sz ) {
853 :
854 : /* This documents the values that will be set in this function */
855 90 : info->bin_sz = bin_sz;
856 90 : info->phndx_dyn = -1;
857 90 : info->shndx_dyn = -1;
858 90 : info->shndx_symtab = -1;
859 90 : info->shndx_strtab = -1;
860 90 : info->shndx_dynstr = -1;
861 90 : info->shndx_dynsymtab = -1;
862 :
863 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L149 */
864 90 : if( FD_UNLIKELY( bin_sz<sizeof(fd_elf64_ehdr) ) ) {
865 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
866 0 : }
867 :
868 90 : fd_elf64_ehdr ehdr = FD_LOAD( fd_elf64_ehdr, bin );
869 90 : ulong ehdr_start = 0;
870 90 : ulong ehdr_end = sizeof(fd_elf64_ehdr);
871 :
872 : /* ELF header
873 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L151-L162 */
874 90 : int parse_ehdr_err =
875 90 : ( fd_uint_load_4( ehdr.e_ident ) != FD_ELF_MAG_LE )
876 90 : | ( ehdr.e_ident[ FD_ELF_EI_CLASS ] != FD_ELF_CLASS_64 )
877 90 : | ( ehdr.e_ident[ FD_ELF_EI_DATA ] != FD_ELF_DATA_LE )
878 90 : | ( ehdr.e_ident[ FD_ELF_EI_VERSION ] != 1 )
879 90 : | ( ehdr.e_version != 1 )
880 90 : | ( ehdr.e_ehsize != sizeof(fd_elf64_ehdr) )
881 90 : | ( ehdr.e_phentsize != sizeof(fd_elf64_phdr) )
882 90 : | ( ehdr.e_shentsize != sizeof(fd_elf64_shdr) )
883 90 : | ( ehdr.e_shstrndx >= ehdr.e_shnum )
884 90 : ;
885 90 : if( FD_UNLIKELY( parse_ehdr_err ) ) {
886 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_FILE_HEADER;
887 0 : }
888 :
889 : /* Program headers
890 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L164-L165 */
891 90 : ulong phdr_start = ehdr.e_phoff;
892 90 : ulong phdr_end, phdr_sz;
893 : /* Elf64::parse_program_header_table() */
894 90 : {
895 90 : if( FD_UNLIKELY( __builtin_umull_overflow( ehdr.e_phnum, sizeof(fd_elf64_phdr), &phdr_sz ) ) ) {
896 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
897 0 : }
898 :
899 90 : if( FD_UNLIKELY( __builtin_uaddl_overflow( ehdr.e_phoff, phdr_sz, &phdr_end ) ) ) {
900 : /* ArithmeticOverflow -> ElfParserError::OutOfBounds
901 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L671-L675 */
902 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
903 0 : }
904 :
905 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L301 */
906 90 : if( FD_UNLIKELY( fd_sbpf_check_overlap( ehdr_start, ehdr_end, phdr_start, phdr_end ) ) ) {
907 0 : return FD_SBPF_ELF_PARSER_ERR_OVERLAP;
908 0 : }
909 :
910 : /* Ensure program header table range lies within the file, like
911 : slice_from_bytes. Unfortunately the checks have to be split up
912 : because Agave throws different error codes depending on which
913 : condition fails...
914 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L302-L303 */
915 90 : if( FD_UNLIKELY( phdr_sz%sizeof(fd_elf64_phdr)!=0UL ) ) {
916 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_SIZE;
917 0 : }
918 :
919 90 : if( FD_UNLIKELY( phdr_end>bin_sz ) ) {
920 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
921 0 : }
922 :
923 90 : if( FD_UNLIKELY( !fd_ulong_is_aligned( phdr_start, 8UL ) ) ) {
924 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_ALIGNMENT;
925 0 : }
926 90 : }
927 :
928 : /* Section headers
929 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L167-L172 */
930 :
931 90 : ulong shdr_start = ehdr.e_shoff;
932 90 : ulong shdr_end, shdr_sz;
933 : /* Elf64::parse_section_header_table() */
934 90 : {
935 90 : if( FD_UNLIKELY( __builtin_umull_overflow( ehdr.e_shnum, sizeof(fd_elf64_shdr), &shdr_sz ) ) ) {
936 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
937 0 : }
938 :
939 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L314-L317 */
940 90 : if( FD_UNLIKELY( __builtin_uaddl_overflow( ehdr.e_shoff, shdr_sz, &shdr_end ) ) ) {
941 : /* ArithmeticOverflow -> ElfParserError::OutOfBounds
942 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L671-L675 */
943 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
944 0 : }
945 :
946 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L318 */
947 90 : if( FD_UNLIKELY( fd_sbpf_check_overlap( ehdr_start, ehdr_end, shdr_start, shdr_end ) ) ) {
948 0 : return FD_SBPF_ELF_PARSER_ERR_OVERLAP;
949 0 : }
950 :
951 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L319 */
952 90 : if( FD_UNLIKELY( fd_sbpf_check_overlap( phdr_start, phdr_end, shdr_start, shdr_end ) ) ) {
953 0 : return FD_SBPF_ELF_PARSER_ERR_OVERLAP;
954 0 : }
955 :
956 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L321 */
957 90 : if( FD_UNLIKELY( (shdr_end-ehdr.e_shoff)%sizeof(fd_elf64_shdr) ) ) {
958 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_SIZE;
959 0 : }
960 :
961 : /* Ensure section header table range lies within the file, like slice_from_bytes */
962 90 : if( FD_UNLIKELY( shdr_end > bin_sz ) ) {
963 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
964 0 : }
965 :
966 90 : if( FD_UNLIKELY( !fd_ulong_is_aligned( ehdr.e_shoff, 8UL ) ) ) {
967 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_ALIGNMENT;
968 0 : }
969 90 : }
970 :
971 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L174-L177 */
972 90 : fd_elf64_shdr shdr = FD_LOAD( fd_elf64_shdr, bin + ehdr.e_shoff );
973 90 : if( FD_UNLIKELY( shdr.sh_type != FD_ELF_SHT_NULL ) ) {
974 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_SECTION_HEADER;
975 0 : }
976 :
977 : /* Parse each program header
978 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L179-L196 */
979 90 : ulong vaddr = 0UL;
980 414 : for( ulong i=0; i<ehdr.e_phnum; i++ ) {
981 324 : fd_elf64_phdr phdr = FD_LOAD( fd_elf64_phdr, bin + phdr_start + i*sizeof(fd_elf64_phdr) );
982 324 : if( FD_UNLIKELY( phdr.p_type != FD_ELF_PT_LOAD ) ) {
983 : /* Remember first PT_DYNAMIC program header for dynamic parsing */
984 84 : if( phdr.p_type==FD_ELF_PT_DYNAMIC && info->phndx_dyn == -1 ) {
985 84 : info->phndx_dyn = (int)i;
986 84 : }
987 84 : continue;
988 84 : }
989 240 : if( FD_UNLIKELY( phdr.p_vaddr<vaddr ) ) {
990 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_PROGRAM_HEADER;
991 0 : }
992 240 : ulong _offset_plus_size;
993 240 : if( FD_UNLIKELY( __builtin_uaddl_overflow( phdr.p_offset, phdr.p_filesz, &_offset_plus_size ) ) ) {
994 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
995 0 : }
996 240 : if( FD_UNLIKELY( phdr.p_offset + phdr.p_filesz > bin_sz ) ) {
997 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
998 0 : }
999 240 : vaddr = phdr.p_vaddr;
1000 240 : }
1001 :
1002 : /* Parse each section header
1003 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L198-L216 */
1004 90 : ulong offset = 0UL;
1005 948 : for( ulong i=0; i<ehdr.e_shnum; i++ ) {
1006 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L200-L205 */
1007 858 : fd_elf64_shdr shdr = FD_LOAD( fd_elf64_shdr, bin + shdr_start + i*sizeof(fd_elf64_shdr) );
1008 858 : if( FD_UNLIKELY( shdr.sh_type==FD_ELF_SHT_NOBITS ) ) {
1009 9 : continue;
1010 9 : }
1011 :
1012 : /* Remember first SHT_DYNAMIC section header for dynamic parsing */
1013 849 : if( shdr.sh_type==FD_ELF_SHT_DYNAMIC && info->shndx_dyn == -1 ) {
1014 84 : info->shndx_dyn = (int)i;
1015 84 : }
1016 :
1017 849 : ulong sh_start = shdr.sh_offset;
1018 849 : ulong sh_end;
1019 849 : if( FD_UNLIKELY( __builtin_uaddl_overflow( shdr.sh_offset, shdr.sh_size, &sh_end ) ) ) {
1020 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
1021 0 : }
1022 :
1023 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L206-L208 */
1024 849 : if( FD_UNLIKELY( fd_sbpf_check_overlap( sh_start, sh_end, ehdr_start, ehdr_end ) ) ) {
1025 0 : return FD_SBPF_ELF_PARSER_ERR_OVERLAP;
1026 0 : }
1027 849 : if( FD_UNLIKELY( fd_sbpf_check_overlap( sh_start, sh_end, phdr_start, phdr_end ) ) ) {
1028 0 : return FD_SBPF_ELF_PARSER_ERR_OVERLAP;
1029 0 : }
1030 849 : if( FD_UNLIKELY( fd_sbpf_check_overlap( sh_start, sh_end, shdr_start, shdr_end ) ) ) {
1031 0 : return FD_SBPF_ELF_PARSER_ERR_OVERLAP;
1032 0 : }
1033 :
1034 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L209-L215 */
1035 849 : if( FD_UNLIKELY( sh_start < offset ) ) {
1036 0 : return FD_SBPF_ELF_PARSER_ERR_SECTION_NOT_IN_ORDER;
1037 0 : }
1038 849 : offset = sh_end;
1039 849 : if( FD_UNLIKELY( sh_end > bin_sz ) ) {
1040 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
1041 0 : }
1042 849 : }
1043 :
1044 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L218-L224
1045 : section_header_table.get() returning ok is equivalent to ehdr.e_shstrndx < ehdr.e_shnum,
1046 : and this is already checked above. So, nothing to do here. */
1047 :
1048 : /* Parse sections
1049 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L240 */
1050 90 : {
1051 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L340-L342 */
1052 90 : if( FD_UNLIKELY( ehdr.e_shstrndx == 0 ) ) {
1053 0 : return FD_SBPF_ELF_PARSER_ERR_NO_SECTION_NAME_STRING_TABLE;
1054 0 : }
1055 :
1056 : /* Use section name string table to identify well-known sections */
1057 90 : ulong section_names_shdr_idx = ehdr.e_shstrndx;
1058 90 : fd_elf64_shdr section_names_shdr = FD_LOAD( fd_elf64_shdr, bin + shdr_start + section_names_shdr_idx*sizeof(fd_elf64_shdr) );
1059 : /* Agave repeats the following validation all the times, we can do it once here
1060 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L474-L476 */
1061 90 : if( FD_UNLIKELY( section_names_shdr.sh_type != FD_ELF_SHT_STRTAB ) ) {
1062 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_SECTION_HEADER;
1063 0 : }
1064 :
1065 : /* Iterate sections and record indices for .text, .symtab, .strtab, .dyn, .dynstr */
1066 924 : for( ulong i=0; i<ehdr.e_shnum; i++ ) {
1067 : /* Again... */
1068 837 : fd_elf64_shdr shdr = FD_LOAD( fd_elf64_shdr, bin + shdr_start + i*sizeof(fd_elf64_shdr) );
1069 :
1070 837 : uchar const * name;
1071 837 : ulong name_len;
1072 837 : 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 );
1073 837 : if( FD_UNLIKELY( res < 0 ) ) {
1074 3 : return res;
1075 3 : }
1076 :
1077 : /* Store the first section by name:
1078 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L350-L355
1079 : The rust code expands in:
1080 : match section_name {
1081 : b".symtab" => {
1082 : if self.symbol_section_header.is_some() {
1083 : return Err(ElfParserError::InvalidSectionHeader);
1084 : }
1085 : self.symbol_section_header = Some(section_header);
1086 : }
1087 : ...
1088 : _ => {}
1089 : }
1090 : Note that the number of bytes compared should not include the
1091 : null-terminator.
1092 : */
1093 834 : if( fd_sbpf_slice_cstr_eq( name, name_len, ".symtab" ) ) {
1094 36 : if( FD_UNLIKELY( info->shndx_symtab != -1 ) ) {
1095 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_SECTION_HEADER;
1096 0 : }
1097 36 : info->shndx_symtab = (int)i;
1098 798 : } else if( fd_sbpf_slice_cstr_eq( name, name_len, ".strtab" ) ) {
1099 36 : if( FD_UNLIKELY( info->shndx_strtab != -1 ) ) {
1100 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_SECTION_HEADER;
1101 0 : }
1102 36 : info->shndx_strtab = (int)i;
1103 762 : } else if( fd_sbpf_slice_cstr_eq( name, name_len, ".dynstr" ) ) {
1104 81 : if( FD_UNLIKELY( info->shndx_dynstr != -1 ) ) {
1105 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_SECTION_HEADER;
1106 0 : }
1107 81 : info->shndx_dynstr = (int)i;
1108 81 : }
1109 834 : }
1110 90 : }
1111 :
1112 : /* Parse dynamic
1113 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L241 */
1114 87 : {
1115 : /* Try PT_DYNAMIC first; if invalid or absent, fall back to SHT_DYNAMIC.
1116 : Note that only the first PT_DYNAMIC and SHT_DYNAMIC are used because of Rust iter().find().
1117 : Mirrors Rust logic:
1118 : - Try PT_DYNAMIC: https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L364-L372
1119 : - 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
1120 : If neither exists, return OK (static file). If SHT_DYNAMIC exists but is invalid, error. */
1121 :
1122 87 : ulong dynamic_table_start = ULONG_MAX;
1123 87 : ulong dynamic_table_end = ULONG_MAX;
1124 :
1125 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L364-L372 */
1126 87 : if( info->phndx_dyn >= 0 ) {
1127 81 : fd_elf64_phdr dyn_ph = FD_LOAD( fd_elf64_phdr, bin + phdr_start + (ulong)info->phndx_dyn*sizeof(fd_elf64_phdr) );
1128 81 : dynamic_table_start = dyn_ph.p_offset;
1129 81 : dynamic_table_end = dyn_ph.p_offset + dyn_ph.p_filesz;
1130 :
1131 : /* slice_from_program_header also checks that the size of the
1132 : slice is a multiple of the type size and that the alignment is
1133 : correct. */
1134 81 : if( FD_UNLIKELY( dynamic_table_end<dynamic_table_start ||
1135 81 : dynamic_table_end>bin_sz ||
1136 81 : dyn_ph.p_filesz%sizeof(fd_elf64_dyn)!=0UL ||
1137 81 : !fd_ulong_is_aligned( dynamic_table_start, 8UL ) ) ) {
1138 : /* skip - try SHT_DYNAMIC instead */
1139 0 : dynamic_table_start = ULONG_MAX;
1140 0 : dynamic_table_end = ULONG_MAX;
1141 0 : }
1142 81 : }
1143 :
1144 : /* If PT_DYNAMIC did not validate, try SHT_DYNAMIC
1145 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L376-L387 */
1146 87 : if( dynamic_table_start==ULONG_MAX && info->shndx_dyn >= 0 ) {
1147 0 : fd_elf64_shdr dyn_sh = FD_LOAD( fd_elf64_shdr, bin + shdr_start + (ulong)info->shndx_dyn*sizeof(fd_elf64_shdr) );
1148 0 : dynamic_table_start = dyn_sh.sh_offset;
1149 0 : if( FD_UNLIKELY( ( __builtin_uaddl_overflow( dyn_sh.sh_offset, dyn_sh.sh_size, &dynamic_table_end ) ) || /* checked_add */
1150 0 : ( dyn_sh.sh_size % sizeof(fd_elf64_dyn) != 0UL ) || /* slice_from_bytes InvalidSize */
1151 0 : ( dynamic_table_end > bin_sz ) || /* slice_from_bytes OutOfBounds */
1152 0 : !fd_ulong_is_aligned( dynamic_table_start, 8UL ) /* slice_from_bytes InvalidAlignment */ ) ) {
1153 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L382-L385 */
1154 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_DYNAMIC_SECTION_TABLE;
1155 0 : }
1156 0 : }
1157 :
1158 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L393 */
1159 87 : if( dynamic_table_start==ULONG_MAX ) {
1160 6 : return FD_SBPF_ELF_SUCCESS;
1161 6 : }
1162 :
1163 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L396-L407 */
1164 81 : ulong dynamic_table[ FD_ELF_DT_NUM ] = { 0UL };
1165 81 : ulong dyn_cnt = (dynamic_table_end - dynamic_table_start) / (ulong)sizeof(fd_elf64_dyn);
1166 846 : for( ulong i = 0UL; i<dyn_cnt; i++ ) {
1167 846 : fd_elf64_dyn dyn = FD_LOAD( fd_elf64_dyn, bin + dynamic_table_start + i*sizeof(fd_elf64_dyn) );
1168 :
1169 846 : if( FD_UNLIKELY( dyn.d_tag==FD_ELF_DT_NULL ) ) {
1170 81 : break;
1171 81 : }
1172 765 : if( FD_UNLIKELY( dyn.d_tag>=FD_ELF_DT_NUM ) ) {
1173 72 : continue;
1174 72 : }
1175 :
1176 693 : dynamic_table[ dyn.d_tag ] = dyn.d_un.d_val;
1177 693 : }
1178 :
1179 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L409 */
1180 81 : do {
1181 81 : ulong vaddr = dynamic_table[ FD_ELF_DT_REL ];
1182 81 : if( FD_UNLIKELY( vaddr==0UL ) ) {
1183 15 : break; /* from this do-while */
1184 15 : }
1185 :
1186 66 : if ( FD_UNLIKELY( dynamic_table[ FD_ELF_DT_RELENT ] != sizeof(fd_elf64_rel) ) ) {
1187 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_DYNAMIC_SECTION_TABLE;
1188 0 : }
1189 :
1190 66 : ulong size = dynamic_table[ FD_ELF_DT_RELSZ ];
1191 66 : if( FD_UNLIKELY( size==0UL ) ) {
1192 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_DYNAMIC_SECTION_TABLE;
1193 0 : }
1194 :
1195 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L430-L444 */
1196 66 : ulong offset = ULONG_MAX;
1197 189 : for( ulong i=0; i<ehdr.e_phnum; i++ ) {
1198 : /* Again... */
1199 189 : fd_elf64_phdr phdr = FD_LOAD( fd_elf64_phdr, bin + phdr_start + i*sizeof(fd_elf64_phdr) );
1200 189 : if( FD_UNLIKELY( phdr.p_vaddr+phdr.p_memsz<phdr.p_vaddr ) ) {
1201 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
1202 0 : }
1203 189 : if( phdr.p_vaddr<=vaddr && vaddr<phdr.p_vaddr+phdr.p_memsz ) {
1204 : /* vaddr - phdr.p_vaddr is guaranteed to be non-negative */
1205 66 : offset = vaddr-phdr.p_vaddr+phdr.p_offset;
1206 66 : if( FD_UNLIKELY( offset<phdr.p_offset ) ) {
1207 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
1208 0 : }
1209 66 : break;
1210 66 : }
1211 189 : }
1212 66 : if( FD_UNLIKELY( offset==ULONG_MAX ) ) {
1213 0 : for( ulong i=0; i<ehdr.e_shnum; i++ ) {
1214 : /* Again... */
1215 0 : fd_elf64_shdr shdr = FD_LOAD( fd_elf64_shdr, bin + shdr_start + i*sizeof(fd_elf64_shdr) );
1216 0 : if( shdr.sh_addr == vaddr ) {
1217 0 : offset = shdr.sh_offset;
1218 0 : break;
1219 0 : }
1220 0 : }
1221 0 : }
1222 66 : if( FD_UNLIKELY( offset==ULONG_MAX ) ) {
1223 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_DYNAMIC_SECTION_TABLE;
1224 0 : }
1225 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L446-L448 */
1226 66 : ulong _offset_plus_size;
1227 66 : if( FD_UNLIKELY( __builtin_uaddl_overflow( offset, size, &_offset_plus_size ) ) ) {
1228 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
1229 0 : }
1230 :
1231 : /* slice_from_bytes checks that size is a multiple of the type
1232 : size and that the alignment of the bytes + offset is correct. */
1233 66 : if( FD_UNLIKELY( ( size%sizeof(fd_elf64_rel)!=0UL ) ||
1234 66 : ( offset+size>bin_sz ) ||
1235 66 : ( !fd_ulong_is_aligned( offset, 8UL ) ) ) ) {
1236 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_DYNAMIC_SECTION_TABLE;
1237 0 : }
1238 :
1239 : /* Save the dynamic relocation table info */
1240 66 : info->dt_rel_off = (uint)offset;
1241 66 : info->dt_rel_sz = (uint)size;
1242 66 : } while( 0 ); /* so we can break out */
1243 :
1244 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L410 */
1245 81 : do {
1246 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L452-L455 */
1247 81 : ulong vaddr = dynamic_table[ FD_ELF_DT_SYMTAB ];
1248 81 : if( FD_UNLIKELY( vaddr==0UL ) ) {
1249 0 : break; /* from this do-while */
1250 0 : }
1251 :
1252 81 : fd_elf64_shdr shdr_sym = { 0 };
1253 426 : for( ulong i=0; i<ehdr.e_shnum; i++ ) {
1254 : /* Again... */
1255 426 : shdr_sym = FD_LOAD( fd_elf64_shdr, bin + shdr_start + i*sizeof(fd_elf64_shdr) );
1256 426 : if( shdr_sym.sh_addr == vaddr ) {
1257 81 : info->shndx_dynsymtab = (int)i;
1258 81 : break;
1259 81 : }
1260 426 : }
1261 :
1262 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L457-L461 */
1263 81 : if( FD_UNLIKELY( info->shndx_dynsymtab==-1 ) ) {
1264 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_DYNAMIC_SECTION_TABLE;
1265 0 : }
1266 :
1267 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L463-L464 */
1268 81 : {
1269 81 : if( FD_UNLIKELY( shdr_sym.sh_type != FD_ELF_SHT_SYMTAB && shdr_sym.sh_type != FD_ELF_SHT_DYNSYM ) ) {
1270 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_SECTION_HEADER;
1271 0 : }
1272 81 : ulong shdr_sym_start = shdr_sym.sh_offset;
1273 81 : ulong shdr_sym_end;
1274 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L574
1275 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L671 */
1276 81 : if( FD_UNLIKELY( __builtin_uaddl_overflow( shdr_sym.sh_offset, shdr_sym.sh_size, &shdr_sym_end ) ) ) {
1277 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
1278 0 : }
1279 : /* slice_from_bytes InvalidSize */
1280 81 : if( FD_UNLIKELY( shdr_sym.sh_size%sizeof(fd_elf64_sym) ) ) {
1281 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_SIZE;
1282 0 : }
1283 : /* slice_from_bytes OutOfBounds */
1284 81 : if( FD_UNLIKELY( shdr_sym_end>bin_sz ) ) {
1285 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
1286 0 : }
1287 : /* slice_from_bytes InvalidAlignment */
1288 81 : if( FD_UNLIKELY( !fd_ulong_is_aligned( shdr_sym_start, 8UL ) ) ) {
1289 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_ALIGNMENT;
1290 0 : }
1291 81 : }
1292 81 : } while( 0 ); /* so we can break out */
1293 81 : }
1294 :
1295 81 : return FD_SBPF_ELF_SUCCESS;
1296 81 : }
1297 :
1298 : /* Performs validation checks on the ELF. Returns an ElfError on failure
1299 : and 0 on success.
1300 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L719-L809 */
1301 : static int
1302 : fd_sbpf_lenient_elf_validate( fd_sbpf_elf_info_t * info,
1303 : void const * bin,
1304 : ulong bin_sz,
1305 87 : fd_elf64_shdr * text_shdr ) {
1306 :
1307 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L721-L736 */
1308 87 : fd_elf64_ehdr ehdr = FD_LOAD( fd_elf64_ehdr, bin );
1309 87 : if( FD_UNLIKELY( ehdr.e_ident[ FD_ELF_EI_CLASS ] != FD_ELF_CLASS_64 ) ) {
1310 0 : return FD_SBPF_ELF_ERR_WRONG_CLASS;
1311 0 : }
1312 87 : if( FD_UNLIKELY( ehdr.e_ident[ FD_ELF_EI_DATA ] != FD_ELF_DATA_LE ) ) {
1313 0 : return FD_SBPF_ELF_ERR_WRONG_ENDIANNESS;
1314 0 : }
1315 87 : if( FD_UNLIKELY( ehdr.e_ident[ FD_ELF_EI_OSABI ] != FD_ELF_OSABI_NONE ) ) {
1316 0 : return FD_SBPF_ELF_ERR_WRONG_ABI;
1317 0 : }
1318 87 : if( FD_UNLIKELY( ehdr.e_machine != FD_ELF_EM_BPF && ehdr.e_machine != FD_ELF_EM_SBPF ) ) {
1319 0 : return FD_SBPF_ELF_ERR_WRONG_MACHINE;
1320 0 : }
1321 87 : if( FD_UNLIKELY( ehdr.e_type != FD_ELF_ET_DYN ) ) {
1322 0 : return FD_SBPF_ELF_ERR_WRONG_TYPE;
1323 0 : }
1324 :
1325 : /* This code doesn't do anything:
1326 : 1. version is already checked at the very beginning of elf_peek
1327 : 2. the if condition is never true because sbpf_version is always v0
1328 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L738-L763 */
1329 :
1330 87 : ulong shdr_start = ehdr.e_shoff;
1331 87 : ulong section_names_shdr_idx = ehdr.e_shstrndx;
1332 87 : fd_elf64_shdr section_names_shdr = FD_LOAD( fd_elf64_shdr, bin + shdr_start + section_names_shdr_idx*sizeof(fd_elf64_shdr) );
1333 :
1334 : /* We do a single iteration over the section header table, collect all info
1335 : we need and return the errors later to match Agave. */
1336 :
1337 87 : int shndx_text = -1;
1338 87 : int writeable_err = 0;
1339 87 : int oob_err = 0;
1340 906 : for( ulong i=0UL; i<ehdr.e_shnum; i++ ) {
1341 : /* Again... */
1342 819 : fd_elf64_shdr shdr = FD_LOAD( fd_elf64_shdr, bin + ehdr.e_shoff + i*sizeof(fd_elf64_shdr) );
1343 :
1344 819 : uchar const * name;
1345 819 : ulong name_len;
1346 819 : 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 );
1347 819 : if( FD_UNLIKELY( res ) ) {
1348 : /* this can never fail because it was checked above, but safer to keep it */
1349 0 : return fd_sbpf_elf_parser_err_to_elf_err( res );
1350 0 : }
1351 :
1352 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L765-L775 */
1353 819 : if( FD_UNLIKELY( fd_sbpf_slice_cstr_eq( name, name_len, ".text" ) ) ) {
1354 87 : if( FD_LIKELY( shndx_text==-1 ) ) {
1355 87 : *text_shdr = shdr; /* Store the text section header */
1356 87 : shndx_text = (int)i;
1357 87 : } else {
1358 0 : return FD_SBPF_ELF_ERR_NOT_ONE_TEXT_SECTION;
1359 0 : }
1360 87 : }
1361 :
1362 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L780-L791 */
1363 819 : if( FD_UNLIKELY( fd_sbpf_slice_cstr_start_with( name, name_len, ".bss" ) ||
1364 819 : ( ( ( shdr.sh_flags & (FD_ELF_SHF_ALLOC | FD_ELF_SHF_WRITE) ) == (FD_ELF_SHF_ALLOC | FD_ELF_SHF_WRITE) ) &&
1365 819 : fd_sbpf_slice_cstr_start_with( name, name_len, ".data" ) &&
1366 819 : !fd_sbpf_slice_cstr_start_with( name, name_len, ".data.rel" ) ) ) ) {
1367 : /* to match Agave return error we can't fail here */
1368 6 : writeable_err = 1;
1369 6 : }
1370 :
1371 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L793-L802 */
1372 819 : ulong shdr_end;
1373 819 : if( FD_UNLIKELY( __builtin_uaddl_overflow( shdr.sh_offset, shdr.sh_size, &shdr_end ) ||
1374 819 : shdr_end>bin_sz ) ) {
1375 0 : oob_err = 1;
1376 0 : }
1377 819 : }
1378 :
1379 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L776-L778 */
1380 87 : if( FD_UNLIKELY( shndx_text==-1 ) ) {
1381 0 : return FD_SBPF_ELF_ERR_NOT_ONE_TEXT_SECTION;
1382 0 : }
1383 :
1384 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L786-L788 */
1385 87 : if( FD_UNLIKELY( writeable_err ) ) {
1386 3 : return FD_SBPF_ELF_ERR_WRITABLE_SECTION_NOT_SUPPORTED;
1387 3 : }
1388 :
1389 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L798 */
1390 84 : if( FD_UNLIKELY( oob_err ) ) {
1391 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1392 0 : }
1393 :
1394 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L804-L806 */
1395 84 : if( FD_UNLIKELY( !(
1396 84 : text_shdr->sh_addr <= ehdr.e_entry && ehdr.e_entry < fd_ulong_sat_add( text_shdr->sh_addr, text_shdr->sh_size )
1397 84 : ) ) ) {
1398 0 : return FD_SBPF_ELF_ERR_ENTRYPOINT_OUT_OF_BOUNDS;
1399 0 : }
1400 :
1401 : /* Get text section file ranges to calculate the size. */
1402 84 : fd_sbpf_range_t text_section_range;
1403 84 : fd_shdr_get_file_range( text_shdr, &text_section_range );
1404 :
1405 84 : info->text_off = (uint)text_shdr->sh_addr;
1406 84 : info->text_sz = text_section_range.hi-text_section_range.lo;
1407 84 : info->text_cnt = (uint)( info->text_sz/8UL );
1408 84 : info->shndx_text = shndx_text;
1409 :
1410 84 : return FD_SBPF_ELF_SUCCESS;
1411 84 : }
1412 :
1413 : /* First part of Agave's load_with_lenient_parser(). We split up this
1414 : function into two parts so we know how much memory we need to
1415 : allocate for the loading step. Returns an ElfError on failure and 0
1416 : on success.
1417 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L593-L638 */
1418 : static int
1419 : fd_sbpf_elf_peek_lenient( fd_sbpf_elf_info_t * info,
1420 : void const * bin,
1421 : ulong bin_sz,
1422 90 : fd_sbpf_loader_config_t const * config ) {
1423 :
1424 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L607 */
1425 90 : int res = fd_sbpf_lenient_elf_parse( info, bin, bin_sz );
1426 90 : if( FD_UNLIKELY( res<0 ) ) {
1427 3 : return fd_sbpf_elf_parser_err_to_elf_err( res );
1428 3 : }
1429 :
1430 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L617 */
1431 87 : fd_elf64_shdr text_shdr = { 0 };
1432 87 : res = fd_sbpf_lenient_elf_validate( info, bin, bin_sz, &text_shdr );
1433 87 : if( FD_UNLIKELY( res<0 ) ) {
1434 3 : return res;
1435 3 : }
1436 :
1437 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L620-L638 */
1438 84 : {
1439 84 : ulong text_section_vaddr = fd_ulong_sat_add( text_shdr.sh_addr, FD_SBPF_MM_RODATA_ADDR );
1440 84 : ulong vaddr_end = text_section_vaddr;
1441 :
1442 : /* Validate bounds and text section addrs / offsets.
1443 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L632-L638 */
1444 84 : if( FD_UNLIKELY( ( config->reject_broken_elfs && text_shdr.sh_addr!=text_shdr.sh_offset ) ||
1445 84 : vaddr_end>FD_SBPF_MM_STACK_ADDR ) ) {
1446 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1447 0 : }
1448 84 : }
1449 :
1450 : /* Peek (vs load) stops here
1451 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L638 */
1452 :
1453 84 : return FD_SBPF_ELF_SUCCESS;
1454 84 : }
1455 :
1456 : static int
1457 : fd_sbpf_program_get_sbpf_version_or_err( void const * bin,
1458 : ulong bin_sz,
1459 141 : fd_sbpf_loader_config_t const * config ) {
1460 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L376-L381 */
1461 141 : const ulong E_FLAGS_OFFSET = 48UL;
1462 :
1463 141 : if( FD_UNLIKELY( bin_sz<E_FLAGS_OFFSET+sizeof(uint) ) ) {
1464 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1465 0 : }
1466 141 : uint e_flags = FD_LOAD( uint, bin+E_FLAGS_OFFSET );
1467 :
1468 141 : uint sbpf_version = 0U;
1469 141 : if( FD_UNLIKELY( config->sbpf_max_version==FD_SBPF_V0 ) ) {
1470 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L384-L388 */
1471 12 : sbpf_version = e_flags==E_FLAGS_SBPF_V2 ? FD_SBPF_RESERVED : FD_SBPF_V0;
1472 129 : } else {
1473 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L390-L396 */
1474 129 : sbpf_version = e_flags < FD_SBPF_VERSION_COUNT ? e_flags : FD_SBPF_RESERVED;
1475 129 : }
1476 :
1477 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L399-L401 */
1478 141 : if( FD_UNLIKELY( !( config->sbpf_min_version <= sbpf_version && sbpf_version <= config->sbpf_max_version ) ) ) {
1479 42 : return FD_SBPF_ELF_ERR_UNSUPPORTED_SBPF_VERSION;
1480 42 : }
1481 :
1482 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L403-L407 */
1483 99 : return (int)sbpf_version;
1484 141 : }
1485 :
1486 : int
1487 : fd_sbpf_elf_peek( fd_sbpf_elf_info_t * info,
1488 : void const * bin,
1489 : ulong bin_sz,
1490 141 : fd_sbpf_loader_config_t const * config ) {
1491 : /* Extract sbpf_version (or error)
1492 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L376-L401 */
1493 141 : int maybe_sbpf_version = fd_sbpf_program_get_sbpf_version_or_err( bin, bin_sz, config );
1494 141 : if( FD_UNLIKELY( maybe_sbpf_version<0 ) ) {
1495 42 : return maybe_sbpf_version;
1496 42 : }
1497 :
1498 : /* Initialize info struct */
1499 99 : *info = (fd_sbpf_elf_info_t) {
1500 99 : .bin_sz = 0U,
1501 99 : .text_off = 0U,
1502 99 : .text_cnt = 0U,
1503 99 : .text_sz = 0UL,
1504 99 : .shndx_text = -1,
1505 99 : .shndx_symtab = -1,
1506 99 : .shndx_strtab = -1,
1507 99 : .shndx_dyn = -1,
1508 99 : .shndx_dynstr = -1,
1509 99 : .shndx_dynsymtab = -1,
1510 99 : .phndx_dyn = -1,
1511 99 : .dt_rel_off = 0UL,
1512 99 : .dt_rel_sz = 0UL,
1513 99 : .sbpf_version = (uint)maybe_sbpf_version,
1514 : /* !!! Keep this in sync with -Werror=missing-field-initializers */
1515 99 : };
1516 :
1517 : /* Invoke strict vs lenient parser. The strict parser is used for
1518 : SBPF version >= 3. The strict parser also returns an ElfParserError
1519 : while the lenient parser returns an ElfError, so we have to map
1520 : the strict parser's error code.
1521 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L403-L407 */
1522 99 : if( FD_UNLIKELY( fd_sbpf_enable_stricter_elf_headers_enabled( info->sbpf_version ) ) ) {
1523 9 : return fd_sbpf_elf_parser_err_to_elf_err( fd_sbpf_elf_peek_strict( info, bin, bin_sz ) );
1524 9 : }
1525 90 : return fd_sbpf_elf_peek_lenient( info, bin, bin_sz, config );
1526 99 : }
1527 :
1528 : /* Parses and concatenates the readonly data sections. This function
1529 : also computes and sets the rodata_sz field inside the SBPF program
1530 : struct. scratch is a pointer to a scratch area with size scratch_sz,
1531 : used to allocate a temporary buffer for the parsed rodata sections
1532 : before copying it back into the rodata (recommended size is bin_sz).
1533 : Returns 0 on success and an ElfError error code on failure. On
1534 : success, the rodata and rodata_sz fields in the sbpf program struct
1535 : are updated.
1536 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L812-L987 */
1537 : static int
1538 : fd_sbpf_parse_ro_sections( fd_sbpf_program_t * prog,
1539 : void const * bin,
1540 : ulong bin_sz,
1541 : fd_sbpf_loader_config_t const * config,
1542 : void * scratch,
1543 69 : ulong scratch_sz ) {
1544 :
1545 69 : fd_sbpf_elf_t const * elf = (fd_sbpf_elf_t const *)bin;
1546 69 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
1547 69 : fd_elf64_shdr const * section_names_shdr = &shdrs[ elf->ehdr.e_shstrndx ];
1548 69 : uchar * rodata = prog->rodata;
1549 :
1550 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L818-L834 */
1551 69 : ulong lowest_addr = ULONG_MAX; /* Lowest section address */
1552 69 : ulong highest_addr = 0UL; /* Highest section address */
1553 69 : ulong ro_fill_length = 0UL; /* Aggregated section length, excluding gaps between sections */
1554 69 : uchar invalid_offsets = 0; /* Whether the section has invalid offsets */
1555 :
1556 : /* Store the section header indices of ro slices to fill later. */
1557 69 : ulong ro_slices_shidxs[ elf->ehdr.e_shnum ];
1558 69 : ulong ro_slices_cnt = 0UL;
1559 :
1560 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L837-L909 */
1561 729 : for( uint i=0U; i<elf->ehdr.e_shnum; i++ ) {
1562 660 : fd_elf64_shdr const * section_header = &shdrs[ i ];
1563 :
1564 : /* Match the section name.
1565 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L838-L845 */
1566 660 : uchar const * name;
1567 660 : ulong name_len;
1568 660 : 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 ) ) ) {
1569 0 : continue;
1570 0 : }
1571 :
1572 660 : if( FD_UNLIKELY( !fd_sbpf_slice_cstr_eq( name, name_len, ".text" ) &&
1573 660 : !fd_sbpf_slice_cstr_eq( name, name_len, ".rodata" ) &&
1574 660 : !fd_sbpf_slice_cstr_eq( name, name_len, ".data.rel.ro" ) &&
1575 660 : !fd_sbpf_slice_cstr_eq( name, name_len, ".eh_frame" ) ) ) {
1576 504 : continue;
1577 504 : }
1578 :
1579 156 : ulong section_addr = section_header->sh_addr;
1580 :
1581 : /* Handling for the section header offsets. If ELF vaddrs are
1582 : enabled, the section header addresses are allowed to be > the
1583 : section header offsets, as long as address - offset is constant
1584 : across all sections. Otherwise, the section header addresses
1585 : and offsets must match.
1586 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L865-L884 */
1587 156 : if( FD_LIKELY( !invalid_offsets ) ) {
1588 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L866-L880 */
1589 156 : if( FD_UNLIKELY( section_addr!=section_header->sh_offset ) ) {
1590 3 : invalid_offsets = 1;
1591 3 : }
1592 156 : }
1593 :
1594 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L886-L897 */
1595 156 : ulong vaddr_end = section_addr;
1596 156 : if( section_addr<FD_SBPF_MM_RODATA_ADDR ) {
1597 156 : vaddr_end = fd_ulong_sat_add( section_addr, FD_SBPF_MM_RODATA_ADDR );
1598 156 : }
1599 :
1600 156 : if( FD_UNLIKELY( ( config->reject_broken_elfs && invalid_offsets ) ||
1601 156 : vaddr_end>FD_SBPF_MM_STACK_ADDR ) ) {
1602 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1603 0 : }
1604 :
1605 : /* Append the ro slices vector and update the lowest / highest addr
1606 : and ro_fill_length variables. Agave stores three fields in the
1607 : ro slices array that can all be derived from the section header,
1608 : so we just need to store the indices.
1609 :
1610 : The call to fd_shdr_get_file_range() is allowed to fail (Agave's
1611 : unwrap_or_default() call returns a range of 0..0 in this case).
1612 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L899-L908 */
1613 156 : fd_sbpf_range_t section_header_range;
1614 156 : fd_shdr_get_file_range( section_header, §ion_header_range );
1615 156 : if( FD_UNLIKELY( section_header_range.hi>bin_sz ) ) {
1616 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1617 0 : }
1618 156 : ulong section_data_len = section_header_range.hi-section_header_range.lo;
1619 :
1620 156 : lowest_addr = fd_ulong_min( lowest_addr, section_addr );
1621 156 : highest_addr = fd_ulong_max( highest_addr, fd_ulong_sat_add( section_addr, section_data_len ) );
1622 156 : ro_fill_length = fd_ulong_sat_add( ro_fill_length, section_data_len );
1623 156 : ro_slices_shidxs[ ro_slices_cnt++ ] = i;
1624 156 : }
1625 :
1626 : /* This checks that the ro sections are not overlapping. This check
1627 : is incomplete, however, because it does not account for the
1628 : existence of gaps between sections in calculations.
1629 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L910-L913 */
1630 69 : if( FD_UNLIKELY( config->reject_broken_elfs &&
1631 69 : fd_ulong_sat_add( lowest_addr, ro_fill_length )>highest_addr ) ) {
1632 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1633 0 : }
1634 :
1635 : /* Note that optimize_rodata is always false.
1636 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L923-L984 */
1637 69 : {
1638 : /* Readonly / non-readonly sections are mixed, so non-readonly
1639 : sections must be zeroed and the readonly sections must be copied
1640 : at their respective offsets.
1641 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L950-L983 */
1642 69 : lowest_addr = 0UL;
1643 :
1644 : /* Bounds check. */
1645 69 : ulong buf_len = highest_addr;
1646 69 : if( FD_UNLIKELY( buf_len>bin_sz ) ) {
1647 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1648 0 : }
1649 :
1650 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L971-L976 */
1651 69 : if( FD_UNLIKELY( buf_len>scratch_sz ) ) {
1652 0 : FD_LOG_CRIT(( "scratch_sz is too small: %lu, required: %lu", scratch_sz, buf_len ));
1653 0 : }
1654 69 : uchar * ro_section = scratch;
1655 69 : fd_memset( ro_section, 0, buf_len );
1656 :
1657 225 : for( ulong i=0UL; i<ro_slices_cnt; i++ ) {
1658 156 : ulong sh_idx = ro_slices_shidxs[ i ];
1659 156 : fd_elf64_shdr const * shdr = &shdrs[ sh_idx ];
1660 156 : ulong section_addr = shdr->sh_addr;
1661 :
1662 : /* This was checked above and should never fail. */
1663 156 : fd_sbpf_range_t slice_range;
1664 156 : fd_shdr_get_file_range( shdr, &slice_range );
1665 156 : if( FD_UNLIKELY( slice_range.hi>bin_sz ) ) {
1666 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1667 0 : }
1668 :
1669 156 : ulong buf_offset_start = fd_ulong_sat_sub( section_addr, lowest_addr );
1670 156 : ulong slice_len = slice_range.hi-slice_range.lo;
1671 156 : if( FD_UNLIKELY( slice_len>buf_len ) ) {
1672 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1673 0 : }
1674 :
1675 156 : fd_memcpy( ro_section+buf_offset_start, rodata+slice_range.lo, slice_len );
1676 156 : }
1677 :
1678 : /* Copy the rodata section back in. */
1679 69 : prog->rodata_sz = buf_len;
1680 69 : fd_memcpy( rodata, ro_section, buf_len );
1681 69 : }
1682 :
1683 69 : return FD_SBPF_ELF_SUCCESS;
1684 69 : }
1685 :
1686 : /* Applies ELF relocations in-place. Returns 0 on success and an
1687 : ElfError error code on failure.
1688 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L990-L1331 */
1689 : static int
1690 : fd_sbpf_program_relocate( fd_sbpf_program_t * prog,
1691 : void const * bin,
1692 : ulong bin_sz,
1693 : fd_sbpf_loader_config_t const * config,
1694 69 : fd_sbpf_loader_t * loader ) {
1695 69 : fd_sbpf_elf_info_t const * elf_info = &prog->info;
1696 69 : fd_sbpf_elf_t const * elf = (fd_sbpf_elf_t const *)bin;
1697 69 : uchar * rodata = prog->rodata;
1698 69 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
1699 69 : fd_elf64_shdr const * shtext = &shdrs[ elf_info->shndx_text ];
1700 :
1701 : /* Copy rodata segment */
1702 69 : fd_memcpy( rodata, elf->bin, elf_info->bin_sz );
1703 :
1704 : /* Fixup all program counter relative call instructions
1705 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1005-L1041 */
1706 69 : {
1707 : /* Validate the bytes range of the text section.
1708 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1006-L1008 */
1709 69 : fd_sbpf_range_t text_section_range;
1710 69 : fd_shdr_get_file_range( shtext, &text_section_range );
1711 :
1712 69 : ulong insn_cnt = (text_section_range.hi-text_section_range.lo)/8UL;
1713 69 : if( FD_UNLIKELY( shtext->sh_size+shtext->sh_offset>bin_sz ) ) {
1714 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1715 0 : }
1716 :
1717 69 : uchar * ptr = rodata + shtext->sh_offset;
1718 :
1719 136377 : for( ulong i=0UL; i<insn_cnt; i++, ptr+=8UL ) {
1720 136308 : ulong insn = FD_LOAD( ulong, ptr );
1721 :
1722 : /* Check for call instruction. If immediate is UINT_MAX, assume
1723 : that compiler generated a relocation instead.
1724 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1015 */
1725 136308 : ulong opc = insn & 0xFF;
1726 136308 : int imm = (int)(insn >> 32UL);
1727 136308 : if( (opc!=FD_SBPF_OP_CALL_IMM) || (imm==-1) ) continue;
1728 :
1729 : /* Calculate and check the target PC
1730 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1016-L1021 */
1731 4536 : long target_pc = fd_long_sat_add( fd_long_sat_add( (long)i, 1L ), imm);
1732 4536 : if( FD_UNLIKELY( target_pc<0L || target_pc>=(long)insn_cnt ) ) {
1733 0 : return FD_SBPF_ELF_ERR_RELATIVE_JUMP_OUT_OF_BOUNDS;
1734 0 : }
1735 :
1736 : /* Update the calldests
1737 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1027-L1032 */
1738 4536 : uint pc_hash;
1739 4536 : int err = fd_sbpf_register_function_hashed_legacy( loader, prog, NULL, 0UL, (ulong)target_pc, &pc_hash );
1740 4536 : if( FD_UNLIKELY( err!=FD_SBPF_ELF_SUCCESS ) ) {
1741 0 : return err;
1742 0 : }
1743 :
1744 : /* Store PC hash in text section. Check for writes outside the
1745 : text section.
1746 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1034-L1038 */
1747 4536 : ulong offset = fd_ulong_sat_add( fd_ulong_sat_mul( i, 8UL ), 4UL ); // offset in text section
1748 4536 : if( FD_UNLIKELY( offset+4UL>shtext->sh_size ) ) {
1749 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1750 0 : }
1751 :
1752 4536 : FD_STORE( uint, ptr+4UL, pc_hash );
1753 4536 : }
1754 69 : }
1755 :
1756 : /* Fixup all the relocations in the relocation section if exists. The
1757 : dynamic relocations table was already parsed and validated in
1758 : fd_sbpf_lenient_elf_parse().
1759 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1046-L1304 */
1760 69 : {
1761 69 : fd_elf64_rel const * dt_rels = (fd_elf64_rel const *)( elf->bin + elf_info->dt_rel_off );
1762 69 : uint dt_rel_cnt = elf_info->dt_rel_sz / sizeof(fd_elf64_rel);
1763 :
1764 7407 : for( uint i=0U; i<dt_rel_cnt; i++ ) {
1765 7338 : fd_elf64_rel const * dt_rel = &dt_rels[ i ];
1766 7338 : ulong r_offset = dt_rel->r_offset;
1767 :
1768 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1068-L1303 */
1769 7338 : int err;
1770 7338 : switch( FD_ELF64_R_TYPE( dt_rel->r_info ) ) {
1771 6 : case FD_ELF_R_BPF_64_64:
1772 6 : err = fd_sbpf_r_bpf_64_64( elf, bin_sz, rodata, elf_info, dt_rel, r_offset );
1773 6 : break;
1774 5442 : case FD_ELF_R_BPF_64_RELATIVE:
1775 5442 : err = fd_sbpf_r_bpf_64_relative(elf, bin_sz, rodata, elf_info, r_offset );
1776 5442 : break;
1777 1890 : case FD_ELF_R_BPF_64_32:
1778 1890 : err = fd_sbpf_r_bpf_64_32( loader, prog, elf, bin_sz, rodata, elf_info, dt_rel, r_offset, config );
1779 1890 : break;
1780 0 : default:
1781 0 : return FD_SBPF_ELF_ERR_UNKNOWN_RELOCATION;
1782 7338 : }
1783 :
1784 7338 : if( FD_UNLIKELY( err!=FD_SBPF_ELF_SUCCESS ) ) {
1785 0 : return err;
1786 0 : }
1787 7338 : }
1788 69 : }
1789 :
1790 : /* ...rest of this function is a no-op because
1791 : enable_symbol_and_section_labels is disabled in production. */
1792 :
1793 69 : return FD_SBPF_ELF_SUCCESS;
1794 69 : }
1795 :
1796 : /* Second part of load_with_lenient_parser().
1797 :
1798 : This function is responsible for "loading" an sBPF program. This
1799 : means...
1800 : 1. Applies any relocations in-place to the rodata section.
1801 : 2. Registers the program entrypoint and other valid calldests.
1802 : 3. Parses and validates the rodata sections, zeroing out any gaps
1803 : between sections.
1804 :
1805 : Returns 0 on success and an ElfError error code on failure.
1806 :
1807 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L640-L689
1808 : */
1809 : static int
1810 : fd_sbpf_program_load_lenient( fd_sbpf_program_t * prog,
1811 : void const * bin,
1812 : ulong bin_sz,
1813 : fd_sbpf_loader_t * loader,
1814 : fd_sbpf_loader_config_t const * config,
1815 : void * scratch,
1816 69 : ulong scratch_sz ) {
1817 :
1818 : /* Load (vs peek) starts here
1819 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L641 */
1820 :
1821 69 : fd_sbpf_elf_t const * elf = (fd_sbpf_elf_t const *)bin;
1822 69 : fd_sbpf_elf_info_t * elf_info = &prog->info;
1823 69 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
1824 69 : fd_elf64_shdr const * sh_text = &shdrs[ elf_info->shndx_text ];
1825 :
1826 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L642-L647 */
1827 69 : int err = fd_sbpf_program_relocate( prog, bin, bin_sz, config, loader );
1828 69 : if( FD_UNLIKELY( err ) ) return err;
1829 :
1830 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L649-L653 */
1831 69 : ulong offset = fd_ulong_sat_sub( elf->ehdr.e_entry, sh_text->sh_addr );
1832 69 : if( FD_UNLIKELY( offset&0x7UL ) ) { /* offset % 8 != 0 */
1833 0 : return FD_SBPF_ELF_ERR_INVALID_ENTRYPOINT;
1834 0 : }
1835 :
1836 : /* Unregister the entrypoint from the calldests, and register the
1837 : entry_pc. Our behavior slightly diverges from Agave's because we
1838 : rely on an explicit entry_pc field within the elf_info struct
1839 : to handle the b"entrypoint" symbol, and rely on PC hash inverses
1840 : for any other CALL_IMM targets.
1841 :
1842 : Note that even though we won't use the calldests value for the
1843 : entry pc, we still need to "register" it to check for any potential
1844 : symbol collisions and report errors accordingly. We unregister it
1845 : first by setting it to ULONG_MAX.
1846 :
1847 : TODO: Add special casing for static syscalls enabled. For now, it
1848 : is not implemented.
1849 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L654-L667 */
1850 69 : prog->entry_pc = ULONG_MAX;
1851 69 : ulong entry_pc = offset/8UL;
1852 69 : err = fd_sbpf_register_function_hashed_legacy(
1853 69 : loader,
1854 69 : prog,
1855 69 : (uchar const *)"entrypoint",
1856 69 : strlen( "entrypoint" ),
1857 69 : entry_pc,
1858 69 : NULL );
1859 69 : if( FD_UNLIKELY( err!=FD_SBPF_ELF_SUCCESS ) ) {
1860 0 : return err;
1861 0 : }
1862 :
1863 : /* Parse the ro sections.
1864 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L669-L676 */
1865 69 : err = fd_sbpf_parse_ro_sections( prog, bin, bin_sz, config, scratch, scratch_sz );
1866 69 : if( FD_UNLIKELY( err!=FD_SBPF_ELF_SUCCESS ) ) {
1867 0 : return err;
1868 0 : }
1869 :
1870 69 : return FD_SBPF_ELF_SUCCESS;
1871 69 : }
1872 :
1873 : int
1874 : fd_sbpf_program_load( fd_sbpf_program_t * prog,
1875 : void const * bin,
1876 : ulong bin_sz,
1877 : fd_sbpf_syscalls_t * syscalls,
1878 : fd_sbpf_loader_config_t const * config,
1879 : void * scratch,
1880 69 : ulong scratch_sz ) {
1881 69 : fd_sbpf_loader_t loader = {
1882 69 : .calldests = prog->calldests,
1883 69 : .syscalls = syscalls,
1884 69 : };
1885 :
1886 : /* Invoke strict vs lenient loader
1887 : Note: info.sbpf_version is already set by fd_sbpf_program_parse()
1888 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L403-L409 */
1889 69 : if( FD_UNLIKELY( fd_sbpf_enable_stricter_elf_headers_enabled( prog->info.sbpf_version ) ) ) {
1890 : /* There is nothing else to do in the strict case except updating
1891 : the prog->rodata_sz field from phdr[ 1 ].p_memsz, and setting
1892 : the entry_pc. */
1893 0 : fd_elf64_ehdr ehdr = FD_LOAD( fd_elf64_ehdr, bin );
1894 0 : fd_elf64_phdr phdr_0 = FD_LOAD( fd_elf64_phdr, bin+sizeof(fd_elf64_ehdr) );
1895 0 : fd_elf64_phdr phdr_1 = FD_LOAD( fd_elf64_phdr, bin+sizeof(fd_elf64_ehdr)+sizeof(fd_elf64_phdr) );
1896 0 : prog->rodata_sz = phdr_1.p_memsz;
1897 0 : prog->entry_pc = ( ehdr.e_entry-phdr_0.p_vaddr )/8UL;
1898 0 : return FD_SBPF_ELF_SUCCESS;
1899 0 : }
1900 69 : int res = fd_sbpf_program_load_lenient( prog, bin, bin_sz, &loader, config, scratch, scratch_sz );
1901 69 : if( FD_UNLIKELY( res!=FD_SBPF_ELF_SUCCESS ) ) {
1902 0 : return res;
1903 0 : }
1904 :
1905 69 : return FD_SBPF_ELF_SUCCESS;
1906 69 : }
1907 :
1908 : #undef ERR
1909 : #undef FAIL
1910 : #undef REQUIRE
|