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 414 : #define FD_SBPF_MM_RODATA_ADDR (0x100000000UL) /* readonly program data */
43 9336 : #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 5706 : fd_sbpf_range_contains( fd_sbpf_range_t const * range, ulong x ) {
65 5706 : return !!(( range->lo<=x ) & ( x<range->hi ));
66 5706 : }
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 6045 : fd_sbpf_range_t * range ) {
79 6045 : if( shdr->sh_type==FD_ELF_SHT_NOBITS ) {
80 0 : *range = (fd_sbpf_range_t) { .lo = 0UL, .hi = 0UL };
81 0 : return NULL;
82 6045 : } else {
83 6045 : *range = (fd_sbpf_range_t) { .lo = shdr->sh_offset, .hi = fd_ulong_sat_add( shdr->sh_offset, shdr->sh_size ) };
84 6045 : return range;
85 6045 : }
86 6045 : }
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 1674 : #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 72 : fd_sbpf_program_align( void ) {
127 72 : return alignof( fd_sbpf_program_t );
128 72 : }
129 :
130 : ulong
131 72 : fd_sbpf_program_footprint( fd_sbpf_elf_info_t const * info ) {
132 72 : FD_COMPILER_UNPREDICTABLE( info ); /* Make this appear as FD_FN_PURE (e.g. footprint might depened on info contents in future) */
133 72 : 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 72 : return FD_LAYOUT_FINI( FD_LAYOUT_APPEND( FD_LAYOUT_APPEND( FD_LAYOUT_INIT,
140 72 : alignof(fd_sbpf_program_t), sizeof(fd_sbpf_program_t) ),
141 72 : fd_sbpf_calldests_align(), fd_sbpf_calldests_footprint( info->text_cnt ) ), /* calldests bitmap */
142 72 : alignof(fd_sbpf_program_t) );
143 72 : }
144 :
145 : fd_sbpf_program_t *
146 : fd_sbpf_program_new( void * prog_mem,
147 : fd_sbpf_elf_info_t const * elf_info,
148 72 : void * rodata ) {
149 :
150 72 : if( FD_UNLIKELY( !prog_mem ) ) {
151 0 : FD_LOG_WARNING(( "NULL prog_mem" ));
152 0 : return NULL;
153 0 : }
154 :
155 72 : if( FD_UNLIKELY( !elf_info ) ) {
156 0 : FD_LOG_WARNING(( "NULL elf_info" ));
157 0 : return NULL;
158 0 : }
159 :
160 72 : 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 72 : 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 72 : FD_SCRATCH_ALLOC_INIT( laddr, prog_mem );
174 72 : 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 72 : .info = *elf_info,
179 72 : .rodata = rodata,
180 72 : .rodata_sz = 0UL,
181 72 : .text = (ulong *)((ulong)rodata + elf_info->text_off), /* FIXME: WHAT IF MISALIGNED */
182 72 : .entry_pc = ULONG_MAX,
183 72 : };
184 :
185 : /* If the text section is empty, then we do not need a calldests map. */
186 72 : ulong pc_max = elf_info->text_cnt;
187 72 : 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 6 : prog->calldests_shmem = NULL;
190 6 : prog->calldests = NULL;
191 66 : } else {
192 : /* Initialize calldests map. */
193 66 : prog->calldests_shmem = fd_sbpf_calldests_new(
194 66 : FD_SCRATCH_ALLOC_APPEND( laddr, fd_sbpf_calldests_align(),
195 66 : fd_sbpf_calldests_footprint( pc_max ) ),
196 0 : pc_max );
197 0 : prog->calldests = fd_sbpf_calldests_join( prog->calldests_shmem );
198 66 : }
199 :
200 72 : return prog;
201 72 : }
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 10317 : char const * cstr ) {
232 10317 : return !!(slice_len==strlen( cstr ) && fd_memeq( slice, cstr, slice_len ));
233 10317 : }
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 1020 : char const * cstr ) {
243 1020 : ulong cstr_len = strlen( cstr );
244 1020 : return !!(slice_len>=cstr_len && fd_memeq( slice, cstr, cstr_len ));
245 1020 : }
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 4263 : 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 4263 : 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 4263 : ulong offset_in_file;
272 4263 : 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 4263 : ulong string_range_start = offset_in_file;
277 4263 : ulong string_range_end = fd_ulong_min( section_header->sh_offset+section_header->sh_size, offset_in_file+maximum_length );
278 4263 : 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 4263 : 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 4263 : uchar * null_terminator_ptr = memchr( (uchar const *)elf_bytes+string_range_start, 0, string_range_end-string_range_start );
289 4263 : if( FD_UNLIKELY( null_terminator_ptr==NULL ) ) {
290 3 : return FD_SBPF_ELF_PARSER_ERR_STRING_TOO_LONG;
291 3 : }
292 :
293 4260 : *out_slice = elf_bytes+string_range_start;
294 4260 : *out_slice_len = (ulong)(null_terminator_ptr-*out_slice);
295 :
296 4260 : return FD_SBPF_ELF_SUCCESS;
297 4263 : }
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 81 : 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 81 : prog->entry_pc = target_pc;
344 :
345 : /* Optimization for this constant value */
346 81 : pc_hash = FD_SBPF_ENTRYPOINT_HASH;
347 4662 : } else {
348 4662 : pc_hash = fd_pchash( (uint)target_pc );
349 4662 : }
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 4662 : fd_sbpf_calldests_insert( loader->calldests, target_pc );
368 4662 : }
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 5568 : ulong r_offset ) {
542 :
543 5568 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
544 5568 : 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 5568 : 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 5568 : fd_sbpf_range_t text_section_range;
551 5568 : if( fd_shdr_get_file_range( sh_text, &text_section_range ) &&
552 5568 : 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 3756 : ulong imm_low_offset = imm_offset;
560 3756 : ulong imm_high_offset = fd_ulong_sat_add( r_offset,
561 3756 : 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 3756 : 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 3756 : 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 3756 : 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 3756 : 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 3756 : ulong refd_addr = ( (ulong)va_high<<32UL ) | va_low;
580 3756 : 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 3756 : if( refd_addr<FD_SBPF_MM_PROGRAM_ADDR ) {
590 3756 : refd_addr = fd_ulong_sat_add( refd_addr, FD_SBPF_MM_PROGRAM_ADDR );
591 3756 : }
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 3756 : 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 3756 : 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 3756 : 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 3756 : FD_STORE( uint, rodata+imm_high_offset, (uint)(refd_addr>>32UL) );
606 3756 : } else {
607 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1216-L1228 */
608 1812 : ulong refd_addr = 0UL;
609 :
610 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1230-L1239 */
611 1812 : 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 1812 : refd_addr = FD_LOAD( uint, rodata+imm_offset );
615 1812 : 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 1812 : 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 1812 : FD_STORE( ulong, rodata+r_offset, refd_addr );
623 1812 : }
624 :
625 5568 : return FD_SBPF_ELF_SUCCESS;
626 5568 : }
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 1920 : fd_sbpf_loader_config_t const * config ) {
639 :
640 1920 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
641 1920 : fd_elf64_shdr const * sh_text = &shdrs[ info->shndx_text ];
642 1920 : 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 1920 : 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 1920 : fd_elf64_sym const * symbol = NULL;
650 :
651 : /* Ensure the dynamic symbol table exists. */
652 1920 : 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 1920 : fd_elf64_shdr const * sh_dynsym = &shdrs[ info->shndx_dynsymtab ];
660 1920 : fd_elf64_sym const * dynsym_table = (fd_elf64_sym const *)( elf->bin + sh_dynsym->sh_offset );
661 1920 : 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 1920 : ulong r_sym = FD_ELF64_R_SYM( dt_rel->r_info );
666 1920 : if( FD_UNLIKELY( r_sym>=dynsym_cnt ) ) {
667 0 : return FD_SBPF_ELF_ERR_UNKNOWN_SYMBOL;
668 0 : }
669 1920 : 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 1920 : uchar const * name;
674 1920 : ulong name_len;
675 1920 : 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 1920 : uint key = 0U;
682 1920 : int symbol_is_function = ( FD_ELF64_ST_TYPE( symbol->st_info )==FD_ELF_STT_FUNC );
683 1920 : {
684 1920 : 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 1782 : } 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 1782 : key = fd_murmur3_32(name, name_len, 0UL );
703 1782 : if( FD_UNLIKELY( config->reject_broken_elfs &&
704 1782 : fd_sbpf_syscalls_query( loader->syscalls, key, NULL )==NULL ) ) {
705 0 : return FD_SBPF_ELF_ERR_UNRESOLVED_SYMBOL;
706 0 : }
707 1782 : }
708 1920 : }
709 :
710 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1297-L1300 */
711 1920 : 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 1920 : FD_STORE( uint, rodata+imm_offset, key );
716 :
717 1920 : return FD_SBPF_ELF_SUCCESS;
718 1920 : }
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 = sizeof(fd_elf64_ehdr) + 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 : /* Parse program headers (expecting 4 segments) */
766 :
767 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L455-L487
768 : Note: Agave iterates with a zip, i.e. it cuts the loop to 4, even
769 : though the number of phdrs is allowed to be higher. */
770 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 };
771 9 : uint expected_p_flags[ EXPECTED_PHDR_CNT ] = { FD_SBPF_PF_X, FD_SBPF_PF_R, FD_SBPF_PF_RW, FD_SBPF_PF_RW };
772 9 : fd_elf64_phdr phdr[ EXPECTED_PHDR_CNT ];
773 45 : for( uint i=0; i<EXPECTED_PHDR_CNT; i++ ) {
774 36 : ulong phdr_off = sizeof(fd_elf64_ehdr) + i*sizeof(fd_elf64_phdr);
775 36 : phdr[ i ] = FD_LOAD( fd_elf64_phdr, bin+phdr_off );
776 :
777 36 : ulong p_filesz = ( expected_p_flags[ i ] & FD_SBPF_PF_W ) ? 0UL : phdr[ i ].p_memsz;
778 36 : int parse_phdr_err =
779 36 : ( phdr[ i ].p_type != FD_ELF_PT_LOAD )
780 36 : | ( phdr[ i ].p_flags != expected_p_flags[ i ] )
781 36 : | ( phdr[ i ].p_offset < program_header_table_end )
782 36 : | ( phdr[ i ].p_offset >= bin_sz )
783 36 : | ( phdr[ i ].p_offset % 8UL != 0UL )
784 36 : | ( phdr[ i ].p_vaddr != expected_p_vaddr[ i ] )
785 36 : | ( phdr[ i ].p_paddr != expected_p_vaddr[ i ] )
786 36 : | ( phdr[ i ].p_filesz != p_filesz )
787 36 : | ( phdr[ i ].p_filesz > bin_sz - phdr[ i ].p_offset )
788 36 : | ( phdr[ i ].p_filesz % 8UL != 0UL )
789 36 : | ( phdr[ i ].p_memsz >= FD_SBPF_MM_REGION_SZ )
790 36 : ;
791 36 : if( FD_UNLIKELY( parse_phdr_err ) ) {
792 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_PROGRAM_HEADER;
793 0 : }
794 36 : }
795 :
796 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L489-L506 */
797 9 : ulong vm_range_start = phdr[ 0 ].p_vaddr;
798 9 : ulong vm_range_end = phdr[ 0 ].p_vaddr + phdr[ 0 ].p_memsz;
799 9 : ulong entry_chk = ehdr.e_entry + 7UL;
800 9 : int parse_e_entry_err =
801 9 : !( vm_range_start <= entry_chk && entry_chk < vm_range_end ) /* rust contains includes min, excludes max*/
802 9 : | ( ehdr.e_entry % 8UL != 0UL )
803 9 : ;
804 9 : if( FD_UNLIKELY( parse_e_entry_err ) ) {
805 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_FILE_HEADER;
806 0 : }
807 :
808 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L507-L515 */
809 9 : ulong entry_pc = ( ehdr.e_entry - phdr[ 0 ].p_vaddr ) / 8UL;
810 9 : ulong insn = fd_ulong_load_8( (uchar const *) bin + phdr[ 0 ].p_offset + entry_pc*8UL );
811 : /* Entrypoint must be a valid function start (ADD64_IMM with dst=r10)
812 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/ebpf.rs#L588 */
813 9 : if( FD_UNLIKELY( !fd_sbpf_is_function_start( fd_sbpf_instr( insn ) ) ) ) {
814 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_FILE_HEADER;
815 0 : }
816 :
817 : /* config.enable_symbol_and_section_labels is false in production,
818 : so there's nothing else to do.
819 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L519 */
820 :
821 9 : info->bin_sz = bin_sz;
822 9 : info->text_off = (uint)phdr[ 0 ].p_offset;
823 9 : info->text_sz = (uint)phdr[ 0 ].p_memsz;
824 9 : info->text_cnt = (uint)( phdr[ 0 ].p_memsz / 8UL );
825 :
826 9 : return FD_SBPF_ELF_SUCCESS;
827 9 : }
828 :
829 : static inline int
830 2853 : fd_sbpf_check_overlap( ulong a_start, ulong a_end, ulong b_start, ulong b_end ) {
831 2853 : return !( ( a_end <= b_start || b_end <= a_start ) );
832 2853 : }
833 :
834 : /* Mirrors Elf64::parse() in Agave. Returns an ElfParserError code on
835 : failure and 0 on success.
836 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L148 */
837 : int
838 : fd_sbpf_lenient_elf_parse( fd_sbpf_elf_info_t * info,
839 : void const * bin,
840 93 : ulong bin_sz ) {
841 :
842 : /* This documents the values that will be set in this function */
843 93 : info->bin_sz = bin_sz;
844 93 : info->phndx_dyn = -1;
845 93 : info->shndx_dyn = -1;
846 93 : info->shndx_symtab = -1;
847 93 : info->shndx_strtab = -1;
848 93 : info->shndx_dynstr = -1;
849 93 : info->shndx_dynsymtab = -1;
850 :
851 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L149 */
852 93 : if( FD_UNLIKELY( bin_sz<sizeof(fd_elf64_ehdr) ) ) {
853 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
854 0 : }
855 :
856 93 : fd_elf64_ehdr ehdr = FD_LOAD( fd_elf64_ehdr, bin );
857 93 : ulong ehdr_start = 0;
858 93 : ulong ehdr_end = sizeof(fd_elf64_ehdr);
859 :
860 : /* ELF header
861 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L151-L162 */
862 93 : int parse_ehdr_err =
863 93 : ( fd_uint_load_4( ehdr.e_ident ) != FD_ELF_MAG_LE )
864 93 : | ( ehdr.e_ident[ FD_ELF_EI_CLASS ] != FD_ELF_CLASS_64 )
865 93 : | ( ehdr.e_ident[ FD_ELF_EI_DATA ] != FD_ELF_DATA_LE )
866 93 : | ( ehdr.e_ident[ FD_ELF_EI_VERSION ] != 1 )
867 93 : | ( ehdr.e_version != 1 )
868 93 : | ( ehdr.e_ehsize != sizeof(fd_elf64_ehdr) )
869 93 : | ( ehdr.e_phentsize != sizeof(fd_elf64_phdr) )
870 93 : | ( ehdr.e_shentsize != sizeof(fd_elf64_shdr) )
871 93 : | ( ehdr.e_shstrndx >= ehdr.e_shnum )
872 93 : ;
873 93 : if( FD_UNLIKELY( parse_ehdr_err ) ) {
874 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_FILE_HEADER;
875 0 : }
876 :
877 : /* Program headers
878 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L164-L165 */
879 93 : ulong phdr_start = ehdr.e_phoff;
880 93 : ulong phdr_end, phdr_sz;
881 : /* Elf64::parse_program_header_table() */
882 93 : {
883 93 : if( FD_UNLIKELY( __builtin_umull_overflow( ehdr.e_phnum, sizeof(fd_elf64_phdr), &phdr_sz ) ) ) {
884 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
885 0 : }
886 :
887 93 : if( FD_UNLIKELY( __builtin_uaddl_overflow( ehdr.e_phoff, phdr_sz, &phdr_end ) ) ) {
888 : /* ArithmeticOverflow -> ElfParserError::OutOfBounds
889 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L671-L675 */
890 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
891 0 : }
892 :
893 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L301 */
894 93 : if( FD_UNLIKELY( fd_sbpf_check_overlap( ehdr_start, ehdr_end, phdr_start, phdr_end ) ) ) {
895 0 : return FD_SBPF_ELF_PARSER_ERR_OVERLAP;
896 0 : }
897 :
898 : /* Ensure program header table range lies within the file, like
899 : slice_from_bytes. Unfortunately the checks have to be split up
900 : because Agave throws different error codes depending on which
901 : condition fails...
902 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L302-L303 */
903 93 : if( FD_UNLIKELY( phdr_sz%sizeof(fd_elf64_phdr)!=0UL ) ) {
904 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_SIZE;
905 0 : }
906 :
907 93 : if( FD_UNLIKELY( phdr_end>bin_sz ) ) {
908 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
909 0 : }
910 :
911 93 : if( FD_UNLIKELY( !fd_ulong_is_aligned( phdr_start, 8UL ) ) ) {
912 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_ALIGNMENT;
913 0 : }
914 93 : }
915 :
916 : /* Section headers
917 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L167-L172 */
918 :
919 93 : ulong shdr_start = ehdr.e_shoff;
920 93 : ulong shdr_end, shdr_sz;
921 : /* Elf64::parse_section_header_table() */
922 93 : {
923 93 : if( FD_UNLIKELY( __builtin_umull_overflow( ehdr.e_shnum, sizeof(fd_elf64_shdr), &shdr_sz ) ) ) {
924 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
925 0 : }
926 :
927 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L314-L317 */
928 93 : if( FD_UNLIKELY( __builtin_uaddl_overflow( ehdr.e_shoff, shdr_sz, &shdr_end ) ) ) {
929 : /* ArithmeticOverflow -> ElfParserError::OutOfBounds
930 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L671-L675 */
931 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
932 0 : }
933 :
934 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L318 */
935 93 : if( FD_UNLIKELY( fd_sbpf_check_overlap( ehdr_start, ehdr_end, shdr_start, shdr_end ) ) ) {
936 0 : return FD_SBPF_ELF_PARSER_ERR_OVERLAP;
937 0 : }
938 :
939 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L319 */
940 93 : if( FD_UNLIKELY( fd_sbpf_check_overlap( phdr_start, phdr_end, shdr_start, shdr_end ) ) ) {
941 0 : return FD_SBPF_ELF_PARSER_ERR_OVERLAP;
942 0 : }
943 :
944 : /* Ensure section header table range lies within the file, like slice_from_bytes */
945 93 : if( FD_UNLIKELY( shdr_end > bin_sz ) ) {
946 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
947 0 : }
948 93 : }
949 :
950 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L174-L177 */
951 93 : fd_elf64_shdr shdr = FD_LOAD( fd_elf64_shdr, bin + ehdr.e_shoff );
952 93 : if( FD_UNLIKELY( shdr.sh_type != FD_ELF_SHT_NULL ) ) {
953 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_SECTION_HEADER;
954 0 : }
955 :
956 : /* Parse each program header
957 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L179-L196 */
958 93 : ulong vaddr = 0UL;
959 420 : for( ulong i=0; i<ehdr.e_phnum; i++ ) {
960 327 : fd_elf64_phdr phdr = FD_LOAD( fd_elf64_phdr, bin + phdr_start + i*sizeof(fd_elf64_phdr) );
961 327 : if( FD_UNLIKELY( phdr.p_type != FD_ELF_PT_LOAD ) ) {
962 : /* Remember first PT_DYNAMIC program header for dynamic parsing */
963 84 : if( phdr.p_type==FD_ELF_PT_DYNAMIC && info->phndx_dyn == -1 ) {
964 84 : info->phndx_dyn = (int)i;
965 84 : }
966 84 : continue;
967 84 : }
968 243 : if( FD_UNLIKELY( phdr.p_vaddr<vaddr ) ) {
969 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_PROGRAM_HEADER;
970 0 : }
971 243 : ulong _offset_plus_size;
972 243 : if( FD_UNLIKELY( __builtin_uaddl_overflow( phdr.p_offset, phdr.p_filesz, &_offset_plus_size ) ) ) {
973 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
974 0 : }
975 243 : if( FD_UNLIKELY( phdr.p_offset + phdr.p_filesz > bin_sz ) ) {
976 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
977 0 : }
978 243 : vaddr = phdr.p_vaddr;
979 243 : }
980 :
981 : /* Parse each section header
982 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L198-L216 */
983 93 : ulong offset = 0UL;
984 960 : for( ulong i=0; i<ehdr.e_shnum; i++ ) {
985 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L200-L205 */
986 867 : fd_elf64_shdr shdr = FD_LOAD( fd_elf64_shdr, bin + shdr_start + i*sizeof(fd_elf64_shdr) );
987 867 : if( FD_UNLIKELY( shdr.sh_type==FD_ELF_SHT_NOBITS ) ) {
988 9 : continue;
989 9 : }
990 :
991 : /* Remember first SHT_DYNAMIC section header for dynamic parsing */
992 858 : if( shdr.sh_type==FD_ELF_SHT_DYNAMIC && info->shndx_dyn == -1 ) {
993 84 : info->shndx_dyn = (int)i;
994 84 : }
995 :
996 858 : ulong sh_start = shdr.sh_offset;
997 858 : ulong sh_end;
998 858 : if( FD_UNLIKELY( __builtin_uaddl_overflow( shdr.sh_offset, shdr.sh_size, &sh_end ) ) ) {
999 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
1000 0 : }
1001 :
1002 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L206-L208 */
1003 858 : if( FD_UNLIKELY( fd_sbpf_check_overlap( sh_start, sh_end, ehdr_start, ehdr_end ) ) ) {
1004 0 : return FD_SBPF_ELF_PARSER_ERR_OVERLAP;
1005 0 : }
1006 858 : if( FD_UNLIKELY( fd_sbpf_check_overlap( sh_start, sh_end, phdr_start, phdr_end ) ) ) {
1007 0 : return FD_SBPF_ELF_PARSER_ERR_OVERLAP;
1008 0 : }
1009 858 : if( FD_UNLIKELY( fd_sbpf_check_overlap( sh_start, sh_end, shdr_start, shdr_end ) ) ) {
1010 0 : return FD_SBPF_ELF_PARSER_ERR_OVERLAP;
1011 0 : }
1012 :
1013 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L209-L215 */
1014 858 : if( FD_UNLIKELY( sh_start < offset ) ) {
1015 0 : return FD_SBPF_ELF_PARSER_ERR_SECTION_NOT_IN_ORDER;
1016 0 : }
1017 858 : offset = sh_end;
1018 858 : if( FD_UNLIKELY( sh_end > bin_sz ) ) {
1019 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
1020 0 : }
1021 858 : }
1022 :
1023 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L218-L224
1024 : section_header_table.get() returning ok is equivalent to ehdr.e_shstrndx < ehdr.e_shnum,
1025 : and this is already checked above. So, nothing to do here. */
1026 :
1027 : /* Parse sections
1028 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L240 */
1029 93 : {
1030 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L340-L342 */
1031 93 : if( FD_UNLIKELY( ehdr.e_shstrndx == 0 ) ) {
1032 0 : return FD_SBPF_ELF_PARSER_ERR_NO_SECTION_NAME_STRING_TABLE;
1033 0 : }
1034 :
1035 : /* Use section name string table to identify well-known sections */
1036 93 : ulong section_names_shdr_idx = ehdr.e_shstrndx;
1037 93 : fd_elf64_shdr section_names_shdr = FD_LOAD( fd_elf64_shdr, bin + shdr_start + section_names_shdr_idx*sizeof(fd_elf64_shdr) );
1038 : /* Agave repeats the following validation all the times, we can do it once here
1039 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L474-L476 */
1040 93 : if( FD_UNLIKELY( section_names_shdr.sh_type != FD_ELF_SHT_STRTAB ) ) {
1041 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_SECTION_HEADER;
1042 0 : }
1043 :
1044 : /* Iterate sections and record indices for .text, .symtab, .strtab, .dyn, .dynstr */
1045 936 : for( ulong i=0; i<ehdr.e_shnum; i++ ) {
1046 : /* Again... */
1047 846 : fd_elf64_shdr shdr = FD_LOAD( fd_elf64_shdr, bin + shdr_start + i*sizeof(fd_elf64_shdr) );
1048 :
1049 846 : uchar const * name;
1050 846 : ulong name_len;
1051 846 : 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 );
1052 846 : if( FD_UNLIKELY( res < 0 ) ) {
1053 3 : return res;
1054 3 : }
1055 :
1056 : /* Store the first section by name:
1057 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L350-L355
1058 : The rust code expands in:
1059 : match section_name {
1060 : b".symtab" => {
1061 : if self.symbol_section_header.is_some() {
1062 : return Err(ElfParserError::InvalidSectionHeader);
1063 : }
1064 : self.symbol_section_header = Some(section_header);
1065 : }
1066 : ...
1067 : _ => {}
1068 : }
1069 : Note that the number of bytes compared should not include the
1070 : null-terminator.
1071 : */
1072 843 : if( fd_sbpf_slice_cstr_eq( name, name_len, ".symtab" ) ) {
1073 36 : if( FD_UNLIKELY( info->shndx_symtab != -1 ) ) {
1074 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_SECTION_HEADER;
1075 0 : }
1076 36 : info->shndx_symtab = (int)i;
1077 807 : } else if( fd_sbpf_slice_cstr_eq( name, name_len, ".strtab" ) ) {
1078 36 : if( FD_UNLIKELY( info->shndx_strtab != -1 ) ) {
1079 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_SECTION_HEADER;
1080 0 : }
1081 36 : info->shndx_strtab = (int)i;
1082 771 : } else if( fd_sbpf_slice_cstr_eq( name, name_len, ".dynstr" ) ) {
1083 81 : if( FD_UNLIKELY( info->shndx_dynstr != -1 ) ) {
1084 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_SECTION_HEADER;
1085 0 : }
1086 81 : info->shndx_dynstr = (int)i;
1087 81 : }
1088 843 : }
1089 93 : }
1090 :
1091 : /* Parse dynamic
1092 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L241 */
1093 90 : {
1094 : /* Try PT_DYNAMIC first; if invalid or absent, fall back to SHT_DYNAMIC.
1095 : Note that only the first PT_DYNAMIC and SHT_DYNAMIC are used because of Rust iter().find().
1096 : Mirrors Rust logic:
1097 : - Try PT_DYNAMIC: https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L364-L372
1098 : - 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
1099 : If neither exists, return OK (static file). If SHT_DYNAMIC exists but is invalid, error. */
1100 :
1101 90 : ulong dynamic_table_start = ULONG_MAX;
1102 90 : ulong dynamic_table_end = ULONG_MAX;
1103 :
1104 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L364-L372 */
1105 90 : if( info->phndx_dyn >= 0 ) {
1106 81 : fd_elf64_phdr dyn_ph = FD_LOAD( fd_elf64_phdr, bin + phdr_start + (ulong)info->phndx_dyn*sizeof(fd_elf64_phdr) );
1107 81 : dynamic_table_start = dyn_ph.p_offset;
1108 81 : dynamic_table_end = dyn_ph.p_offset + dyn_ph.p_filesz;
1109 :
1110 : /* slice_from_program_header also checks that the size of the
1111 : slice is a multiple of the type size and that the alignment is
1112 : correct. */
1113 81 : if( FD_UNLIKELY( dynamic_table_end<dynamic_table_start ||
1114 81 : dynamic_table_end>bin_sz ||
1115 81 : dyn_ph.p_filesz%sizeof(fd_elf64_dyn)!=0UL ||
1116 81 : !fd_ulong_is_aligned( dynamic_table_start, 8UL ) ) ) {
1117 : /* skip - try SHT_DYNAMIC instead */
1118 0 : dynamic_table_start = ULONG_MAX;
1119 0 : dynamic_table_end = ULONG_MAX;
1120 0 : }
1121 81 : }
1122 :
1123 : /* If PT_DYNAMIC did not validate, try SHT_DYNAMIC
1124 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L376-L387 */
1125 90 : if( dynamic_table_start==ULONG_MAX && info->shndx_dyn >= 0 ) {
1126 0 : fd_elf64_shdr dyn_sh = FD_LOAD( fd_elf64_shdr, bin + shdr_start + (ulong)info->shndx_dyn*sizeof(fd_elf64_shdr) );
1127 0 : dynamic_table_start = dyn_sh.sh_offset;
1128 0 : dynamic_table_end = dyn_sh.sh_offset + dyn_sh.sh_size;
1129 0 : if( FD_UNLIKELY( ( dynamic_table_end < dynamic_table_start )
1130 0 : | ( dynamic_table_end > bin_sz )
1131 0 : | ( dyn_sh.sh_size % sizeof(fd_elf64_dyn) != 0UL ) ) ) {
1132 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L382-L385 */
1133 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_DYNAMIC_SECTION_TABLE;
1134 0 : }
1135 0 : }
1136 :
1137 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L393 */
1138 90 : if( dynamic_table_start==ULONG_MAX ) {
1139 9 : return FD_SBPF_ELF_SUCCESS;
1140 9 : }
1141 :
1142 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L396-L407 */
1143 81 : ulong dynamic_table[ FD_ELF_DT_NUM ] = { 0UL };
1144 81 : ulong dyn_cnt = (dynamic_table_end - dynamic_table_start) / (ulong)sizeof(fd_elf64_dyn);
1145 846 : for( ulong i = 0UL; i<dyn_cnt; i++ ) {
1146 846 : fd_elf64_dyn dyn = FD_LOAD( fd_elf64_dyn, bin + dynamic_table_start + i*sizeof(fd_elf64_dyn) );
1147 :
1148 846 : if( FD_UNLIKELY( dyn.d_tag==FD_ELF_DT_NULL ) ) {
1149 81 : break;
1150 81 : }
1151 765 : if( FD_UNLIKELY( dyn.d_tag>=FD_ELF_DT_NUM ) ) {
1152 72 : continue;
1153 72 : }
1154 :
1155 693 : dynamic_table[ dyn.d_tag ] = dyn.d_un.d_val;
1156 693 : }
1157 :
1158 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L409 */
1159 81 : do {
1160 81 : ulong vaddr = dynamic_table[ FD_ELF_DT_REL ];
1161 81 : if( FD_UNLIKELY( vaddr==0UL ) ) {
1162 15 : break; /* from this do-while */
1163 15 : }
1164 :
1165 66 : if ( FD_UNLIKELY( dynamic_table[ FD_ELF_DT_RELENT ] != sizeof(fd_elf64_rel) ) ) {
1166 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_DYNAMIC_SECTION_TABLE;
1167 0 : }
1168 :
1169 66 : ulong size = dynamic_table[ FD_ELF_DT_RELSZ ];
1170 66 : if( FD_UNLIKELY( size==0UL ) ) {
1171 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_DYNAMIC_SECTION_TABLE;
1172 0 : }
1173 :
1174 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L430-L444 */
1175 66 : ulong offset = ULONG_MAX;
1176 189 : for( ulong i=0; i<ehdr.e_phnum; i++ ) {
1177 : /* Again... */
1178 189 : fd_elf64_phdr phdr = FD_LOAD( fd_elf64_phdr, bin + phdr_start + i*sizeof(fd_elf64_phdr) );
1179 189 : if( FD_UNLIKELY( phdr.p_vaddr+phdr.p_memsz<phdr.p_vaddr ) ) {
1180 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
1181 0 : }
1182 189 : if( phdr.p_vaddr<=vaddr && vaddr<phdr.p_vaddr+phdr.p_memsz ) {
1183 : /* vaddr - phdr.p_vaddr is guaranteed to be non-negative */
1184 66 : offset = vaddr-phdr.p_vaddr+phdr.p_offset;
1185 66 : if( FD_UNLIKELY( offset<phdr.p_offset ) ) {
1186 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
1187 0 : }
1188 66 : break;
1189 66 : }
1190 189 : }
1191 66 : if( FD_UNLIKELY( offset==ULONG_MAX ) ) {
1192 0 : for( ulong i=0; i<ehdr.e_shnum; i++ ) {
1193 : /* Again... */
1194 0 : fd_elf64_shdr shdr = FD_LOAD( fd_elf64_shdr, bin + shdr_start + i*sizeof(fd_elf64_shdr) );
1195 0 : if( shdr.sh_addr == vaddr ) {
1196 0 : offset = shdr.sh_offset;
1197 0 : break;
1198 0 : }
1199 0 : }
1200 0 : }
1201 66 : if( FD_UNLIKELY( offset==ULONG_MAX ) ) {
1202 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_DYNAMIC_SECTION_TABLE;
1203 0 : }
1204 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L446-L448 */
1205 66 : ulong _offset_plus_size;
1206 66 : if( FD_UNLIKELY( __builtin_uaddl_overflow( offset, size, &_offset_plus_size ) ) ) {
1207 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
1208 0 : }
1209 :
1210 : /* slice_from_bytes checks that size is a multiple of the type
1211 : size and that the alignment of the bytes + offset is correct. */
1212 66 : if( FD_UNLIKELY( ( size%sizeof(fd_elf64_rel)!=0UL ) ||
1213 66 : ( offset+size>bin_sz ) ||
1214 66 : ( !fd_ulong_is_aligned( offset, 8UL ) ) ) ) {
1215 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_DYNAMIC_SECTION_TABLE;
1216 0 : }
1217 :
1218 : /* Save the dynamic relocation table info */
1219 66 : info->dt_rel_off = (uint)offset;
1220 66 : info->dt_rel_sz = (uint)size;
1221 66 : } while( 0 ); /* so we can break out */
1222 :
1223 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L410 */
1224 81 : do {
1225 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L452-L455 */
1226 81 : ulong vaddr = dynamic_table[ FD_ELF_DT_SYMTAB ];
1227 81 : if( FD_UNLIKELY( vaddr==0UL ) ) {
1228 0 : break; /* from this do-while */
1229 0 : }
1230 :
1231 81 : fd_elf64_shdr shdr_sym = { 0 };
1232 426 : for( ulong i=0; i<ehdr.e_shnum; i++ ) {
1233 : /* Again... */
1234 426 : shdr_sym = FD_LOAD( fd_elf64_shdr, bin + shdr_start + i*sizeof(fd_elf64_shdr) );
1235 426 : if( shdr_sym.sh_addr == vaddr ) {
1236 81 : info->shndx_dynsymtab = (int)i;
1237 81 : break;
1238 81 : }
1239 426 : }
1240 :
1241 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L457-L461 */
1242 81 : if( FD_UNLIKELY( info->shndx_dynsymtab==-1 ) ) {
1243 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_DYNAMIC_SECTION_TABLE;
1244 0 : }
1245 :
1246 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf_parser/mod.rs#L463-L464 */
1247 81 : {
1248 81 : if( FD_UNLIKELY( shdr_sym.sh_type != FD_ELF_SHT_SYMTAB && shdr_sym.sh_type != FD_ELF_SHT_DYNSYM ) ) {
1249 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_SECTION_HEADER;
1250 0 : }
1251 81 : ulong shdr_sym_start = shdr_sym.sh_offset;
1252 81 : ulong shdr_sym_end = shdr_sym.sh_offset + shdr_sym.sh_size;
1253 81 : if( FD_UNLIKELY( ( shdr_sym_end < shdr_sym_start )
1254 81 : | ( shdr_sym_end > bin_sz )
1255 81 : | ( shdr_sym.sh_size % sizeof(fd_elf64_sym) != 0UL ) ) ) {
1256 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_SIZE;
1257 0 : }
1258 81 : }
1259 81 : } while( 0 ); /* so we can break out */
1260 81 : }
1261 :
1262 81 : return FD_SBPF_ELF_SUCCESS;
1263 81 : }
1264 :
1265 : /* Performs validation checks on the ELF. Returns an ElfError on failure
1266 : and 0 on success.
1267 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L719-L809 */
1268 : int
1269 : fd_sbpf_lenient_elf_validate( fd_sbpf_elf_info_t * info,
1270 : void const * bin,
1271 : ulong bin_sz,
1272 90 : fd_elf64_shdr * text_shdr ) {
1273 :
1274 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L721-L736 */
1275 90 : fd_elf64_ehdr ehdr = FD_LOAD( fd_elf64_ehdr, bin );
1276 90 : if( FD_UNLIKELY( ehdr.e_ident[ FD_ELF_EI_CLASS ] != FD_ELF_CLASS_64 ) ) {
1277 0 : return FD_SBPF_ELF_ERR_WRONG_CLASS;
1278 0 : }
1279 90 : if( FD_UNLIKELY( ehdr.e_ident[ FD_ELF_EI_DATA ] != FD_ELF_DATA_LE ) ) {
1280 0 : return FD_SBPF_ELF_ERR_WRONG_ENDIANNESS;
1281 0 : }
1282 90 : if( FD_UNLIKELY( ehdr.e_ident[ FD_ELF_EI_OSABI ] != FD_ELF_OSABI_NONE ) ) {
1283 0 : return FD_SBPF_ELF_ERR_WRONG_ABI;
1284 0 : }
1285 90 : if( FD_UNLIKELY( ehdr.e_machine != FD_ELF_EM_BPF && ehdr.e_machine != FD_ELF_EM_SBPF ) ) {
1286 0 : return FD_SBPF_ELF_ERR_WRONG_MACHINE;
1287 0 : }
1288 90 : if( FD_UNLIKELY( ehdr.e_type != FD_ELF_ET_DYN ) ) {
1289 0 : return FD_SBPF_ELF_ERR_WRONG_TYPE;
1290 0 : }
1291 :
1292 : /* This code doesn't do anything:
1293 : 1. version is already checked at the very beginning of elf_peek
1294 : 2. the if condition is never true because sbpf_version is always v0
1295 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L738-L763 */
1296 :
1297 90 : ulong shdr_start = ehdr.e_shoff;
1298 90 : ulong section_names_shdr_idx = ehdr.e_shstrndx;
1299 90 : fd_elf64_shdr section_names_shdr = FD_LOAD( fd_elf64_shdr, bin + shdr_start + section_names_shdr_idx*sizeof(fd_elf64_shdr) );
1300 :
1301 : /* We do a single iteration over the section header table, collect all info
1302 : we need and return the errors later to match Agave. */
1303 :
1304 90 : int shndx_text = -1;
1305 90 : int writeable_err = 0;
1306 90 : int oob_err = 0;
1307 918 : for( ulong i=0UL; i<ehdr.e_shnum; i++ ) {
1308 : /* Again... */
1309 828 : fd_elf64_shdr shdr = FD_LOAD( fd_elf64_shdr, bin + ehdr.e_shoff + i*sizeof(fd_elf64_shdr) );
1310 :
1311 828 : uchar const * name;
1312 828 : ulong name_len;
1313 828 : 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 );
1314 828 : if( FD_UNLIKELY( res ) ) {
1315 : /* this can never fail because it was checked above, but safer to keep it */
1316 0 : return fd_sbpf_elf_parser_err_to_elf_err( res );
1317 0 : }
1318 :
1319 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L765-L775 */
1320 828 : if( FD_UNLIKELY( fd_sbpf_slice_cstr_eq( name, name_len, ".text" ) ) ) {
1321 90 : if( FD_LIKELY( shndx_text==-1 ) ) {
1322 90 : *text_shdr = shdr; /* Store the text section header */
1323 90 : shndx_text = (int)i;
1324 90 : } else {
1325 0 : return FD_SBPF_ELF_ERR_NOT_ONE_TEXT_SECTION;
1326 0 : }
1327 90 : }
1328 :
1329 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L780-L791 */
1330 828 : if( FD_UNLIKELY( fd_sbpf_slice_cstr_start_with( name, name_len, ".bss" ) ||
1331 828 : ( ( ( shdr.sh_flags & (FD_ELF_SHF_ALLOC | FD_ELF_SHF_WRITE) ) == (FD_ELF_SHF_ALLOC | FD_ELF_SHF_WRITE) ) &&
1332 828 : fd_sbpf_slice_cstr_start_with( name, name_len, ".data" ) &&
1333 828 : !fd_sbpf_slice_cstr_start_with( name, name_len, ".data.rel" ) ) ) ) {
1334 : /* to match Agave return error we can't fail here */
1335 6 : writeable_err = 1;
1336 6 : }
1337 :
1338 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L793-L802 */
1339 828 : ulong shdr_end;
1340 828 : if( FD_UNLIKELY( __builtin_uaddl_overflow( shdr.sh_offset, shdr.sh_size, &shdr_end ) ||
1341 828 : shdr_end>bin_sz ) ) {
1342 0 : oob_err = 1;
1343 0 : }
1344 828 : }
1345 :
1346 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L776-L778 */
1347 90 : if( FD_UNLIKELY( shndx_text==-1 ) ) {
1348 0 : return FD_SBPF_ELF_ERR_NOT_ONE_TEXT_SECTION;
1349 0 : }
1350 :
1351 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L786-L788 */
1352 90 : if( FD_UNLIKELY( writeable_err ) ) {
1353 3 : return FD_SBPF_ELF_ERR_WRITABLE_SECTION_NOT_SUPPORTED;
1354 3 : }
1355 :
1356 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L798 */
1357 87 : if( FD_UNLIKELY( oob_err ) ) {
1358 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1359 0 : }
1360 :
1361 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L804-L806 */
1362 87 : if( FD_UNLIKELY( !(
1363 87 : text_shdr->sh_addr <= ehdr.e_entry && ehdr.e_entry < fd_ulong_sat_add( text_shdr->sh_addr, text_shdr->sh_size )
1364 87 : ) ) ) {
1365 0 : return FD_SBPF_ELF_ERR_ENTRYPOINT_OUT_OF_BOUNDS;
1366 0 : }
1367 :
1368 : /* Get text section file ranges to calculate the size. */
1369 87 : fd_sbpf_range_t text_section_range;
1370 87 : fd_shdr_get_file_range( text_shdr, &text_section_range );
1371 :
1372 87 : info->text_off = (uint)text_shdr->sh_addr;
1373 87 : info->text_sz = text_section_range.hi-text_section_range.lo;
1374 87 : info->text_cnt = (uint)( info->text_sz/8UL );
1375 87 : info->shndx_text = shndx_text;
1376 :
1377 87 : return FD_SBPF_ELF_SUCCESS;
1378 87 : }
1379 :
1380 : /* First part of Agave's load_with_lenient_parser(). We split up this
1381 : function into two parts so we know how much memory we need to
1382 : allocate for the loading step. Returns an ElfError on failure and 0
1383 : on success.
1384 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L593-L638 */
1385 : static int
1386 : fd_sbpf_elf_peek_lenient( fd_sbpf_elf_info_t * info,
1387 : void const * bin,
1388 : ulong bin_sz,
1389 93 : fd_sbpf_loader_config_t const * config ) {
1390 :
1391 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L607 */
1392 93 : int res = fd_sbpf_lenient_elf_parse( info, bin, bin_sz );
1393 93 : if( FD_UNLIKELY( res<0 ) ) {
1394 3 : return fd_sbpf_elf_parser_err_to_elf_err( res );
1395 3 : }
1396 :
1397 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L617 */
1398 90 : fd_elf64_shdr text_shdr = { 0 };
1399 90 : res = fd_sbpf_lenient_elf_validate( info, bin, bin_sz, &text_shdr );
1400 90 : if( FD_UNLIKELY( res<0 ) ) {
1401 3 : return res;
1402 3 : }
1403 :
1404 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L620-L638 */
1405 87 : {
1406 87 : ulong text_section_vaddr = fd_ulong_sat_add( text_shdr.sh_addr, FD_SBPF_MM_RODATA_ADDR );
1407 87 : ulong vaddr_end = text_section_vaddr;
1408 :
1409 : /* Validate bounds and text section addrs / offsets.
1410 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L632-L638 */
1411 87 : if( FD_UNLIKELY( ( config->reject_broken_elfs && text_shdr.sh_addr!=text_shdr.sh_offset ) ||
1412 87 : vaddr_end>FD_SBPF_MM_STACK_ADDR ) ) {
1413 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1414 0 : }
1415 87 : }
1416 :
1417 : /* Peek (vs load) stops here
1418 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L638 */
1419 :
1420 87 : return FD_SBPF_ELF_SUCCESS;
1421 87 : }
1422 :
1423 : static int
1424 : fd_sbpf_program_get_sbpf_version_or_err( void const * bin,
1425 : ulong bin_sz,
1426 153 : fd_sbpf_loader_config_t const * config ) {
1427 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L376-L381 */
1428 153 : const ulong E_FLAGS_OFFSET = 48UL;
1429 :
1430 153 : if( FD_UNLIKELY( bin_sz<E_FLAGS_OFFSET+sizeof(uint) ) ) {
1431 9 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1432 9 : }
1433 144 : uint e_flags = FD_LOAD( uint, bin+E_FLAGS_OFFSET );
1434 :
1435 144 : uint sbpf_version = 0U;
1436 144 : if( FD_UNLIKELY( config->sbpf_max_version==FD_SBPF_V0 ) ) {
1437 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L384-L388 */
1438 12 : sbpf_version = e_flags==E_FLAGS_SBPF_V2 ? FD_SBPF_RESERVED : FD_SBPF_V0;
1439 132 : } else {
1440 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L390-L396 */
1441 132 : sbpf_version = e_flags < FD_SBPF_VERSION_COUNT ? e_flags : FD_SBPF_RESERVED;
1442 132 : }
1443 :
1444 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L399-L401 */
1445 144 : if( FD_UNLIKELY( !( config->sbpf_min_version <= sbpf_version && sbpf_version <= config->sbpf_max_version ) ) ) {
1446 42 : return FD_SBPF_ELF_ERR_UNSUPPORTED_SBPF_VERSION;
1447 42 : }
1448 :
1449 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L403-L407 */
1450 102 : return (int)sbpf_version;
1451 144 : }
1452 :
1453 : int
1454 : fd_sbpf_elf_peek( fd_sbpf_elf_info_t * info,
1455 : void const * bin,
1456 : ulong bin_sz,
1457 153 : fd_sbpf_loader_config_t const * config ) {
1458 : /* Extract sbpf_version (or error)
1459 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L376-L401 */
1460 153 : int maybe_sbpf_version = fd_sbpf_program_get_sbpf_version_or_err( bin, bin_sz, config );
1461 153 : if( FD_UNLIKELY( maybe_sbpf_version<0 ) ) {
1462 51 : return maybe_sbpf_version;
1463 51 : }
1464 :
1465 : /* Initialize info struct */
1466 102 : *info = (fd_sbpf_elf_info_t) {
1467 102 : .bin_sz = 0U,
1468 102 : .text_off = 0U,
1469 102 : .text_cnt = 0U,
1470 102 : .text_sz = 0UL,
1471 102 : .shndx_text = -1,
1472 102 : .shndx_symtab = -1,
1473 102 : .shndx_strtab = -1,
1474 102 : .shndx_dyn = -1,
1475 102 : .shndx_dynstr = -1,
1476 102 : .shndx_dynsymtab = -1,
1477 102 : .phndx_dyn = -1,
1478 102 : .dt_rel_off = 0UL,
1479 102 : .dt_rel_sz = 0UL,
1480 102 : .sbpf_version = (uint)maybe_sbpf_version,
1481 : /* !!! Keep this in sync with -Werror=missing-field-initializers */
1482 102 : };
1483 :
1484 : /* Invoke strict vs lenient parser. The strict parser is used for
1485 : SBPF version >= 3. The strict parser also returns an ElfParserError
1486 : while the lenient parser returns an ElfError, so we have to map
1487 : the strict parser's error code.
1488 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L403-L407 */
1489 102 : if( FD_UNLIKELY( fd_sbpf_enable_stricter_elf_headers_enabled( info->sbpf_version ) ) ) {
1490 9 : return fd_sbpf_elf_parser_err_to_elf_err( fd_sbpf_elf_peek_strict( info, bin, bin_sz ) );
1491 9 : }
1492 93 : return fd_sbpf_elf_peek_lenient( info, bin, bin_sz, config );
1493 102 : }
1494 :
1495 : /* Parses and concatenates the readonly data sections. This function
1496 : also computes and sets the rodata_sz field inside the SBPF program
1497 : struct.
1498 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L812-L987 */
1499 : static int
1500 : fd_sbpf_parse_ro_sections( fd_sbpf_program_t * prog,
1501 : void const * bin,
1502 : ulong bin_sz,
1503 72 : fd_sbpf_loader_config_t const * config ) {
1504 :
1505 72 : fd_sbpf_elf_t const * elf = (fd_sbpf_elf_t const *)bin;
1506 72 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
1507 72 : fd_elf64_shdr const * section_names_shdr = &shdrs[ elf->ehdr.e_shstrndx ];
1508 72 : uchar * rodata = prog->rodata;
1509 :
1510 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L818-L834 */
1511 72 : ulong lowest_addr = ULONG_MAX; /* Lowest section address */
1512 72 : ulong highest_addr = 0UL; /* Highest section address */
1513 72 : ulong ro_fill_length = 0UL; /* Aggregated section length, excluding gaps between sections */
1514 72 : uchar invalid_offsets = 0; /* Whether the section has invalid offsets */
1515 :
1516 : /* Store the section header indices of ro slices to fill later. */
1517 72 : ulong ro_slices_shidxs[ elf->ehdr.e_shnum ];
1518 72 : ulong ro_slices_cnt = 0UL;
1519 :
1520 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L837-L909 */
1521 741 : for( uint i=0U; i<elf->ehdr.e_shnum; i++ ) {
1522 669 : fd_elf64_shdr const * section_header = &shdrs[ i ];
1523 :
1524 : /* Match the section name.
1525 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L838-L845 */
1526 669 : uchar const * name;
1527 669 : ulong name_len;
1528 669 : 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 ) ) ) {
1529 0 : continue;
1530 0 : }
1531 :
1532 669 : if( FD_UNLIKELY( !fd_sbpf_slice_cstr_eq( name, name_len, ".text" ) &&
1533 669 : !fd_sbpf_slice_cstr_eq( name, name_len, ".rodata" ) &&
1534 669 : !fd_sbpf_slice_cstr_eq( name, name_len, ".data.rel.ro" ) &&
1535 669 : !fd_sbpf_slice_cstr_eq( name, name_len, ".eh_frame" ) ) ) {
1536 510 : continue;
1537 510 : }
1538 :
1539 159 : ulong section_addr = section_header->sh_addr;
1540 :
1541 : /* Handling for the section header offsets. If ELF vaddrs are
1542 : enabled, the section header addresses are allowed to be > the
1543 : section header offsets, as long as address - offset is constant
1544 : across all sections. Otherwise, the section header addresses
1545 : and offsets must match.
1546 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L865-L884 */
1547 159 : if( FD_LIKELY( !invalid_offsets ) ) {
1548 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L866-L880 */
1549 159 : if( FD_UNLIKELY( section_addr!=section_header->sh_offset ) ) {
1550 6 : invalid_offsets = 1;
1551 6 : }
1552 159 : }
1553 :
1554 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L886-L897 */
1555 159 : ulong vaddr_end = section_addr;
1556 159 : if( section_addr<FD_SBPF_MM_RODATA_ADDR ) {
1557 159 : vaddr_end = fd_ulong_sat_add( section_addr, FD_SBPF_MM_RODATA_ADDR );
1558 159 : }
1559 :
1560 159 : if( FD_UNLIKELY( ( config->reject_broken_elfs && invalid_offsets ) ||
1561 159 : vaddr_end>FD_SBPF_MM_STACK_ADDR ) ) {
1562 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1563 0 : }
1564 :
1565 : /* Append the ro slices vector and update the lowest / highest addr
1566 : and ro_fill_length variables. Agave stores three fields in the
1567 : ro slices array that can all be derived from the section header,
1568 : so we just need to store the indices.
1569 :
1570 : The call to fd_shdr_get_file_range() is allowed to fail (Agave's
1571 : unwrap_or_default() call returns a range of 0..0 in this case).
1572 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L899-L908 */
1573 159 : fd_sbpf_range_t section_header_range;
1574 159 : fd_shdr_get_file_range( section_header, §ion_header_range );
1575 159 : if( FD_UNLIKELY( section_header_range.hi>bin_sz ) ) {
1576 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1577 0 : }
1578 159 : ulong section_data_len = section_header_range.hi-section_header_range.lo;
1579 :
1580 159 : lowest_addr = fd_ulong_min( lowest_addr, section_addr );
1581 159 : highest_addr = fd_ulong_max( highest_addr, fd_ulong_sat_add( section_addr, section_data_len ) );
1582 159 : ro_fill_length = fd_ulong_sat_add( ro_fill_length, section_data_len );
1583 159 : ro_slices_shidxs[ ro_slices_cnt++ ] = i;
1584 159 : }
1585 :
1586 : /* This checks that the ro sections are not overlapping. This check
1587 : is incomplete, however, because it does not account for the
1588 : existence of gaps between sections in calculations.
1589 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L910-L913 */
1590 72 : if( FD_UNLIKELY( config->reject_broken_elfs &&
1591 72 : fd_ulong_sat_add( lowest_addr, ro_fill_length )>highest_addr ) ) {
1592 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1593 0 : }
1594 :
1595 : /* Note that optimize_rodata is always false.
1596 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L923-L984 */
1597 72 : {
1598 : /* Readonly / non-readonly sections are mixed, so non-readonly
1599 : sections must be zeroed and the readonly sections must be copied
1600 : at their respective offsets.
1601 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L950-L983 */
1602 72 : lowest_addr = 0UL;
1603 :
1604 : /* Bounds check. */
1605 72 : ulong buf_len = highest_addr;
1606 72 : if( FD_UNLIKELY( buf_len>bin_sz ) ) {
1607 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1608 0 : }
1609 :
1610 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L971-L976 */
1611 72 : uchar ro_section[ buf_len ]; fd_memset( ro_section, 0, buf_len );
1612 231 : for( ulong i=0UL; i<ro_slices_cnt; i++ ) {
1613 159 : ulong sh_idx = ro_slices_shidxs[ i ];
1614 159 : fd_elf64_shdr const * shdr = &shdrs[ sh_idx ];
1615 159 : ulong section_addr = shdr->sh_addr;
1616 :
1617 : /* This was checked above and should never fail. */
1618 159 : fd_sbpf_range_t slice_range;
1619 159 : fd_shdr_get_file_range( shdr, &slice_range );
1620 159 : if( FD_UNLIKELY( slice_range.hi>bin_sz ) ) {
1621 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1622 0 : }
1623 :
1624 159 : ulong buf_offset_start = fd_ulong_sat_sub( section_addr, lowest_addr );
1625 159 : ulong slice_len = slice_range.hi-slice_range.lo;
1626 159 : if( FD_UNLIKELY( slice_len>buf_len ) ) {
1627 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1628 0 : }
1629 :
1630 159 : fd_memcpy( ro_section+buf_offset_start, rodata+slice_range.lo, slice_len );
1631 159 : }
1632 :
1633 : /* Copy the rodata section back in. */
1634 72 : prog->rodata_sz = buf_len;
1635 72 : fd_memcpy( rodata, ro_section, buf_len );
1636 72 : }
1637 :
1638 72 : return FD_SBPF_ELF_SUCCESS;
1639 72 : }
1640 :
1641 : /* Applies ELF relocations in-place. Returns 0 on success and an
1642 : ElfError error code on failure.
1643 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L990-L1331 */
1644 : static int
1645 : fd_sbpf_program_relocate( fd_sbpf_program_t * prog,
1646 : void const * bin,
1647 : ulong bin_sz,
1648 : fd_sbpf_loader_config_t const * config,
1649 72 : fd_sbpf_loader_t * loader ) {
1650 72 : fd_sbpf_elf_info_t const * elf_info = &prog->info;
1651 72 : fd_sbpf_elf_t const * elf = (fd_sbpf_elf_t const *)bin;
1652 72 : uchar * rodata = prog->rodata;
1653 72 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
1654 72 : fd_elf64_shdr const * shtext = &shdrs[ elf_info->shndx_text ];
1655 :
1656 : /* Copy rodata segment */
1657 72 : fd_memcpy( rodata, elf->bin, elf_info->bin_sz );
1658 :
1659 : /* Fixup all program counter relative call instructions
1660 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1005-L1041 */
1661 72 : {
1662 : /* Validate the bytes range of the text section.
1663 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1006-L1008 */
1664 72 : fd_sbpf_range_t text_section_range;
1665 72 : fd_shdr_get_file_range( shtext, &text_section_range );
1666 :
1667 72 : ulong insn_cnt = (text_section_range.hi-text_section_range.lo)/8UL;
1668 72 : if( FD_UNLIKELY( shtext->sh_size+shtext->sh_offset>bin_sz ) ) {
1669 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1670 0 : }
1671 :
1672 72 : uchar * ptr = rodata + shtext->sh_offset;
1673 :
1674 138309 : for( ulong i=0UL; i<insn_cnt; i++, ptr+=8UL ) {
1675 138237 : ulong insn = FD_LOAD( ulong, ptr );
1676 :
1677 : /* Check for call instruction. If immediate is UINT_MAX, assume
1678 : that compiler generated a relocation instead.
1679 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1015 */
1680 138237 : ulong opc = insn & 0xFF;
1681 138237 : int imm = (int)(insn >> 32UL);
1682 138237 : if( (opc!=FD_SBPF_OP_CALL_IMM) || (imm==-1) ) continue;
1683 :
1684 : /* Calculate and check the target PC
1685 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1016-L1021 */
1686 4533 : long target_pc = fd_long_sat_add( fd_long_sat_add( (long)i, 1L ), imm);
1687 4533 : if( FD_UNLIKELY( target_pc<0L || target_pc>=(long)insn_cnt ) ) {
1688 0 : return FD_SBPF_ELF_ERR_RELATIVE_JUMP_OUT_OF_BOUNDS;
1689 0 : }
1690 :
1691 : /* Update the calldests
1692 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1027-L1032 */
1693 4533 : uint pc_hash;
1694 4533 : int err = fd_sbpf_register_function_hashed_legacy( loader, prog, NULL, 0UL, (ulong)target_pc, &pc_hash );
1695 4533 : if( FD_UNLIKELY( err!=FD_SBPF_ELF_SUCCESS ) ) {
1696 0 : return err;
1697 0 : }
1698 :
1699 : /* Store PC hash in text section. Check for writes outside the
1700 : text section.
1701 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1034-L1038 */
1702 4533 : ulong offset = fd_ulong_sat_add( fd_ulong_sat_mul( i, 8UL ), 4UL ); // offset in text section
1703 4533 : if( FD_UNLIKELY( offset+4UL>shtext->sh_size ) ) {
1704 0 : return FD_SBPF_ELF_ERR_VALUE_OUT_OF_BOUNDS;
1705 0 : }
1706 :
1707 4533 : FD_STORE( uint, ptr+4UL, pc_hash );
1708 4533 : }
1709 72 : }
1710 :
1711 : /* Fixup all the relocations in the relocation section if exists. The
1712 : dynamic relocations table was already parsed and validated in
1713 : fd_sbpf_lenient_elf_parse().
1714 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1046-L1304 */
1715 72 : {
1716 72 : fd_elf64_rel const * dt_rels = (fd_elf64_rel const *)( elf->bin + elf_info->dt_rel_off );
1717 72 : uint dt_rel_cnt = elf_info->dt_rel_sz / sizeof(fd_elf64_rel);
1718 :
1719 7566 : for( uint i=0U; i<dt_rel_cnt; i++ ) {
1720 7494 : fd_elf64_rel const * dt_rel = &dt_rels[ i ];
1721 7494 : ulong r_offset = dt_rel->r_offset;
1722 :
1723 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L1068-L1303 */
1724 7494 : int err;
1725 7494 : switch( FD_ELF64_R_TYPE( dt_rel->r_info ) ) {
1726 6 : case FD_ELF_R_BPF_64_64:
1727 6 : err = fd_sbpf_r_bpf_64_64( elf, bin_sz, rodata, elf_info, dt_rel, r_offset );
1728 6 : break;
1729 5568 : case FD_ELF_R_BPF_64_RELATIVE:
1730 5568 : err = fd_sbpf_r_bpf_64_relative(elf, bin_sz, rodata, elf_info, r_offset );
1731 5568 : break;
1732 1920 : case FD_ELF_R_BPF_64_32:
1733 1920 : err = fd_sbpf_r_bpf_64_32( loader, prog, elf, bin_sz, rodata, elf_info, dt_rel, r_offset, config );
1734 1920 : break;
1735 0 : default:
1736 0 : return FD_SBPF_ELF_ERR_UNKNOWN_RELOCATION;
1737 7494 : }
1738 :
1739 7494 : if( FD_UNLIKELY( err!=FD_SBPF_ELF_SUCCESS ) ) {
1740 0 : return err;
1741 0 : }
1742 7494 : }
1743 72 : }
1744 :
1745 : /* ...rest of this function is a no-op because
1746 : enable_symbol_and_section_labels is disabled in production. */
1747 :
1748 72 : return FD_SBPF_ELF_SUCCESS;
1749 72 : }
1750 :
1751 : /* Second part of load_with_lenient_parser().
1752 :
1753 : This function is responsible for "loading" an sBPF program. This
1754 : means...
1755 : 1. Applies any relocations in-place to the rodata section.
1756 : 2. Registers the program entrypoint and other valid calldests.
1757 : 3. Parses and validates the rodata sections, zeroing out any gaps
1758 : between sections.
1759 :
1760 : Returns 0 on success and an ElfError error code on failure.
1761 :
1762 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L640-L689
1763 : */
1764 : static int
1765 : fd_sbpf_program_load_lenient( fd_sbpf_program_t * prog,
1766 : void const * bin,
1767 : ulong bin_sz,
1768 : fd_sbpf_loader_t * loader,
1769 72 : fd_sbpf_loader_config_t const * config ) {
1770 :
1771 : /* Load (vs peek) starts here
1772 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L641 */
1773 :
1774 72 : fd_sbpf_elf_t const * elf = (fd_sbpf_elf_t const *)bin;
1775 72 : fd_sbpf_elf_info_t * elf_info = &prog->info;
1776 72 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
1777 72 : fd_elf64_shdr const * sh_text = &shdrs[ elf_info->shndx_text ];
1778 :
1779 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L642-L647 */
1780 72 : int err = fd_sbpf_program_relocate( prog, bin, bin_sz, config, loader );
1781 72 : if( FD_UNLIKELY( err ) ) return err;
1782 :
1783 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L649-L653 */
1784 72 : ulong offset = fd_ulong_sat_sub( elf->ehdr.e_entry, sh_text->sh_addr );
1785 72 : if( FD_UNLIKELY( offset&0x7UL ) ) { /* offset % 8 != 0 */
1786 0 : return FD_SBPF_ELF_ERR_INVALID_ENTRYPOINT;
1787 0 : }
1788 :
1789 : /* Unregister the entrypoint from the calldests, and register the
1790 : entry_pc. Our behavior slightly diverges from Agave's because we
1791 : rely on an explicit entry_pc field within the elf_info struct
1792 : to handle the b"entrypoint" symbol, and rely on PC hash inverses
1793 : for any other CALL_IMM targets.
1794 :
1795 : Note that even though we won't use the calldests value for the
1796 : entry pc, we still need to "register" it to check for any potential
1797 : symbol collisions and report errors accordingly. We unregister it
1798 : first by setting it to ULONG_MAX.
1799 :
1800 : TODO: Add special casing for static syscalls enabled. For now, it
1801 : is not implemented.
1802 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L654-L667 */
1803 72 : prog->entry_pc = ULONG_MAX;
1804 72 : ulong entry_pc = offset/8UL;
1805 72 : err = fd_sbpf_register_function_hashed_legacy(
1806 72 : loader,
1807 72 : prog,
1808 72 : (uchar const *)"entrypoint",
1809 72 : strlen( "entrypoint" ),
1810 72 : entry_pc,
1811 72 : NULL );
1812 72 : if( FD_UNLIKELY( err!=FD_SBPF_ELF_SUCCESS ) ) {
1813 0 : return err;
1814 0 : }
1815 :
1816 : /* Parse the ro sections.
1817 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L669-L676 */
1818 72 : err = fd_sbpf_parse_ro_sections( prog, bin, bin_sz, config );
1819 72 : if( FD_UNLIKELY( err!=FD_SBPF_ELF_SUCCESS ) ) {
1820 0 : return err;
1821 0 : }
1822 :
1823 72 : return FD_SBPF_ELF_SUCCESS;
1824 72 : }
1825 :
1826 : int
1827 : fd_sbpf_program_load( fd_sbpf_program_t * prog,
1828 : void const * bin,
1829 : ulong bin_sz,
1830 : fd_sbpf_syscalls_t * syscalls,
1831 72 : fd_sbpf_loader_config_t const * config ) {
1832 72 : fd_sbpf_loader_t loader = {
1833 72 : .calldests = prog->calldests,
1834 72 : .syscalls = syscalls,
1835 72 : };
1836 :
1837 : /* Invoke strict vs lenient loader
1838 : Note: info.sbpf_version is already set by fd_sbpf_program_parse()
1839 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L403-L409 */
1840 72 : if( FD_UNLIKELY( fd_sbpf_enable_stricter_elf_headers_enabled( prog->info.sbpf_version ) ) ) {
1841 : /* There is nothing else to do in the strict case except updating
1842 : the prog->rodata_sz field from phdr[ 1 ].p_memsz, and setting
1843 : the entry_pc. */
1844 0 : fd_elf64_ehdr ehdr = FD_LOAD( fd_elf64_ehdr, bin );
1845 0 : fd_elf64_phdr phdr_0 = FD_LOAD( fd_elf64_phdr, bin+sizeof(fd_elf64_ehdr) );
1846 0 : fd_elf64_phdr phdr_1 = FD_LOAD( fd_elf64_phdr, bin+sizeof(fd_elf64_ehdr)+sizeof(fd_elf64_phdr) );
1847 0 : prog->rodata_sz = phdr_1.p_memsz;
1848 0 : prog->entry_pc = ( ehdr.e_entry-phdr_0.p_vaddr )/8UL;
1849 0 : return FD_SBPF_ELF_SUCCESS;
1850 0 : }
1851 72 : int res = fd_sbpf_program_load_lenient( prog, bin, bin_sz, &loader, config );
1852 72 : if( FD_UNLIKELY( res!=FD_SBPF_ELF_SUCCESS ) ) {
1853 0 : return res;
1854 0 : }
1855 :
1856 72 : return FD_SBPF_ELF_SUCCESS;
1857 72 : }
1858 :
1859 : #undef ERR
1860 : #undef FAIL
1861 : #undef REQUIRE
|