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