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