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 447 : #define FD_SBPF_MM_RODATA_ADDR (0x100000000UL) /* readonly program data */
43 10704 : #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 6546 : fd_sbpf_range_contains( fd_sbpf_range_t const * range, ulong x ) {
65 6546 : return !!(( range->lo<=x ) & ( x<range->hi ));
66 6546 : }
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 6915 : fd_sbpf_range_t * range ) {
79 6915 : if( shdr->sh_type==FD_ELF_SHT_NOBITS ) {
80 0 : *range = (fd_sbpf_range_t) { .lo = 0UL, .hi = 0UL };
81 0 : return NULL;
82 6915 : } else {
83 6915 : *range = (fd_sbpf_range_t) { .lo = shdr->sh_offset, .hi = fd_ulong_sat_add( shdr->sh_offset, shdr->sh_size ) };
84 6915 : return range;
85 6915 : }
86 6915 : }
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 1764 : #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 11424 : char const * cstr ) {
232 11424 : return !!(slice_len==strlen( cstr ) && fd_memeq( slice, cstr, slice_len ));
233 11424 : }
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 1083 : char const * cstr ) {
243 1083 : ulong cstr_len = strlen( cstr );
244 1083 : return !!(slice_len>=cstr_len && fd_memeq( slice, cstr, cstr_len ));
245 1083 : }
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 4680 : 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 4680 : 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 4680 : ulong offset_in_file;
272 4680 : 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 4680 : ulong string_range_start = offset_in_file;
277 4680 : ulong string_range_end = fd_ulong_min( section_header->sh_offset+section_header->sh_size, offset_in_file+maximum_length );
278 4680 : 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 4680 : 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 4680 : uchar * null_terminator_ptr = memchr( (uchar const *)elf_bytes+string_range_start, 0, string_range_end-string_range_start );
289 4680 : if( FD_UNLIKELY( null_terminator_ptr==NULL ) ) {
290 3 : return FD_SBPF_ELF_PARSER_ERR_STRING_TOO_LONG;
291 3 : }
292 :
293 4677 : *out_slice = elf_bytes+string_range_start;
294 4677 : *out_slice_len = (ulong)(null_terminator_ptr-*out_slice);
295 :
296 4677 : return FD_SBPF_ELF_SUCCESS;
297 4680 : }
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 5517 : uint * opt_out_pc_hash ) {
333 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/program.rs#L156-L160 */
334 5517 : uint pc_hash;
335 5517 : uchar is_entrypoint = fd_sbpf_slice_cstr_eq( name, name_len, "entrypoint" ) ||
336 5517 : target_pc==FD_SBPF_ENTRYPOINT_PC;
337 5517 : if( FD_UNLIKELY( is_entrypoint ) ) {
338 84 : 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 84 : prog->entry_pc = target_pc;
344 :
345 : /* Optimization for this constant value */
346 84 : pc_hash = FD_SBPF_ENTRYPOINT_HASH;
347 5433 : } else {
348 5433 : pc_hash = fd_pchash( (uint)target_pc );
349 5433 : }
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 5517 : 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 5517 : if( FD_LIKELY( !is_entrypoint &&
365 5517 : loader->calldests &&
366 5517 : fd_sbpf_calldests_valid_idx( loader->calldests, target_pc ) ) ) {
367 5433 : fd_sbpf_calldests_insert( loader->calldests, target_pc );
368 5433 : }
369 :
370 5517 : if( opt_out_pc_hash ) *opt_out_pc_hash = pc_hash;
371 5517 : return FD_SBPF_ELF_SUCCESS;
372 5517 : }
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 6402 : ulong r_offset ) {
542 :
543 6402 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
544 6402 : 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 6402 : 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 6402 : fd_sbpf_range_t text_section_range;
551 6402 : if( fd_shdr_get_file_range( sh_text, &text_section_range ) &&
552 6402 : 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 4290 : ulong imm_low_offset = imm_offset;
560 4290 : ulong imm_high_offset = fd_ulong_sat_add( r_offset,
561 4290 : 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 4290 : 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 4290 : 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 4290 : 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 4290 : 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 4290 : ulong refd_addr = ( (ulong)va_high<<32UL ) | va_low;
580 4290 : 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 4290 : if( refd_addr<FD_SBPF_MM_PROGRAM_ADDR ) {
590 4290 : refd_addr = fd_ulong_sat_add( refd_addr, FD_SBPF_MM_PROGRAM_ADDR );
591 4290 : }
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 4290 : 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 4290 : 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 4290 : 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 4290 : FD_STORE( uint, rodata+imm_high_offset, (uint)(refd_addr>>32UL) );
606 4290 : } else {
607 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1216-L1228 */
608 2112 : ulong refd_addr = 0UL;
609 :
610 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1230-L1239 */
611 2112 : 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 2112 : refd_addr = FD_LOAD( uint, rodata+imm_offset );
615 2112 : 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 2112 : 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 2112 : FD_STORE( ulong, rodata+r_offset, refd_addr );
623 2112 : }
624 :
625 6402 : return FD_SBPF_ELF_SUCCESS;
626 6402 : }
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 2202 : fd_sbpf_loader_config_t const * config ) {
639 :
640 2202 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
641 2202 : fd_elf64_shdr const * sh_text = &shdrs[ info->shndx_text ];
642 2202 : 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 2202 : 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 2202 : fd_elf64_sym const * symbol = NULL;
650 :
651 : /* Ensure the dynamic symbol table exists. */
652 2202 : 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 2202 : fd_elf64_shdr const * sh_dynsym = &shdrs[ info->shndx_dynsymtab ];
660 2202 : fd_elf64_sym const * dynsym_table = (fd_elf64_sym const *)( elf->bin + sh_dynsym->sh_offset );
661 2202 : 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 2202 : ulong r_sym = FD_ELF64_R_SYM( dt_rel->r_info );
666 2202 : if( FD_UNLIKELY( r_sym>=dynsym_cnt ) ) {
667 0 : return FD_SBPF_ELF_ERR_UNKNOWN_SYMBOL;
668 0 : }
669 2202 : 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 2202 : uchar const * name;
674 2202 : ulong name_len;
675 2202 : 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 2202 : uint key = 0U;
682 2202 : int symbol_is_function = ( FD_ELF64_ST_TYPE( symbol->st_info )==FD_ELF_STT_FUNC );
683 2202 : {
684 2202 : 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 144 : fd_sbpf_range_t text_section_range = (fd_sbpf_range_t) {
687 144 : .lo = sh_text->sh_addr,
688 144 : .hi = fd_ulong_sat_add( sh_text->sh_addr, sh_text->sh_size ) };
689 144 : 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 144 : ulong target_pc = fd_ulong_sat_sub( symbol->st_value, sh_text->sh_addr ) / 8UL;
695 144 : int err = fd_sbpf_register_function_hashed_legacy( loader, prog, name, name_len, target_pc, &key );
696 144 : if( FD_UNLIKELY( err!=FD_SBPF_ELF_SUCCESS ) ) {
697 0 : return err;
698 0 : }
699 2058 : } 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 2058 : key = fd_murmur3_32(name, name_len, 0UL );
703 2058 : if( FD_UNLIKELY( config->reject_broken_elfs &&
704 2058 : fd_sbpf_syscalls_query( loader->syscalls, key, NULL )==NULL ) ) {
705 0 : return FD_SBPF_ELF_ERR_UNRESOLVED_SYMBOL;
706 0 : }
707 2058 : }
708 2202 : }
709 :
710 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1297-L1300 */
711 2202 : 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 2202 : FD_STORE( uint, rodata+imm_offset, key );
716 :
717 2202 : return FD_SBPF_ELF_SUCCESS;
718 2202 : }
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 2997 : fd_sbpf_check_overlap( ulong a_start, ulong a_end, ulong b_start, ulong b_end ) {
843 2997 : return !( ( a_end <= b_start || b_end <= a_start ) );
844 2997 : }
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 96 : ulong bin_sz ) {
853 :
854 : /* This documents the values that will be set in this function */
855 96 : info->bin_sz = bin_sz;
856 96 : info->phndx_dyn = -1;
857 96 : info->shndx_dyn = -1;
858 96 : info->shndx_symtab = -1;
859 96 : info->shndx_strtab = -1;
860 96 : info->shndx_dynstr = -1;
861 96 : info->shndx_dynsymtab = -1;
862 :
863 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L149 */
864 96 : if( FD_UNLIKELY( bin_sz<sizeof(fd_elf64_ehdr) ) ) {
865 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
866 0 : }
867 :
868 96 : fd_elf64_ehdr ehdr = FD_LOAD( fd_elf64_ehdr, bin );
869 96 : ulong ehdr_start = 0;
870 96 : 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 96 : int parse_ehdr_err =
875 96 : ( fd_uint_load_4( ehdr.e_ident ) != FD_ELF_MAG_LE )
876 96 : | ( ehdr.e_ident[ FD_ELF_EI_CLASS ] != FD_ELF_CLASS_64 )
877 96 : | ( ehdr.e_ident[ FD_ELF_EI_DATA ] != FD_ELF_DATA_LE )
878 96 : | ( ehdr.e_ident[ FD_ELF_EI_VERSION ] != 1 )
879 96 : | ( ehdr.e_version != 1 )
880 96 : | ( ehdr.e_ehsize != sizeof(fd_elf64_ehdr) )
881 96 : | ( ehdr.e_phentsize != sizeof(fd_elf64_phdr) )
882 96 : | ( ehdr.e_shentsize != sizeof(fd_elf64_shdr) )
883 96 : | ( ehdr.e_shstrndx >= ehdr.e_shnum )
884 96 : ;
885 96 : 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 96 : ulong phdr_start = ehdr.e_phoff;
892 96 : ulong phdr_end, phdr_sz;
893 : /* Elf64::parse_program_header_table() */
894 96 : {
895 96 : 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 96 : 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 96 : 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 96 : if( FD_UNLIKELY( phdr_sz%sizeof(fd_elf64_phdr)!=0UL ) ) {
916 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_SIZE;
917 0 : }
918 :
919 96 : if( FD_UNLIKELY( phdr_end>bin_sz ) ) {
920 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
921 0 : }
922 :
923 96 : if( FD_UNLIKELY( !fd_ulong_is_aligned( phdr_start, 8UL ) ) ) {
924 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_ALIGNMENT;
925 0 : }
926 96 : }
927 :
928 : /* Section headers
929 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L167-L172 */
930 :
931 96 : ulong shdr_start = ehdr.e_shoff;
932 96 : ulong shdr_end, shdr_sz;
933 : /* Elf64::parse_section_header_table() */
934 96 : {
935 96 : 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 96 : 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 96 : 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 96 : 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 96 : 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 96 : if( FD_UNLIKELY( shdr_end > bin_sz ) ) {
963 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
964 0 : }
965 :
966 96 : if( FD_UNLIKELY( !fd_ulong_is_aligned( ehdr.e_shoff, 8UL ) ) ) {
967 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_ALIGNMENT;
968 0 : }
969 96 : }
970 :
971 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L174-L177 */
972 96 : fd_elf64_shdr shdr = FD_LOAD( fd_elf64_shdr, bin + ehdr.e_shoff );
973 96 : 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 96 : ulong vaddr = 0UL;
980 444 : for( ulong i=0; i<ehdr.e_phnum; i++ ) {
981 348 : fd_elf64_phdr phdr = FD_LOAD( fd_elf64_phdr, bin + phdr_start + i*sizeof(fd_elf64_phdr) );
982 348 : if( FD_UNLIKELY( phdr.p_type != FD_ELF_PT_LOAD ) ) {
983 : /* Remember first PT_DYNAMIC program header for dynamic parsing */
984 90 : if( phdr.p_type==FD_ELF_PT_DYNAMIC && info->phndx_dyn == -1 ) {
985 90 : info->phndx_dyn = (int)i;
986 90 : }
987 90 : continue;
988 90 : }
989 258 : if( FD_UNLIKELY( phdr.p_vaddr<vaddr ) ) {
990 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_PROGRAM_HEADER;
991 0 : }
992 258 : ulong _offset_plus_size;
993 258 : 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 258 : 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 258 : vaddr = phdr.p_vaddr;
1000 258 : }
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 96 : ulong offset = 0UL;
1005 1008 : 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 912 : fd_elf64_shdr shdr = FD_LOAD( fd_elf64_shdr, bin + shdr_start + i*sizeof(fd_elf64_shdr) );
1008 912 : 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 903 : if( shdr.sh_type==FD_ELF_SHT_DYNAMIC && info->shndx_dyn == -1 ) {
1014 90 : info->shndx_dyn = (int)i;
1015 90 : }
1016 :
1017 903 : ulong sh_start = shdr.sh_offset;
1018 903 : ulong sh_end;
1019 903 : 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 903 : 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 903 : 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 903 : 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 903 : if( FD_UNLIKELY( sh_start < offset ) ) {
1036 0 : return FD_SBPF_ELF_PARSER_ERR_SECTION_NOT_IN_ORDER;
1037 0 : }
1038 903 : offset = sh_end;
1039 903 : if( FD_UNLIKELY( sh_end > bin_sz ) ) {
1040 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
1041 0 : }
1042 903 : }
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 96 : {
1051 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L340-L342 */
1052 96 : 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 96 : ulong section_names_shdr_idx = ehdr.e_shstrndx;
1058 96 : 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 96 : 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 984 : for( ulong i=0; i<ehdr.e_shnum; i++ ) {
1067 : /* Again... */
1068 891 : fd_elf64_shdr shdr = FD_LOAD( fd_elf64_shdr, bin + shdr_start + i*sizeof(fd_elf64_shdr) );
1069 :
1070 891 : uchar const * name;
1071 891 : ulong name_len;
1072 891 : 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 891 : 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 888 : 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 852 : } 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 816 : } else if( fd_sbpf_slice_cstr_eq( name, name_len, ".dynstr" ) ) {
1104 87 : if( FD_UNLIKELY( info->shndx_dynstr != -1 ) ) {
1105 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_SECTION_HEADER;
1106 0 : }
1107 87 : info->shndx_dynstr = (int)i;
1108 87 : }
1109 888 : }
1110 96 : }
1111 :
1112 : /* Parse dynamic
1113 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L241 */
1114 93 : {
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 93 : ulong dynamic_table_start = ULONG_MAX;
1123 93 : 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 93 : if( info->phndx_dyn >= 0 ) {
1127 87 : fd_elf64_phdr dyn_ph = FD_LOAD( fd_elf64_phdr, bin + phdr_start + (ulong)info->phndx_dyn*sizeof(fd_elf64_phdr) );
1128 87 : dynamic_table_start = dyn_ph.p_offset;
1129 87 : 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 87 : if( FD_UNLIKELY( dynamic_table_end<dynamic_table_start ||
1135 87 : dynamic_table_end>bin_sz ||
1136 87 : dyn_ph.p_filesz%sizeof(fd_elf64_dyn)!=0UL ||
1137 87 : !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 87 : }
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 93 : 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 93 : 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 87 : ulong dynamic_table[ FD_ELF_DT_NUM ] = { 0UL };
1165 87 : ulong dyn_cnt = (dynamic_table_end - dynamic_table_start) / (ulong)sizeof(fd_elf64_dyn);
1166 912 : for( ulong i = 0UL; i<dyn_cnt; i++ ) {
1167 912 : fd_elf64_dyn dyn = FD_LOAD( fd_elf64_dyn, bin + dynamic_table_start + i*sizeof(fd_elf64_dyn) );
1168 :
1169 912 : if( FD_UNLIKELY( dyn.d_tag==FD_ELF_DT_NULL ) ) {
1170 87 : break;
1171 87 : }
1172 825 : if( FD_UNLIKELY( dyn.d_tag>=FD_ELF_DT_NUM ) ) {
1173 78 : continue;
1174 78 : }
1175 :
1176 747 : dynamic_table[ dyn.d_tag ] = dyn.d_un.d_val;
1177 747 : }
1178 :
1179 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L409 */
1180 87 : do {
1181 87 : ulong vaddr = dynamic_table[ FD_ELF_DT_REL ];
1182 87 : if( FD_UNLIKELY( vaddr==0UL ) ) {
1183 15 : break; /* from this do-while */
1184 15 : }
1185 :
1186 72 : 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 72 : ulong size = dynamic_table[ FD_ELF_DT_RELSZ ];
1191 72 : 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 72 : ulong offset = ULONG_MAX;
1197 207 : for( ulong i=0; i<ehdr.e_phnum; i++ ) {
1198 : /* Again... */
1199 207 : fd_elf64_phdr phdr = FD_LOAD( fd_elf64_phdr, bin + phdr_start + i*sizeof(fd_elf64_phdr) );
1200 207 : 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 207 : if( phdr.p_vaddr<=vaddr && vaddr<phdr.p_vaddr+phdr.p_memsz ) {
1204 : /* vaddr - phdr.p_vaddr is guaranteed to be non-negative */
1205 72 : offset = vaddr-phdr.p_vaddr+phdr.p_offset;
1206 72 : if( FD_UNLIKELY( offset<phdr.p_offset ) ) {
1207 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
1208 0 : }
1209 72 : break;
1210 72 : }
1211 207 : }
1212 72 : 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 72 : 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 72 : ulong _offset_plus_size;
1227 72 : 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 72 : if( FD_UNLIKELY( ( size%sizeof(fd_elf64_rel)!=0UL ) ||
1234 72 : ( offset+size>bin_sz ) ||
1235 72 : ( !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 72 : info->dt_rel_off = (uint)offset;
1241 72 : info->dt_rel_sz = (uint)size;
1242 72 : } 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 87 : do {
1246 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L452-L455 */
1247 87 : ulong vaddr = dynamic_table[ FD_ELF_DT_SYMTAB ];
1248 87 : if( FD_UNLIKELY( vaddr==0UL ) ) {
1249 0 : break; /* from this do-while */
1250 0 : }
1251 :
1252 87 : fd_elf64_shdr shdr_sym = { 0 };
1253 462 : for( ulong i=0; i<ehdr.e_shnum; i++ ) {
1254 : /* Again... */
1255 462 : shdr_sym = FD_LOAD( fd_elf64_shdr, bin + shdr_start + i*sizeof(fd_elf64_shdr) );
1256 462 : if( shdr_sym.sh_addr == vaddr ) {
1257 87 : info->shndx_dynsymtab = (int)i;
1258 87 : break;
1259 87 : }
1260 462 : }
1261 :
1262 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L457-L461 */
1263 87 : 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 87 : {
1269 87 : 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 87 : ulong shdr_sym_start = shdr_sym.sh_offset;
1273 87 : 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 87 : 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 87 : 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 87 : 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 87 : if( FD_UNLIKELY( !fd_ulong_is_aligned( shdr_sym_start, 8UL ) ) ) {
1289 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_ALIGNMENT;
1290 0 : }
1291 87 : }
1292 87 : } while( 0 ); /* so we can break out */
1293 87 : }
1294 :
1295 87 : return FD_SBPF_ELF_SUCCESS;
1296 87 : }
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 93 : fd_elf64_shdr * text_shdr ) {
1306 :
1307 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L721-L736 */
1308 93 : fd_elf64_ehdr ehdr = FD_LOAD( fd_elf64_ehdr, bin );
1309 93 : 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 93 : 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 93 : 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 93 : 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 93 : 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 93 : ulong shdr_start = ehdr.e_shoff;
1331 93 : ulong section_names_shdr_idx = ehdr.e_shstrndx;
1332 93 : 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 93 : int shndx_text = -1;
1338 93 : int writeable_err = 0;
1339 93 : int oob_err = 0;
1340 966 : for( ulong i=0UL; i<ehdr.e_shnum; i++ ) {
1341 : /* Again... */
1342 873 : fd_elf64_shdr shdr = FD_LOAD( fd_elf64_shdr, bin + ehdr.e_shoff + i*sizeof(fd_elf64_shdr) );
1343 :
1344 873 : uchar const * name;
1345 873 : ulong name_len;
1346 873 : 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 873 : 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 873 : if( FD_UNLIKELY( fd_sbpf_slice_cstr_eq( name, name_len, ".text" ) ) ) {
1354 93 : if( FD_LIKELY( shndx_text==-1 ) ) {
1355 93 : *text_shdr = shdr; /* Store the text section header */
1356 93 : shndx_text = (int)i;
1357 93 : } else {
1358 0 : return FD_SBPF_ELF_ERR_NOT_ONE_TEXT_SECTION;
1359 0 : }
1360 93 : }
1361 :
1362 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L780-L791 */
1363 873 : if( FD_UNLIKELY( fd_sbpf_slice_cstr_start_with( name, name_len, ".bss" ) ||
1364 873 : ( ( ( shdr.sh_flags & (FD_ELF_SHF_ALLOC | FD_ELF_SHF_WRITE) ) == (FD_ELF_SHF_ALLOC | FD_ELF_SHF_WRITE) ) &&
1365 873 : fd_sbpf_slice_cstr_start_with( name, name_len, ".data" ) &&
1366 873 : !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 873 : ulong shdr_end;
1373 873 : if( FD_UNLIKELY( __builtin_uaddl_overflow( shdr.sh_offset, shdr.sh_size, &shdr_end ) ||
1374 873 : shdr_end>bin_sz ) ) {
1375 0 : oob_err = 1;
1376 0 : }
1377 873 : }
1378 :
1379 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L776-L778 */
1380 93 : 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 93 : 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 90 : 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 90 : if( FD_UNLIKELY( !(
1396 90 : text_shdr->sh_addr <= ehdr.e_entry && ehdr.e_entry < fd_ulong_sat_add( text_shdr->sh_addr, text_shdr->sh_size )
1397 90 : ) ) ) {
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 90 : fd_sbpf_range_t text_section_range;
1403 90 : fd_shdr_get_file_range( text_shdr, &text_section_range );
1404 :
1405 90 : info->text_off = (uint)text_shdr->sh_addr;
1406 90 : info->text_sz = text_section_range.hi-text_section_range.lo;
1407 90 : info->text_cnt = (uint)( info->text_sz/8UL );
1408 90 : info->shndx_text = shndx_text;
1409 :
1410 90 : return FD_SBPF_ELF_SUCCESS;
1411 90 : }
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 96 : fd_sbpf_loader_config_t const * config ) {
1423 :
1424 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L607 */
1425 96 : int res = fd_sbpf_lenient_elf_parse( info, bin, bin_sz );
1426 96 : 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 93 : fd_elf64_shdr text_shdr = { 0 };
1432 93 : res = fd_sbpf_lenient_elf_validate( info, bin, bin_sz, &text_shdr );
1433 93 : 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 90 : {
1439 90 : ulong text_section_vaddr = fd_ulong_sat_add( text_shdr.sh_addr, FD_SBPF_MM_RODATA_ADDR );
1440 90 : 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 90 : if( FD_UNLIKELY( ( config->reject_broken_elfs && text_shdr.sh_addr!=text_shdr.sh_offset ) ||
1445 90 : vaddr_end>FD_SBPF_MM_STACK_ADDR ) ) {
1446 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1447 0 : }
1448 90 : }
1449 :
1450 : /* Peek (vs load) stops here
1451 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L638 */
1452 :
1453 90 : return FD_SBPF_ELF_SUCCESS;
1454 90 : }
1455 :
1456 : static int
1457 : fd_sbpf_program_get_sbpf_version_or_err( void const * bin,
1458 : ulong bin_sz,
1459 147 : fd_sbpf_loader_config_t const * config ) {
1460 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L376-L381 */
1461 147 : const ulong E_FLAGS_OFFSET = 48UL;
1462 :
1463 147 : if( FD_UNLIKELY( bin_sz<E_FLAGS_OFFSET+sizeof(uint) ) ) {
1464 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1465 0 : }
1466 147 : uint e_flags = FD_LOAD( uint, bin+E_FLAGS_OFFSET );
1467 :
1468 147 : uint sbpf_version = 0U;
1469 147 : 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 135 : } else {
1473 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L390-L396 */
1474 135 : sbpf_version = e_flags < FD_SBPF_VERSION_COUNT ? e_flags : FD_SBPF_RESERVED;
1475 135 : }
1476 :
1477 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L399-L401 */
1478 147 : 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 105 : return (int)sbpf_version;
1484 147 : }
1485 :
1486 : int
1487 : fd_sbpf_elf_peek( fd_sbpf_elf_info_t * info,
1488 : void const * bin,
1489 : ulong bin_sz,
1490 147 : 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 147 : int maybe_sbpf_version = fd_sbpf_program_get_sbpf_version_or_err( bin, bin_sz, config );
1494 147 : if( FD_UNLIKELY( maybe_sbpf_version<0 ) ) {
1495 42 : return maybe_sbpf_version;
1496 42 : }
1497 :
1498 : /* Initialize info struct */
1499 105 : *info = (fd_sbpf_elf_info_t) {
1500 105 : .bin_sz = 0U,
1501 105 : .text_off = 0U,
1502 105 : .text_cnt = 0U,
1503 105 : .text_sz = 0UL,
1504 105 : .shndx_text = -1,
1505 105 : .shndx_symtab = -1,
1506 105 : .shndx_strtab = -1,
1507 105 : .shndx_dyn = -1,
1508 105 : .shndx_dynstr = -1,
1509 105 : .shndx_dynsymtab = -1,
1510 105 : .phndx_dyn = -1,
1511 105 : .dt_rel_off = 0UL,
1512 105 : .dt_rel_sz = 0UL,
1513 105 : .sbpf_version = (uint)maybe_sbpf_version,
1514 : /* !!! Keep this in sync with -Werror=missing-field-initializers */
1515 105 : };
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 105 : 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 96 : return fd_sbpf_elf_peek_lenient( info, bin, bin_sz, config );
1526 105 : }
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 75 : ulong scratch_sz ) {
1544 :
1545 75 : fd_sbpf_elf_t const * elf = (fd_sbpf_elf_t const *)bin;
1546 75 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
1547 75 : fd_elf64_shdr const * section_names_shdr = &shdrs[ elf->ehdr.e_shstrndx ];
1548 75 : uchar * rodata = prog->rodata;
1549 :
1550 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L818-L834 */
1551 75 : ulong lowest_addr = ULONG_MAX; /* Lowest section address */
1552 75 : ulong highest_addr = 0UL; /* Highest section address */
1553 75 : ulong ro_fill_length = 0UL; /* Aggregated section length, excluding gaps between sections */
1554 75 : uchar invalid_offsets = 0; /* Whether the section has invalid offsets */
1555 :
1556 : /* Store the section header indices of ro slices to fill later. */
1557 75 : ulong ro_slices_shidxs[ elf->ehdr.e_shnum ];
1558 75 : ulong ro_slices_cnt = 0UL;
1559 :
1560 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L837-L909 */
1561 789 : for( uint i=0U; i<elf->ehdr.e_shnum; i++ ) {
1562 714 : 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 714 : uchar const * name;
1567 714 : ulong name_len;
1568 714 : 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 714 : if( FD_UNLIKELY( !fd_sbpf_slice_cstr_eq( name, name_len, ".text" ) &&
1573 714 : !fd_sbpf_slice_cstr_eq( name, name_len, ".rodata" ) &&
1574 714 : !fd_sbpf_slice_cstr_eq( name, name_len, ".data.rel.ro" ) &&
1575 714 : !fd_sbpf_slice_cstr_eq( name, name_len, ".eh_frame" ) ) ) {
1576 540 : continue;
1577 540 : }
1578 :
1579 174 : 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 174 : if( FD_LIKELY( !invalid_offsets ) ) {
1588 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L866-L880 */
1589 174 : if( FD_UNLIKELY( section_addr!=section_header->sh_offset ) ) {
1590 3 : invalid_offsets = 1;
1591 3 : }
1592 174 : }
1593 :
1594 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L886-L897 */
1595 174 : ulong vaddr_end = section_addr;
1596 174 : if( section_addr<FD_SBPF_MM_RODATA_ADDR ) {
1597 174 : vaddr_end = fd_ulong_sat_add( section_addr, FD_SBPF_MM_RODATA_ADDR );
1598 174 : }
1599 :
1600 174 : if( FD_UNLIKELY( ( config->reject_broken_elfs && invalid_offsets ) ||
1601 174 : 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 174 : fd_sbpf_range_t section_header_range;
1614 174 : fd_shdr_get_file_range( section_header, §ion_header_range );
1615 174 : if( FD_UNLIKELY( section_header_range.hi>bin_sz ) ) {
1616 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1617 0 : }
1618 174 : ulong section_data_len = section_header_range.hi-section_header_range.lo;
1619 :
1620 174 : lowest_addr = fd_ulong_min( lowest_addr, section_addr );
1621 174 : highest_addr = fd_ulong_max( highest_addr, fd_ulong_sat_add( section_addr, section_data_len ) );
1622 174 : ro_fill_length = fd_ulong_sat_add( ro_fill_length, section_data_len );
1623 174 : ro_slices_shidxs[ ro_slices_cnt++ ] = i;
1624 174 : }
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 75 : if( FD_UNLIKELY( config->reject_broken_elfs &&
1631 75 : 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 75 : {
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 75 : lowest_addr = 0UL;
1643 :
1644 : /* Bounds check. */
1645 75 : ulong buf_len = highest_addr;
1646 75 : 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 75 : 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 75 : uchar * ro_section = scratch;
1655 75 : fd_memset( ro_section, 0, buf_len );
1656 :
1657 249 : for( ulong i=0UL; i<ro_slices_cnt; i++ ) {
1658 174 : ulong sh_idx = ro_slices_shidxs[ i ];
1659 174 : fd_elf64_shdr const * shdr = &shdrs[ sh_idx ];
1660 174 : ulong section_addr = shdr->sh_addr;
1661 :
1662 : /* This was checked above and should never fail. */
1663 174 : fd_sbpf_range_t slice_range;
1664 174 : fd_shdr_get_file_range( shdr, &slice_range );
1665 174 : if( FD_UNLIKELY( slice_range.hi>bin_sz ) ) {
1666 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1667 0 : }
1668 :
1669 174 : ulong buf_offset_start = fd_ulong_sat_sub( section_addr, lowest_addr );
1670 174 : ulong slice_len = slice_range.hi-slice_range.lo;
1671 174 : if( FD_UNLIKELY( slice_len>buf_len ) ) {
1672 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1673 0 : }
1674 :
1675 174 : fd_memcpy( ro_section+buf_offset_start, rodata+slice_range.lo, slice_len );
1676 174 : }
1677 :
1678 : /* Copy the rodata section back in. */
1679 75 : prog->rodata_sz = buf_len;
1680 75 : fd_memcpy( rodata, ro_section, buf_len );
1681 75 : }
1682 :
1683 75 : return FD_SBPF_ELF_SUCCESS;
1684 75 : }
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 75 : fd_sbpf_loader_t * loader ) {
1695 75 : fd_sbpf_elf_info_t const * elf_info = &prog->info;
1696 75 : fd_sbpf_elf_t const * elf = (fd_sbpf_elf_t const *)bin;
1697 75 : uchar * rodata = prog->rodata;
1698 75 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
1699 75 : fd_elf64_shdr const * shtext = &shdrs[ elf_info->shndx_text ];
1700 :
1701 : /* Copy rodata segment */
1702 75 : 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 75 : {
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 75 : fd_sbpf_range_t text_section_range;
1710 75 : fd_shdr_get_file_range( shtext, &text_section_range );
1711 :
1712 75 : ulong insn_cnt = (text_section_range.hi-text_section_range.lo)/8UL;
1713 75 : 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 75 : uchar * ptr = rodata + shtext->sh_offset;
1718 :
1719 157761 : for( ulong i=0UL; i<insn_cnt; i++, ptr+=8UL ) {
1720 157686 : 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 157686 : ulong opc = insn & 0xFF;
1726 157686 : int imm = (int)(insn >> 32UL);
1727 157686 : 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 5298 : long target_pc = fd_long_sat_add( fd_long_sat_add( (long)i, 1L ), imm);
1732 5298 : 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 5298 : uint pc_hash;
1739 5298 : int err = fd_sbpf_register_function_hashed_legacy( loader, prog, NULL, 0UL, (ulong)target_pc, &pc_hash );
1740 5298 : 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 5298 : ulong offset = fd_ulong_sat_add( fd_ulong_sat_mul( i, 8UL ), 4UL ); // offset in text section
1748 5298 : if( FD_UNLIKELY( offset+4UL>shtext->sh_size ) ) {
1749 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1750 0 : }
1751 :
1752 5298 : FD_STORE( uint, ptr+4UL, pc_hash );
1753 5298 : }
1754 75 : }
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 75 : {
1761 75 : fd_elf64_rel const * dt_rels = (fd_elf64_rel const *)( elf->bin + elf_info->dt_rel_off );
1762 75 : uint dt_rel_cnt = elf_info->dt_rel_sz / sizeof(fd_elf64_rel);
1763 :
1764 8685 : for( uint i=0U; i<dt_rel_cnt; i++ ) {
1765 8610 : fd_elf64_rel const * dt_rel = &dt_rels[ i ];
1766 8610 : 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 8610 : int err;
1770 8610 : 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 6402 : case FD_ELF_R_BPF_64_RELATIVE:
1775 6402 : err = fd_sbpf_r_bpf_64_relative(elf, bin_sz, rodata, elf_info, r_offset );
1776 6402 : break;
1777 2202 : case FD_ELF_R_BPF_64_32:
1778 2202 : err = fd_sbpf_r_bpf_64_32( loader, prog, elf, bin_sz, rodata, elf_info, dt_rel, r_offset, config );
1779 2202 : break;
1780 0 : default:
1781 0 : return FD_SBPF_ELF_ERR_UNKNOWN_RELOCATION;
1782 8610 : }
1783 :
1784 8610 : if( FD_UNLIKELY( err!=FD_SBPF_ELF_SUCCESS ) ) {
1785 0 : return err;
1786 0 : }
1787 8610 : }
1788 75 : }
1789 :
1790 : /* ...rest of this function is a no-op because
1791 : enable_symbol_and_section_labels is disabled in production. */
1792 :
1793 75 : return FD_SBPF_ELF_SUCCESS;
1794 75 : }
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 75 : 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 75 : fd_sbpf_elf_t const * elf = (fd_sbpf_elf_t const *)bin;
1822 75 : fd_sbpf_elf_info_t * elf_info = &prog->info;
1823 75 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
1824 75 : 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 75 : int err = fd_sbpf_program_relocate( prog, bin, bin_sz, config, loader );
1828 75 : if( FD_UNLIKELY( err ) ) return err;
1829 :
1830 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L649-L653 */
1831 75 : ulong offset = fd_ulong_sat_sub( elf->ehdr.e_entry, sh_text->sh_addr );
1832 75 : 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 75 : prog->entry_pc = ULONG_MAX;
1851 75 : ulong entry_pc = offset/8UL;
1852 75 : err = fd_sbpf_register_function_hashed_legacy(
1853 75 : loader,
1854 75 : prog,
1855 75 : (uchar const *)"entrypoint",
1856 75 : strlen( "entrypoint" ),
1857 75 : entry_pc,
1858 75 : NULL );
1859 75 : 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 75 : err = fd_sbpf_parse_ro_sections( prog, bin, bin_sz, config, scratch, scratch_sz );
1866 75 : if( FD_UNLIKELY( err!=FD_SBPF_ELF_SUCCESS ) ) {
1867 0 : return err;
1868 0 : }
1869 :
1870 75 : return FD_SBPF_ELF_SUCCESS;
1871 75 : }
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 75 : ulong scratch_sz ) {
1881 75 : fd_sbpf_loader_t loader = {
1882 75 : .calldests = prog->calldests,
1883 75 : .syscalls = syscalls,
1884 75 : };
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 75 : 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 75 : int res = fd_sbpf_program_load_lenient( prog, bin, bin_sz, &loader, config, scratch, scratch_sz );
1901 75 : if( FD_UNLIKELY( res!=FD_SBPF_ELF_SUCCESS ) ) {
1902 0 : return res;
1903 0 : }
1904 :
1905 75 : return FD_SBPF_ELF_SUCCESS;
1906 75 : }
1907 :
1908 : #undef ERR
1909 : #undef FAIL
1910 : #undef REQUIRE
|