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 : /* Error handling *****************************************************/
12 :
13 : /* Thread local storage last error value */
14 :
15 : static FD_TL int ldr_errno = 0;
16 : static FD_TL int ldr_err_srcln = -1;
17 : #define FD_SBPF_ERRBUF_SZ (128UL)
18 : static FD_TL char fd_sbpf_errbuf[ FD_SBPF_ERRBUF_SZ ] = {0};
19 :
20 : /* fd_sbpf_loader_seterr remembers the error ID and line number of the
21 : current file at which the last error occurred. */
22 :
23 : __attribute__((cold,noinline)) static int
24 : fd_sbpf_loader_seterr( int err,
25 72 : int srcln ) {
26 72 : ldr_errno = err;
27 72 : ldr_err_srcln = srcln;
28 72 : return err;
29 72 : }
30 :
31 : /* Macros for returning an error from the current function while also
32 : remembering the error code. */
33 :
34 6 : #define ERR( err ) return fd_sbpf_loader_seterr( (err), __LINE__ )
35 6 : #define FAIL() ERR( FD_SBPF_ERR_INVALID_ELF )
36 44532 : #define REQUIRE(x) do { if ( FD_UNLIKELY( !(x) ) ) FAIL(); } while (0)
37 :
38 : char const *
39 9 : fd_sbpf_strerror( void ) {
40 9 : if( FD_UNLIKELY( ldr_errno==0 ) )
41 9 : strcpy( fd_sbpf_errbuf, "ok" );
42 0 : else
43 9 : snprintf( fd_sbpf_errbuf, FD_SBPF_ERRBUF_SZ,
44 9 : "code %d at %s(%d)", ldr_errno, __FILE__, ldr_err_srcln );
45 9 : return fd_sbpf_errbuf;
46 9 : }
47 :
48 : /* ELF loader, part 1 **************************************************
49 :
50 : Start with a static piece of scratch memory and do basic validation
51 : of the file content. Walk the section table once and remember
52 : sections of interest.
53 :
54 : ### Terminology
55 :
56 : This source follows common ELF naming practices.
57 :
58 : section: a named data region present in the ELF file
59 : segment: a contiguous memory region containing sections
60 : (not necessarily contiguous in the ELF file)
61 :
62 : physical address (paddr): Byte offset into ELF file (uchar * bin)
63 : virtual address (vaddr): VM memory address */
64 :
65 : /* Provide convenient access to file header and ELF content */
66 :
67 : __extension__ union fd_sbpf_elf {
68 : fd_elf64_ehdr ehdr;
69 : uchar bin[0];
70 : };
71 : typedef union fd_sbpf_elf fd_sbpf_elf_t;
72 :
73 : /* FD_SBPF_MM_{...}_ADDR are hardcoded virtual addresses of segments
74 : in the sBPF virtual machine.
75 :
76 : FIXME: These should be defined elsewhere */
77 :
78 9 : #define FD_SBPF_MM_BYTECODE_ADDR (0x0UL) /* bytecode */
79 9 : #define FD_SBPF_MM_RODATA_ADDR (0x100000000UL) /* readonly program data */
80 9423 : #define FD_SBPF_MM_PROGRAM_ADDR (0x100000000UL) /* readonly program data */
81 9 : #define FD_SBPF_MM_STACK_ADDR (0x200000000UL) /* stack */
82 9 : #define FD_SBPF_MM_HEAP_ADDR (0x300000000UL) /* heap */
83 36 : #define FD_SBPF_MM_REGION_SZ (0x100000000UL) /* max region size */
84 :
85 9 : #define FD_SBPF_PF_X (1U) /* executable */
86 54 : #define FD_SBPF_PF_W (2U) /* writable */
87 27 : #define FD_SBPF_PF_R (4U) /* readable */
88 18 : #define FD_SBPF_PF_RW (FD_SBPF_PF_R|FD_SBPF_PF_W)
89 :
90 54 : #define EXPECTED_PHDR_CNT (4U)
91 :
92 : /* _fd_int_store_if_negative stores x to *p if *p is negative (branchless) */
93 :
94 : static inline int
95 : _fd_int_store_if_negative( int * p,
96 168 : int x ) {
97 168 : return (*p = fd_int_if( (*p)<0, x, *p ));
98 168 : }
99 :
100 : /* fd_sbpf_check_ehdr verifies the ELF file header. */
101 :
102 : static int
103 : fd_sbpf_check_ehdr( fd_elf64_ehdr const * ehdr,
104 : ulong elf_sz,
105 : uint min_version,
106 87 : uint max_version ) {
107 :
108 : /* Validate ELF magic */
109 87 : REQUIRE( ( fd_uint_load_4( ehdr->e_ident )==0x464c457fU )
110 : /* Validate file type/target identification
111 : Solana/Agave performs header checks across two places:
112 : - Elf64::parse https://github.com/solana-labs/rbpf/blob/v0.8.0/src/elf_parser/mod.rs#L108
113 : - Executable::validate https://github.com/solana-labs/rbpf/blob/v0.8.0/src/elf.rs#L518
114 : These two sections are executed in close proximity, with no modifications to the header in between.
115 : We can therefore consolidate the checks in one place.
116 : */
117 87 : & ( ehdr->e_ident[ FD_ELF_EI_CLASS ]==FD_ELF_CLASS_64 )
118 87 : & ( ehdr->e_ident[ FD_ELF_EI_DATA ]==FD_ELF_DATA_LE )
119 87 : & ( ehdr->e_ident[ FD_ELF_EI_VERSION ]==1 )
120 87 : & ( ehdr->e_ident[ FD_ELF_EI_OSABI ]==FD_ELF_OSABI_NONE )
121 87 : & ( ehdr->e_type ==FD_ELF_ET_DYN )
122 87 : & ( ( ehdr->e_machine ==FD_ELF_EM_BPF )
123 87 : | ( ehdr->e_machine ==FD_ELF_EM_SBPF ) )
124 87 : & ( ehdr->e_version ==1 )
125 : /* Coherence checks */
126 87 : & ( ehdr->e_ehsize ==sizeof(fd_elf64_ehdr) )
127 87 : & ( ehdr->e_phentsize==sizeof(fd_elf64_phdr) )
128 87 : & ( ehdr->e_shentsize==sizeof(fd_elf64_shdr) )
129 87 : & ( ehdr->e_shstrndx < ehdr->e_shnum )
130 87 : & ( ehdr->e_flags >= min_version )
131 87 : & ( max_version
132 87 : ? ( ehdr->e_flags <= max_version )
133 87 : : ( ehdr->e_flags != FD_ELF_EF_SBPF_V2 )
134 87 : )
135 87 : );
136 :
137 : /* Bounds check program header table */
138 :
139 87 : ulong const phoff = ehdr->e_phoff;
140 87 : ulong const phnum = ehdr->e_phnum;
141 87 : REQUIRE( ( fd_ulong_is_aligned( phoff, 8UL ) )
142 87 : & ( phoff<=elf_sz ) ); /* out of bounds */
143 :
144 87 : REQUIRE( phnum<=(ULONG_MAX/sizeof(fd_elf64_phdr)) ); /* overflow */
145 87 : ulong const phsz = phnum*sizeof(fd_elf64_phdr);
146 :
147 87 : ulong const phoff_end = phoff+phsz;
148 87 : REQUIRE( ( phoff_end>=phoff ) /* overflow */
149 87 : & ( phoff_end<=elf_sz ) /* out of bounds */
150 87 : & ( (phoff_end==0UL) /* overlaps file header */
151 87 : | (phoff>=sizeof(fd_elf64_ehdr)) ) );
152 :
153 : /* Bounds check section header table */
154 :
155 87 : ulong const shoff = ehdr->e_shoff;
156 87 : ulong const shnum = ehdr->e_shnum;
157 87 : REQUIRE( ( fd_ulong_is_aligned( shoff, 8UL ) )
158 87 : & ( shoff>=sizeof(fd_elf64_ehdr) ) /* overlaps file header */
159 87 : & ( shoff< elf_sz ) /* out of bounds */
160 87 : & ( shnum> 0UL ) ); /* not enough sections */
161 :
162 87 : REQUIRE( shoff<=(ULONG_MAX/sizeof(fd_elf64_shdr)) ); /* overflow */
163 87 : ulong const shsz = shnum*sizeof(fd_elf64_shdr);
164 :
165 87 : ulong const shoff_end = shoff+shsz;
166 87 : REQUIRE( ( shoff_end>=shoff ) /* overflow */
167 87 : & ( shoff_end<=elf_sz ) ); /* out of bounds */
168 :
169 : /* Overlap checks */
170 :
171 87 : REQUIRE( (phoff>=shoff_end) | (shoff>=phoff_end) ); /* overlap shdrs<>phdrs */
172 :
173 87 : return 0;
174 87 : }
175 :
176 : /* shdr_get_loaded_size returns the loaded size of a section, i.e. the
177 : number of bytes loaded into the rodata segment. sBPF ELFs grossly
178 : misuse the sh_size parameter. When SHT_NOBITS is set, the actual
179 : section size is zero, and the section size is ignored. */
180 :
181 : static ulong
182 5925 : shdr_get_loaded_size( fd_elf64_shdr const * shdr ) {
183 5925 : return fd_ulong_if( shdr->sh_type==FD_ELF_SHT_NOBITS, 0UL, shdr->sh_size );
184 5925 : }
185 :
186 : /* check_cstr verifies a string in a string table. Returns non-NULL if
187 : the string is null terminated and contains at most max non-NULL
188 : characters. Returns NULL if the off is out of bounds, or if the max
189 : or EOF are reached before the null terminator. */
190 :
191 : static char const *
192 : check_cstr( uchar const * bin,
193 : ulong bin_sz,
194 : ulong off,
195 : ulong max,
196 2730 : ulong * opt_sz ) {
197 2730 : if( FD_UNLIKELY( off>=bin_sz ) ) return NULL;
198 2730 : max += 1UL; /* include NULL terminator */
199 2730 : max = fd_ulong_min( max, bin_sz-off ); /* truncate to available size */
200 2730 : char const * cstr = (char const *)( bin+off );
201 2730 : ulong len = strnlen( cstr, max );
202 2730 : if( opt_sz ) *opt_sz = len;
203 2730 : return len<max ? cstr : NULL;
204 2730 : }
205 :
206 : /* fd_sbpf_load_phdrs walks the program header table. Remembers info
207 : along the way, and performs various validations.
208 :
209 : Assumes that ...
210 : - table does not overlap with file header or section header table and
211 : is within bounds
212 : - offset of program header table is 8 byte aligned */
213 :
214 : static int
215 : fd_sbpf_load_phdrs( fd_sbpf_elf_info_t * info,
216 : fd_sbpf_elf_t const * elf,
217 87 : ulong elf_sz ) {
218 :
219 87 : ulong const pht_offset = elf->ehdr.e_phoff;
220 87 : ulong const pht_cnt = elf->ehdr.e_phnum;
221 :
222 : /* Virtual address of last seen program header */
223 87 : ulong p_load_vaddr = 0UL;
224 :
225 : /* Read program header table */
226 87 : fd_elf64_phdr const * phdr = (fd_elf64_phdr const *)( elf->bin + pht_offset );
227 408 : for( ulong i=0; i<pht_cnt; i++ ) {
228 321 : switch( phdr[i].p_type ) {
229 84 : case FD_ELF_PT_DYNAMIC:
230 : /* Remember first PT_DYNAMIC segment */
231 84 : _fd_int_store_if_negative( &info->phndx_dyn, (int)i );
232 84 : break;
233 237 : case FD_ELF_PT_LOAD:
234 : /* LOAD segments must be ordered */
235 237 : REQUIRE( phdr[ i ].p_vaddr >= p_load_vaddr );
236 237 : p_load_vaddr = phdr[ i ].p_vaddr;
237 : /* Segment must be within bounds */
238 237 : REQUIRE( ( phdr[ i ].p_offset + phdr[ i ].p_filesz >= phdr[ i ].p_offset )
239 237 : & ( phdr[ i ].p_offset + phdr[ i ].p_filesz <= elf_sz ) );
240 : /* No overlap checks */
241 237 : break;
242 237 : default:
243 : /* Ignore other segment types */
244 0 : break;
245 321 : }
246 321 : }
247 :
248 87 : return 0;
249 87 : }
250 :
251 : /* FD_SBPF_SECTION_NAME_SZ_MAX is the maximum length of a symbol name cstr
252 : including zero terminator.
253 : https://github.com/solana-labs/rbpf/blob/c168a8715da668a71584ea46696d85f25c8918f6/src/elf_parser/mod.rs#L12 */
254 1617 : #define FD_SBPF_SECTION_NAME_SZ_MAX (16UL)
255 :
256 : /* fd_sbpf_load_shdrs walks the section header table. Remembers info
257 : along the way, and performs various validations.
258 :
259 : Assumes that ...
260 : - table does not overlap with file header or program header table and
261 : is within bounds
262 : - offset of section header table is 8 byte aligned
263 : - section header table has at least one entry */
264 :
265 : static int
266 : fd_sbpf_load_shdrs( fd_sbpf_elf_info_t * info,
267 : fd_sbpf_elf_t const * elf,
268 : ulong elf_sz,
269 87 : int elf_deploy_checks ) {
270 :
271 : /* File Header */
272 87 : ulong const eh_offset = 0UL;
273 87 : ulong const eh_offend = sizeof(fd_elf64_ehdr);
274 :
275 : /* Section Header Table */
276 87 : ulong const sht_offset = elf->ehdr.e_shoff;
277 87 : ulong const sht_cnt = elf->ehdr.e_shnum;
278 87 : ulong const sht_sz = sht_cnt*sizeof(fd_elf64_shdr);
279 87 : ulong const sht_offend = sht_offset + sht_sz;
280 :
281 87 : fd_elf64_shdr const * shdr = (fd_elf64_shdr const *)( elf->bin + sht_offset );
282 :
283 : /* Program Header Table */
284 87 : ulong const pht_offset = elf->ehdr.e_phoff;
285 87 : ulong const pht_cnt = elf->ehdr.e_phnum;
286 87 : ulong const pht_offend = pht_offset + (pht_cnt*sizeof(fd_elf64_phdr));
287 :
288 : /* Overlap checks */
289 87 : REQUIRE( (sht_offset>=eh_offend ) | (sht_offend<=eh_offset ) ); /* overlaps ELF file header */
290 87 : REQUIRE( (sht_offset>=pht_offend) | (sht_offend<=pht_offset) ); /* overlaps program header table */
291 :
292 : /* Require SHT_STRTAB for section name table */
293 :
294 87 : REQUIRE( elf->ehdr.e_shstrndx < sht_cnt ); /* out of bounds */
295 87 : REQUIRE( shdr[ elf->ehdr.e_shstrndx ].sh_type==FD_ELF_SHT_STRTAB );
296 :
297 87 : ulong shstr_off = shdr[ elf->ehdr.e_shstrndx ].sh_offset;
298 87 : ulong shstr_sz = shdr[ elf->ehdr.e_shstrndx ].sh_size;
299 87 : REQUIRE( shstr_off<elf_sz );
300 87 : shstr_sz = fd_ulong_min( shstr_sz, elf_sz-shstr_off );
301 :
302 : /* Clear the "loaded sections" bitmap */
303 :
304 87 : fd_memset( info->loaded_sections, 0, sizeof(info->loaded_sections) );
305 :
306 : /* Validate section header table.
307 : Check that all sections are in bounds, ordered, and don't overlap. */
308 :
309 87 : ulong min_sh_offset = 0UL; /* lowest permitted section offset */
310 :
311 : /* Keep track of the physical (file address) end of all relevant
312 : sections to determine rodata_sz */
313 87 : ulong psegment_end = 0UL; /* Upper bound of physical (file) addressing */
314 :
315 : /* While validating section header table, also figure out size
316 : of the rodata segment. This is the minimal virtual address range
317 : that spans all sections. */
318 87 : ulong vsegment_start = FD_SBPF_MM_PROGRAM_ADDR; /* Lower bound of segment virtual address */
319 87 : ulong vsegment_end = 0UL; /* Upper bound of segment virtual address */
320 :
321 87 : ulong tot_section_sz = 0UL; /* Size of all sections */
322 87 : ulong lowest_addr = 0UL;
323 87 : ulong highest_addr = 0UL;
324 :
325 891 : for( ulong i=0UL; i<sht_cnt; i++ ) {
326 810 : uint sh_type = shdr[ i ].sh_type;
327 810 : uint sh_name = shdr[ i ].sh_name;
328 810 : ulong sh_addr = shdr[ i ].sh_addr;
329 810 : ulong sh_offset = shdr[ i ].sh_offset;
330 810 : ulong sh_size = shdr[ i ].sh_size;
331 810 : ulong sh_offend = sh_offset + sh_size;
332 :
333 : /* First section must be SHT_NULL */
334 810 : REQUIRE( i>0UL || sh_type==FD_ELF_SHT_NULL );
335 :
336 : /* check that physical range has no overflow and is within bounds */
337 810 : REQUIRE( sh_offend >= sh_offset );
338 810 : REQUIRE( sh_offend <= elf_sz ); // https://github.com/solana-labs/rbpf/blob/v0.8.0/src/elf_parser/mod.rs#L180
339 :
340 810 : if( sh_type!=FD_ELF_SHT_NOBITS ) {
341 : /* Overlap checks */
342 807 : REQUIRE( (sh_offset>=eh_offend ) | (sh_offend<=eh_offset ) ); /* overlaps ELF file header */
343 807 : REQUIRE( (sh_offset>=pht_offend) | (sh_offend<=pht_offset) ); /* overlaps program header table */
344 807 : REQUIRE( (sh_offset>=sht_offend) | (sh_offend<=sht_offset) ); /* overlaps section header table */
345 :
346 : /* Ordering and overlap check
347 : https://github.com/solana-labs/rbpf/blob/v0.8.0/src/elf_parser/mod.rs#L177
348 : */
349 807 : REQUIRE( sh_offset >= min_sh_offset );
350 807 : min_sh_offset = sh_offend;
351 807 : }
352 :
353 810 : if( sh_type==FD_ELF_SHT_DYNAMIC ) {
354 : /* Remember first SHT_DYNAMIC segment */
355 84 : _fd_int_store_if_negative( &info->shndx_dyn, (int)i );
356 84 : }
357 :
358 810 : ulong name_off = shstr_off + (ulong)sh_name;
359 810 : REQUIRE( ( name_off<elf_sz ) /* out of bounds */
360 810 : & ( sh_name <shstr_sz ) );
361 :
362 : /* Create name cstr */
363 :
364 810 : char const * name_ptr = check_cstr( elf->bin + shstr_off, shstr_sz, sh_name, FD_SBPF_SECTION_NAME_SZ_MAX-1UL, NULL );
365 810 : REQUIRE( name_ptr );
366 807 : char __attribute__((aligned(16UL))) name[ FD_SBPF_SECTION_NAME_SZ_MAX ] = {0};
367 807 : strncpy( name, name_ptr, FD_SBPF_SECTION_NAME_SZ_MAX-1UL );
368 :
369 : /* Check name */
370 : /* TODO switch table for this? */
371 : /* TODO reject duplicate sections */
372 :
373 : /* https://github.com/firedancer-io/sbpf/blob/sbpf-v0.11.1-patches/src/elf.rs#L855 */
374 807 : if( FD_LIKELY( strncmp( name, ".text", sizeof(".text") )==0 ||
375 807 : strncmp( name, ".rodata", sizeof(".rodata") )==0 ||
376 807 : strncmp( name, ".data.rel.ro", sizeof(".data.rel.ro") )==0 ||
377 807 : strncmp( name, ".eh_frame", sizeof(".eh_frame") )==0 ) ) {
378 210 : lowest_addr = fd_ulong_min( lowest_addr, sh_addr );
379 210 : highest_addr = fd_ulong_max( highest_addr, fd_ulong_sat_add( sh_addr, sh_size ) );
380 210 : }
381 :
382 807 : int load = 0; /* should section be loaded? */
383 :
384 807 : /**/ if( 0==memcmp( name, ".text", 6UL /* equals */ ) ) {
385 87 : REQUIRE( (info->shndx_text)<0 ); /* check for duplicate */
386 87 : info->shndx_text = (int)i;
387 87 : load = 1;
388 87 : }
389 720 : else if( (0==memcmp( name, ".rodata", 8UL /* equals */ ) )
390 720 : | (0==memcmp( name, ".data.rel.ro", 13UL /* equals */ ) )
391 720 : | (0==memcmp( name, ".eh_frame", 10UL /* equals */ ) ) ) {
392 123 : load = 1;
393 123 : }
394 597 : else if( 0==memcmp( name, ".symtab", 8UL /* equals */ ) ) {
395 33 : REQUIRE( (info->shndx_symtab)<0 );
396 33 : info->shndx_symtab = (int)i;
397 33 : }
398 564 : else if( 0==memcmp( name, ".strtab", 8UL /* equals */ ) ) {
399 33 : REQUIRE( (info->shndx_strtab)<0 );
400 33 : info->shndx_strtab = (int)i;
401 33 : }
402 531 : else if( 0==memcmp( name, ".dynstr", 8UL /* equals */ ) ) {
403 81 : REQUIRE( (info->shndx_dynstr)<0 );
404 81 : info->shndx_dynstr = (int)i;
405 81 : }
406 450 : else if( 0==memcmp( name, ".bss", 4UL /* has prefix */ ) ) {
407 0 : FAIL();
408 0 : }
409 450 : else if( 0==memcmp( name, ".data.rel", 9UL /* has prefix */ ) ) {} /* ignore */
410 450 : else if( (0==memcmp( name, ".data", 5UL /* has prefix */ ) )
411 450 : & ( ( shdr[ i ].sh_flags & (FD_ELF_SHF_ALLOC|FD_ELF_SHF_WRITE) )
412 450 : ==(FD_ELF_SHF_ALLOC|FD_ELF_SHF_WRITE) ) ) {
413 0 : FAIL();
414 0 : }
415 450 : else {} /* ignore */
416 : /* else ignore */
417 :
418 807 : if( load ) {
419 : /* Remember that section should be loaded */
420 :
421 210 : info->loaded_sections[ i>>6UL ] |= (1UL)<<(i&63UL);
422 :
423 : /* Check that virtual address range is in MM_PROGRAM bounds */
424 :
425 210 : ulong sh_actual_size = shdr_get_loaded_size( &shdr[ i ] );
426 210 : ulong sh_virtual_end = sh_addr + sh_actual_size;
427 :
428 : /* https://github.com/solana-labs/rbpf/blob/v0.8.0/src/elf.rs#L426 */
429 210 : if ( FD_UNLIKELY( elf_deploy_checks ) ){
430 57 : REQUIRE( sh_addr == sh_offset );
431 57 : }
432 207 : REQUIRE( sh_addr < FD_SBPF_MM_PROGRAM_ADDR ); /* overflow check */
433 207 : REQUIRE( sh_actual_size < FD_SBPF_MM_PROGRAM_ADDR ); /* overflow check */
434 207 : REQUIRE( sh_virtual_end <= FD_SBPF_MM_STACK_ADDR-FD_SBPF_MM_PROGRAM_ADDR ); /* check overlap with stack */
435 :
436 : /* Check that physical address range is in bounds
437 : (Seems redundant?) */
438 207 : ulong paddr_end = sh_offset + sh_actual_size;
439 207 : REQUIRE( paddr_end >= sh_offset );
440 207 : REQUIRE( paddr_end <= elf_sz );
441 :
442 207 : vsegment_start = fd_ulong_min( vsegment_start, sh_addr );
443 : /* Expand range to fit section */
444 207 : psegment_end = fd_ulong_max( psegment_end, paddr_end );
445 207 : vsegment_end = fd_ulong_max( vsegment_end, sh_virtual_end );
446 :
447 : /* Coherence check sum of section sizes */
448 207 : REQUIRE( tot_section_sz + sh_actual_size >= tot_section_sz ); /* overflow check */
449 207 : tot_section_sz += sh_actual_size;
450 207 : }
451 807 : }
452 :
453 : /* https://github.com/firedancer-io/sbpf/blob/sbpf-v0.11.1-patches/src/elf.rs#L982 */
454 81 : REQUIRE( fd_ulong_sat_sub( highest_addr, lowest_addr ) <= elf_sz ); /* addr out of bounds */
455 :
456 : /* More coherence checks */
457 81 : REQUIRE( psegment_end <= elf_sz ); // https://github.com/solana-labs/rbpf/blob/v0.8.0/src/elf.rs#L782
458 :
459 :
460 : /* Check that the rodata segment is within bounds
461 : https://github.com/solana-labs/rbpf/blob/v0.8.0/src/elf.rs#L725 */
462 81 : if ( FD_UNLIKELY( elf_deploy_checks ) ){
463 18 : REQUIRE( fd_ulong_sat_add( vsegment_start, tot_section_sz) <= vsegment_end );
464 18 : }
465 :
466 : /* Require .text section */
467 :
468 81 : REQUIRE( (info->shndx_text)>=0 );
469 81 : fd_elf64_shdr const * shdr_text = &shdr[ info->shndx_text ];
470 81 : REQUIRE( (shdr_text->sh_addr <= elf->ehdr.e_entry)
471 : /* check that entrypoint is in text VM range */
472 81 : & (elf->ehdr.e_entry < fd_ulong_sat_add( shdr_text->sh_addr, shdr_text->sh_size ) ) );
473 : /* NOTE: Does NOT check that the entrypoint is in text section file
474 : range (which may be 0 sz if SHT_NOBITS). This check is
475 : separately done in the sBPF verifier. */
476 :
477 81 : info->text_off = (uint)shdr_text->sh_offset;
478 81 : ulong text_size = shdr_get_loaded_size( shdr_text );
479 81 : info->text_sz = text_size;
480 81 : info->text_cnt = (uint) text_size / 8U;
481 :
482 :
483 : /* Convert entrypoint offset to program counter */
484 :
485 81 : info->rodata_sz = (uint)psegment_end;
486 81 : info->rodata_footprint = (uint)elf_sz;
487 :
488 81 : ulong entry_off = fd_ulong_sat_sub( elf->ehdr.e_entry, shdr_text->sh_addr );
489 81 : ulong entry_pc = entry_off / 8UL;
490 :
491 : /* Follows https://github.com/solana-labs/rbpf/blob/v0.8.0/src/elf.rs#L443 */
492 81 : REQUIRE( fd_ulong_is_aligned( entry_off, 8UL ) );
493 81 : REQUIRE( entry_pc < ( info->rodata_sz / 8UL ) );
494 81 : info->entry_pc = (uint)entry_pc;
495 :
496 81 : if( (info->shndx_dynstr)>=0 ) {
497 81 : fd_elf64_shdr const * shdr_dynstr = &shdr[ info->shndx_dynstr ];
498 81 : ulong sh_offset = shdr_dynstr->sh_offset;
499 81 : ulong sh_size = shdr_dynstr->sh_size;
500 81 : REQUIRE( (sh_offset+sh_size>=sh_offset) & (sh_offset+sh_size<=info->rodata_footprint) );
501 81 : info->dynstr_off = (uint)sh_offset;
502 81 : info->dynstr_sz = (uint)sh_size;
503 81 : }
504 :
505 81 : return 0;
506 81 : }
507 :
508 : fd_sbpf_elf_info_t *
509 : fd_sbpf_elf_peek_old( fd_sbpf_elf_info_t * info,
510 : void const * bin,
511 : ulong elf_sz,
512 : int elf_deploy_checks,
513 : uint sbpf_min_version,
514 87 : uint sbpf_max_version ) {
515 :
516 : /* Reject overlong ELFs (using uint addressing internally).
517 : This is well beyond Solana's max account size of 10 MB. */
518 87 : if( FD_UNLIKELY( elf_sz>UINT_MAX ) )
519 0 : return NULL;
520 :
521 87 : fd_sbpf_elf_t const * elf = (fd_sbpf_elf_t const *)bin;
522 87 : int err;
523 :
524 : /* Validate file header */
525 87 : if( FD_UNLIKELY( (err=fd_sbpf_check_ehdr( &elf->ehdr, elf_sz, sbpf_min_version, sbpf_max_version ))!=0 ) )
526 0 : return NULL;
527 :
528 : /* Program headers */
529 87 : if( FD_UNLIKELY( (err=fd_sbpf_load_phdrs( info, elf, elf_sz ))!=0 ) )
530 0 : return NULL;
531 :
532 : /* Section headers */
533 87 : if( FD_UNLIKELY( (err=fd_sbpf_load_shdrs( info, elf, elf_sz, elf_deploy_checks ))!=0 ) )
534 6 : return NULL;
535 :
536 : /* Set SBPF version from ELF e_flags */
537 81 : info->sbpf_version = sbpf_max_version ? elf->ehdr.e_flags : 0UL;
538 :
539 81 : return info;
540 87 : }
541 :
542 : /* ELF loader, part 2 **************************************************
543 :
544 : Prepare a copy of a subrange of the ELF content: The rodata segment.
545 : Mangle the copy by applying dynamic relocations. Then, zero out
546 : parts of the segment that are not interesting to the loader.
547 :
548 : ### Terminology
549 :
550 : Shorthands for relocation handling:
551 :
552 : S: Symbol value (typically an ELF physical address)
553 : A: Implicit addend, i.e. the original value of the field that the
554 : relocation handler is about to write to
555 : V: Virtual address, i.e. the target value that the relocation
556 : handler is about to write into where the implicit addend was
557 : previously stored */
558 :
559 : ulong
560 66 : fd_sbpf_program_align( void ) {
561 66 : return alignof( fd_sbpf_program_t );
562 66 : }
563 :
564 : ulong
565 66 : fd_sbpf_program_footprint( fd_sbpf_elf_info_t const * info ) {
566 66 : FD_COMPILER_UNPREDICTABLE( info ); /* Make this appear as FD_FN_PURE (e.g. footprint might depened on info contents in future) */
567 66 : if( FD_UNLIKELY( fd_sbpf_enable_stricter_elf_headers( info->sbpf_version ) ) ) {
568 : /* SBPF v3+ no longer neeeds calldests bitmap */
569 0 : return FD_LAYOUT_FINI( FD_LAYOUT_APPEND( FD_LAYOUT_INIT,
570 0 : alignof(fd_sbpf_program_t), sizeof(fd_sbpf_program_t) ),
571 0 : alignof(fd_sbpf_program_t) );
572 0 : }
573 66 : return FD_LAYOUT_FINI( FD_LAYOUT_APPEND( FD_LAYOUT_APPEND( FD_LAYOUT_INIT,
574 66 : alignof(fd_sbpf_program_t), sizeof(fd_sbpf_program_t) ),
575 66 : fd_sbpf_calldests_align(), fd_sbpf_calldests_footprint( info->rodata_sz / 8UL ) ), /* calldests bitmap */
576 66 : alignof(fd_sbpf_program_t) );
577 66 : }
578 :
579 : fd_sbpf_program_t *
580 : fd_sbpf_program_new( void * prog_mem,
581 : fd_sbpf_elf_info_t const * elf_info,
582 66 : void * rodata ) {
583 :
584 66 : if( FD_UNLIKELY( !prog_mem ) ) {
585 0 : FD_LOG_WARNING(( "NULL prog_mem" ));
586 0 : return NULL;
587 0 : }
588 :
589 66 : if( FD_UNLIKELY( !elf_info ) ) {
590 0 : FD_LOG_WARNING(( "NULL elf_info" ));
591 0 : return NULL;
592 0 : }
593 :
594 66 : if( FD_UNLIKELY( ((elf_info->rodata_footprint)>0U) & (!rodata)) ) {
595 0 : FD_LOG_WARNING(( "NULL rodata" ));
596 0 : return NULL;
597 0 : }
598 :
599 : /* https://github.com/solana-labs/rbpf/blob/v0.8.0/src/elf_parser/mod.rs#L99 */
600 66 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong) rodata, FD_SBPF_PROG_RODATA_ALIGN ) ) ){
601 0 : FD_LOG_WARNING(( "rodata is not 8-byte aligned" ));
602 0 : return NULL;
603 0 : }
604 :
605 : /* Initialize program struct */
606 :
607 66 : FD_SCRATCH_ALLOC_INIT( laddr, prog_mem );
608 66 : fd_sbpf_program_t * prog = FD_SCRATCH_ALLOC_APPEND( laddr, alignof(fd_sbpf_program_t), sizeof(fd_sbpf_program_t) );
609 :
610 0 : *prog = (fd_sbpf_program_t) {
611 66 : .info = *elf_info,
612 66 : .rodata = rodata,
613 66 : .rodata_sz = elf_info->rodata_sz,
614 66 : .text = (ulong *)((ulong)rodata + elf_info->text_off), /* FIXME: WHAT IF MISALIGNED */
615 66 : .text_off = elf_info->text_off,
616 66 : .text_cnt = elf_info->text_cnt,
617 66 : .text_sz = elf_info->text_sz,
618 66 : .entry_pc = elf_info->entry_pc
619 66 : };
620 :
621 66 : if( FD_UNLIKELY( fd_sbpf_enable_stricter_elf_headers( elf_info->sbpf_version ) ) ) {
622 : /* No calldests map in SBPF v3+ */
623 0 : prog->calldests_shmem = NULL;
624 0 : prog->calldests = NULL;
625 66 : } else {
626 : /* Initialize calldests map */
627 66 : ulong pc_max = elf_info->rodata_sz / 8UL;
628 66 : prog->calldests_shmem = fd_sbpf_calldests_new(
629 66 : FD_SCRATCH_ALLOC_APPEND( laddr, fd_sbpf_calldests_align(),
630 66 : fd_sbpf_calldests_footprint( pc_max ) ),
631 0 : pc_max );
632 0 : prog->calldests = fd_sbpf_calldests_join( prog->calldests_shmem );
633 66 : }
634 :
635 66 : return prog;
636 66 : }
637 :
638 : void *
639 30 : fd_sbpf_program_delete( fd_sbpf_program_t * mem ) {
640 :
641 30 : fd_sbpf_calldests_delete( fd_sbpf_calldests_leave( mem->calldests ) );
642 30 : fd_memset( mem, 0, sizeof(fd_sbpf_program_t) );
643 :
644 30 : return (void *)mem;
645 30 : }
646 :
647 : /* fd_sbpf_loader_t contains various temporary state during loading */
648 :
649 : struct fd_sbpf_loader {
650 : /* External objects */
651 : ulong * calldests; /* owned by program */
652 : fd_sbpf_syscalls_t * syscalls; /* owned by caller */
653 :
654 : /* Dynamic table */
655 : uint dyn_off; /* File offset of dynamic table (UINT_MAX=missing) */
656 : uint dyn_cnt; /* Number of dynamic table entries */
657 :
658 : /* Dynamic table entries */
659 : ulong dt_rel;
660 : ulong dt_relent;
661 : ulong dt_relsz;
662 : ulong dt_symtab;
663 :
664 : /* Dynamic symbols */
665 : uint dynsym_off; /* File offset of .dynsym section (0=missing) */
666 : uint dynsym_cnt; /* Symbol count */
667 :
668 : int elf_deploy_checks;
669 : };
670 : typedef struct fd_sbpf_loader fd_sbpf_loader_t;
671 :
672 : /* FD_SBPF_SYM_NAME_SZ_MAX is the maximum length of a symbol name cstr
673 : including zero terminator.
674 : https://github.com/solana-labs/rbpf/blob/c168a8715da668a71584ea46696d85f25c8918f6/src/elf_parser/mod.rs#L13 */
675 1920 : #define FD_SBPF_SYM_NAME_SZ_MAX (64UL)
676 :
677 :
678 : static int
679 : fd_sbpf_find_dynamic( fd_sbpf_loader_t * loader,
680 : fd_sbpf_elf_t const * elf,
681 : ulong elf_sz,
682 66 : fd_sbpf_elf_info_t const * info ) {
683 :
684 66 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
685 66 : fd_elf64_phdr const * phdrs = (fd_elf64_phdr const *)( elf->bin + elf->ehdr.e_phoff );
686 :
687 : /* Try first PT_DYNAMIC in program header table */
688 :
689 66 : if( (info->phndx_dyn)>=0 ) {
690 66 : ulong dyn_off = phdrs[ info->phndx_dyn ].p_offset;
691 66 : ulong dyn_sz = phdrs[ info->phndx_dyn ].p_filesz;
692 66 : ulong dyn_end = dyn_off+dyn_sz;
693 :
694 : /* Fall through to SHT_DYNAMIC if invalid */
695 :
696 66 : if( FD_LIKELY( ( dyn_end>=dyn_off ) /* overflow */
697 66 : & ( dyn_end<=elf_sz ) /* out of bounds */
698 66 : & fd_ulong_is_aligned( dyn_off, 8UL ) /* misaligned */
699 66 : & fd_ulong_is_aligned( dyn_sz, sizeof(fd_elf64_dyn) ) /* misaligned sz */ ) ) {
700 66 : loader->dyn_off = (uint)dyn_off;
701 66 : loader->dyn_cnt = (uint)(dyn_sz / sizeof(fd_elf64_dyn));
702 66 : return 0;
703 66 : }
704 66 : }
705 :
706 : /* Try first SHT_DYNAMIC in section header table */
707 :
708 0 : if( (info->shndx_dyn)>0 ) {
709 0 : ulong dyn_off = shdrs[ info->shndx_dyn ].sh_offset;
710 0 : ulong dyn_sz = shdrs[ info->shndx_dyn ].sh_size;
711 0 : ulong dyn_end = dyn_off+dyn_sz;
712 :
713 : /* This time, don't tolerate errors */
714 :
715 0 : REQUIRE( ( dyn_end>=dyn_off ) /* overflow */
716 0 : & ( dyn_end<=elf_sz ) /* out of bounds */
717 0 : & fd_ulong_is_aligned( dyn_off, 8UL ) /* misaligned */
718 0 : & fd_ulong_is_aligned( dyn_sz, sizeof(fd_elf64_dyn) ) /* misaligned sz */ );
719 :
720 0 : loader->dyn_off = (uint)dyn_off;
721 0 : loader->dyn_cnt = (uint)(dyn_sz / sizeof(fd_elf64_dyn));
722 0 : return 0;
723 0 : }
724 :
725 : /* Missing or invalid PT_DYNAMIC and missing SHT_DYNAMIC, skip. */
726 0 : return 0;
727 0 : }
728 :
729 : static int
730 : fd_sbpf_load_dynamic( fd_sbpf_loader_t * loader,
731 : fd_sbpf_elf_t const * elf,
732 66 : ulong elf_sz ) {
733 :
734 66 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
735 :
736 : /* Skip if no dynamic table was found */
737 :
738 66 : if( !loader->dyn_cnt ) return 0;
739 :
740 : /* Walk dynamic table */
741 :
742 66 : fd_elf64_dyn const * dyn = (fd_elf64_dyn const *)( elf->bin + loader->dyn_off );
743 66 : ulong const dyn_cnt = loader->dyn_cnt;
744 :
745 681 : for( ulong i=0; i<dyn_cnt; i++ ) {
746 681 : if( FD_UNLIKELY( dyn[i].d_tag==FD_ELF_DT_NULL ) ) break;
747 :
748 615 : ulong d_val = dyn[i].d_un.d_val;
749 615 : switch( dyn[i].d_tag ) {
750 51 : case FD_ELF_DT_REL: loader->dt_rel =d_val; break;
751 51 : case FD_ELF_DT_RELENT: loader->dt_relent=d_val; break;
752 51 : case FD_ELF_DT_RELSZ: loader->dt_relsz =d_val; break;
753 66 : case FD_ELF_DT_SYMTAB: loader->dt_symtab=d_val; break;
754 615 : }
755 615 : }
756 :
757 : /* Load dynamic symbol table */
758 :
759 66 : if( loader->dt_symtab ) {
760 : /* Search for dynamic symbol table
761 : FIXME unfortunate bounded O(n^2) -- could convert to binary search */
762 :
763 : /* FIXME this could be clobbered by relocations, causing strict
764 : aliasing violations */
765 :
766 66 : fd_elf64_shdr const * shdr_dynsym = NULL;
767 :
768 336 : for( ulong i=0; i<elf->ehdr.e_shnum; i++ ) {
769 336 : if( shdrs[ i ].sh_addr == loader->dt_symtab ) {
770 : /* TODO: verify this ... */
771 : /* Check section type */
772 66 : uint sh_type = shdrs[ i ].sh_type;
773 : // https://github.com/solana-labs/rbpf/blob/v0.8.5/src/elf_parser/mod.rs#L500
774 66 : REQUIRE( (sh_type==FD_ELF_SHT_SYMTAB) | (sh_type==FD_ELF_SHT_DYNSYM) );
775 :
776 66 : shdr_dynsym = &shdrs[ i ];
777 66 : break;
778 66 : }
779 336 : }
780 66 : REQUIRE( shdr_dynsym );
781 :
782 : /* Check if out of bounds or misaligned */
783 :
784 66 : ulong sh_offset = shdr_dynsym->sh_offset;
785 66 : ulong sh_size = shdr_dynsym->sh_size;
786 :
787 66 : REQUIRE( ( sh_offset+sh_size>=sh_offset )
788 66 : & ( sh_offset+sh_size<=elf_sz )
789 66 : & ( fd_ulong_is_aligned( sh_offset, 8UL ) )
790 66 : & ( sh_size % sizeof (fd_elf64_sym) == 0UL ) );
791 :
792 66 : loader->dynsym_off = (uint)sh_offset;
793 66 : loader->dynsym_cnt = (uint)(sh_size/sizeof(fd_elf64_sym));
794 66 : }
795 :
796 66 : return 0;
797 66 : }
798 :
799 : /* ELF Dynamic Relocations *********************************************
800 :
801 : ### Summary
802 :
803 : The sBPF ELF loader provides a limited dynamic relocation mechanism
804 : to fix up Clang-generated shared objects for execution in an sBPF VM.
805 :
806 : The relocation types themselves violate the eBPF and ELF specs in
807 : various ways. In short, the relocation table (via DT_REL) is used to
808 : shift program code from zero-based addressing to the MM_PROGRAM
809 : segment in the VM memory map (at 0x1_0000_0000).
810 :
811 : As part of the Solana VM protocol it abides by strict determinism
812 : requirements. This sadly means that we will have to replicate all
813 : edge cases and bugs in the Solana Labs ELF loader.
814 :
815 : Three relocation types are currently supported:
816 : - R_BPF_64_64: Sets an absolute address of a symbol as the
817 : 64-bit immediate field of an lddw instruction
818 : - R_BPF_64_RELATIVE: Adds MM_PROGRAM_START (0x1_0000_0000) to ...
819 : a) ... the 64-bit imm field of an lddw instruction (if in text)
820 : b) ... a 64-bit integer (if not in text section)
821 : - R_BPF_64_32: Sets the 32-bit immediate field of a call
822 : instruction to ...
823 : a) the ID of a local function (Murmur3 hash of function PC address)
824 : b) the ID of a syscall
825 :
826 : Obviously invalid relocations (e.g. out-of-bounds of ELF file or
827 : unsupported reloc type) raise an error.
828 : Relocations that would corrupt ELF data structures are silently
829 : ignored (using the fd_sbpf_reloc_mask mechanism).
830 :
831 : ### History
832 :
833 : The use of relocations is technically redundant, as the Solana VM
834 : memory map has been hardcoded in program runtime v1 (so far the only
835 : runtime). However, virtually all deployed programs as of April 2023
836 : are position-independent shared objects and make heavy use of such
837 : relocations.
838 :
839 : Relocations in the Solana VM have a complicated history. Over the
840 : course of years, multiple protocol bugs have been added and fixed.
841 : The ELF loader needs to handle all these edge cases to avoid breaking
842 : "userspace". I.e. any deployed programs which might be immutable
843 : must continue to function.
844 :
845 : While this complex logic will probably stick around for the next few
846 : years, the Solana protocol is getting increasingly restrictive for
847 : newly deployed ELFs. Another proposed change is upgrading to
848 : position-dependent binaries without any dynamic relocations. */
849 :
850 : /* R_BPF_64_64 relocates an absolute address into the extended imm field
851 : of an lddw-form instruction. (Two instruction slots, low 32 bits in
852 : first immediate field, high 32 bits in second immediate field)
853 :
854 : Bits 0..32 32..64 64..96 96..128
855 : [ ... ] [ IMM_LO ] [ ... ] [ IMM_HI ] */
856 :
857 : static int
858 : fd_sbpf_r_bpf_64_64( fd_sbpf_loader_t const * loader,
859 : fd_sbpf_elf_t const * elf,
860 : ulong elf_sz,
861 : uchar * rodata,
862 : fd_sbpf_elf_info_t const * info,
863 6 : fd_elf64_rel const * rel ) {
864 :
865 6 : (void)info;
866 :
867 6 : uint r_sym = FD_ELF64_R_SYM( rel->r_info );
868 6 : ulong r_offset = rel->r_offset;
869 :
870 : /* Bounds check */
871 6 : REQUIRE( ( r_offset+16UL> r_offset )
872 6 : & ( r_offset+16UL<=elf_sz ) );
873 :
874 : /* Offsets of implicit addend (immediate fields) */
875 6 : ulong A_off_lo = r_offset+ 4UL;
876 6 : ulong A_off_hi = r_offset+12UL;
877 :
878 : /* Read implicit addend (imm field of first insn slot) */
879 : // SBF_V2: ulong A_off = is_text ? r_offset+4UL : r_offset;
880 6 : REQUIRE( A_off_lo+4UL<elf_sz );
881 :
882 : /* Lookup symbol */
883 6 : REQUIRE( r_sym < loader->dynsym_cnt );
884 6 : fd_elf64_sym const * dynsyms = (fd_elf64_sym const *)( elf->bin + loader->dynsym_off );
885 6 : fd_elf64_sym const * sym = &dynsyms[ r_sym ];
886 6 : ulong S = sym->st_value;
887 :
888 : /* Relocate */
889 6 : ulong A = FD_LOAD( uint, &rodata[ A_off_lo ] );
890 6 : ulong V = fd_ulong_sat_add( S, A );
891 6 : if( V<FD_SBPF_MM_PROGRAM_ADDR ) V+=FD_SBPF_MM_PROGRAM_ADDR;
892 :
893 : /* Write back */
894 6 : FD_STORE( uint, &rodata[ A_off_lo ], (uint)(V ) );
895 6 : FD_STORE( uint, &rodata[ A_off_hi ], (uint)(V>>32UL) );
896 :
897 6 : return 0;
898 6 : }
899 :
900 : /* R_BPF_64_RELATIVE is almost entirely Solana specific. */
901 :
902 : static int
903 : fd_sbpf_r_bpf_64_relative( fd_sbpf_elf_t const * elf,
904 : ulong elf_sz,
905 : uchar * rodata,
906 : fd_sbpf_elf_info_t const * info,
907 5568 : fd_elf64_rel const * rel ) {
908 :
909 5568 : ulong r_offset = rel->r_offset;
910 :
911 : /* Is reloc target in .text section? */
912 5568 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
913 5568 : fd_elf64_shdr const * shdr_text = &shdrs[ info->shndx_text ];
914 5568 : int is_text = ( ( r_offset >= shdr_text->sh_offset ) &
915 5568 : ( r_offset < shdr_text->sh_offset +
916 5568 : shdr_get_loaded_size( shdr_text ) ) );
917 :
918 5568 : if( is_text ) {
919 : /* If reloc target is in .text, behave like R_BPF_64_64, except:
920 : - R_SYM(r_info) is ignored
921 : - If implicit addend looks like a physical address, make it
922 : a virtual address (by adding a constant offset)
923 :
924 : This relocation type seems to make little sense but is required
925 : for most programs. */
926 :
927 3756 : REQUIRE( (r_offset+16UL>r_offset) & (r_offset+16UL<=elf_sz) );
928 3756 : ulong imm_lo_off = r_offset+ 4UL;
929 3756 : ulong imm_hi_off = r_offset+12UL;
930 :
931 : /* Read implicit addend */
932 3756 : uint va_lo = FD_LOAD( uint, rodata+imm_lo_off );
933 3756 : uint va_hi = FD_LOAD( uint, rodata+imm_hi_off );
934 3756 : ulong va = ( (ulong)va_hi<<32UL ) | va_lo;
935 :
936 3756 : REQUIRE( va!=0UL );
937 3756 : va = va<FD_SBPF_MM_PROGRAM_ADDR ? va+FD_SBPF_MM_PROGRAM_ADDR : va;
938 :
939 : /* Write back
940 : Skip bounds check as .text is guaranteed to be writable */
941 3756 : FD_STORE( uint, rodata+imm_lo_off, (uint)( va ) );
942 3756 : FD_STORE( uint, rodata+imm_hi_off, (uint)( va>>32UL ) );
943 3756 : } else {
944 : /* Outside .text do a 64-bit write */
945 :
946 : /* Bounds checks */
947 1812 : REQUIRE( (r_offset+8UL>r_offset) & (r_offset+8UL<=elf_sz) );
948 :
949 : /* Read implicit addend */
950 1812 : ulong va = FD_LOAD( uint, rodata+r_offset+4UL );
951 :
952 : /* Relocate */
953 1812 : va = fd_ulong_sat_add( va, FD_SBPF_MM_PROGRAM_ADDR );
954 :
955 : /* Write back */
956 1812 : FD_STORE( ulong, rodata+r_offset, va );
957 1812 : }
958 :
959 5568 : return 0;
960 5568 : }
961 :
962 : static int
963 : fd_sbpf_r_bpf_64_32( fd_sbpf_loader_t const * loader,
964 : fd_sbpf_elf_t const * elf,
965 : ulong elf_sz,
966 : uchar * rodata,
967 : fd_sbpf_elf_info_t const * info,
968 1920 : fd_elf64_rel const * rel ) {
969 :
970 1920 : uint r_sym = FD_ELF64_R_SYM( rel->r_info );
971 1920 : ulong r_offset = rel->r_offset;
972 :
973 : /* Lookup symbol */
974 1920 : REQUIRE( r_sym < loader->dynsym_cnt );
975 1920 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
976 1920 : fd_elf64_sym const * dynsyms = (fd_elf64_sym const *)( elf->bin + loader->dynsym_off );
977 1920 : fd_elf64_sym const * sym = &dynsyms[ r_sym ];
978 1920 : ulong S = sym->st_value;
979 :
980 : /* Verify .dynstr (TODO can we lift this out of the reloc handler?) */
981 1920 : REQUIRE( info->shndx_dynstr > 0 );
982 1920 : REQUIRE( shdrs[ info->shndx_dynstr ].sh_type == FD_ELF_SHT_STRTAB );
983 :
984 : /* Verify symbol name */
985 1920 : ulong name_len;
986 1920 : char const * name = check_cstr( elf->bin + info->dynstr_off, info->dynstr_sz, sym->st_name, FD_SBPF_SYM_NAME_SZ_MAX-1UL, &name_len );
987 1920 : REQUIRE( name );
988 :
989 : /* Value to write into relocated field */
990 1920 : uint V;
991 :
992 1920 : int is_func_call = ( FD_ELF64_ST_TYPE( sym->st_info ) == FD_ELF_STT_FUNC )
993 1920 : & ( S!=0UL );
994 1920 : if( is_func_call ) {
995 : /* Check whether function call is in virtual memory range of text section. */
996 138 : fd_elf64_shdr const * shdr_text = &shdrs[ info->shndx_text ];
997 138 : ulong sh_addr = shdr_text->sh_addr;
998 138 : ulong sh_size = shdr_text->sh_size;
999 138 : REQUIRE( (S>=sh_addr) & (S<sh_addr+sh_size) );
1000 :
1001 : /* Note: The above check is broken, as sh_size is interpreted as 0
1002 : for SHT_NOBITS section. Yet, this is the "correct" loading
1003 : behavior according to protocol rules. */
1004 :
1005 : /* Register function call */
1006 138 : ulong target_pc = (S-sh_addr) / 8UL;
1007 :
1008 : /* TODO bounds check the target? */
1009 :
1010 : /* Register new entry */
1011 138 : uint hash;
1012 138 : if( name_len >= 10UL && 0==strncmp( name, "entrypoint", name_len ) ) {
1013 : /* Skip insertion of "entrypoint" relocation entries to calldests. This
1014 : emulates Solana/Agave's behavior of unregistering these entries before
1015 : registering the entrypoint manually.
1016 : Entrypoint is registered in fd_sbpf_program_load.
1017 : Hash is still applied. */
1018 9 : hash = 0x71e3cf81;
1019 129 : } else {
1020 129 : hash = fd_pchash( (uint)target_pc );
1021 129 : if( FD_LIKELY( target_pc < (info->rodata_sz / 8UL ) ) )
1022 129 : fd_sbpf_calldests_insert( loader->calldests, target_pc );
1023 129 : }
1024 :
1025 : /* Check for collision with syscall ID
1026 : https://github.com/solana-labs/rbpf/blob/57139e9e1fca4f01155f7d99bc55cdcc25b0bc04/src/program.rs#L142-L146 */
1027 138 : REQUIRE( !fd_sbpf_syscalls_query( loader->syscalls, (ulong)hash, NULL ) );
1028 138 : V = (uint)hash;
1029 1782 : } else {
1030 : /* FIXME Should cache Murmur hashes.
1031 : If max ELF size is 10MB, can fit about 640k relocs.
1032 : Each reloc could point to a symbol with the same st_name,
1033 : which results in 640MB hash input data without caching. */
1034 1782 : uint hash = fd_murmur3_32( name, name_len, 0UL );
1035 : /* Ensure that requested syscall ID exists only when deploying
1036 : https://github.com/solana-labs/rbpf/blob/v0.8.0/src/elf.rs#L1097 */
1037 1782 : if ( FD_UNLIKELY( loader->elf_deploy_checks ) ) {
1038 66 : REQUIRE( fd_sbpf_syscalls_query( loader->syscalls, (ulong)hash, NULL ) );
1039 66 : }
1040 :
1041 1782 : V = hash;
1042 1782 : }
1043 :
1044 : /* Bounds checks */
1045 1920 : REQUIRE( (r_offset+8UL>r_offset) & (r_offset+8UL<=elf_sz) );
1046 1920 : ulong A_off = r_offset+4UL;
1047 :
1048 : /* Apply relocation */
1049 1920 : FD_STORE( uint, rodata+A_off, V );
1050 :
1051 1920 : return 0;
1052 1920 : }
1053 :
1054 : static int
1055 : fd_sbpf_apply_reloc( fd_sbpf_loader_t const * loader,
1056 : fd_sbpf_elf_t const * elf,
1057 : ulong elf_sz,
1058 : uchar * rodata,
1059 : fd_sbpf_elf_info_t const * info,
1060 7494 : fd_elf64_rel const * rel ) {
1061 7494 : switch( FD_ELF64_R_TYPE( rel->r_info ) ) {
1062 6 : case FD_ELF_R_BPF_64_64:
1063 6 : return fd_sbpf_r_bpf_64_64 ( loader, elf, elf_sz, rodata, info, rel );
1064 5568 : case FD_ELF_R_BPF_64_RELATIVE:
1065 5568 : return fd_sbpf_r_bpf_64_relative( elf, elf_sz, rodata, info, rel );
1066 1920 : case FD_ELF_R_BPF_64_32:
1067 1920 : return fd_sbpf_r_bpf_64_32 ( loader, elf, elf_sz, rodata, info, rel );
1068 0 : default:
1069 0 : ERR( FD_SBPF_ERR_INVALID_ELF );
1070 7494 : }
1071 7494 : }
1072 :
1073 : /* fd_sbpf_hash_calls converts local call instructions in the "LLVM
1074 : form" (immediate is a program counter offset) to eBPF form (immediate
1075 : is a hash of the target program counter). Corresponds to
1076 : fixup_relative calls in solana-labs/rbpf.
1077 :
1078 : Assumes that the text section range exists, is within bounds, and
1079 : does not overlap with the ELF file header, program header table, or
1080 : section header table. */
1081 :
1082 : static int
1083 : fd_sbpf_hash_calls( fd_sbpf_loader_t * loader,
1084 : fd_sbpf_program_t * prog,
1085 66 : fd_sbpf_elf_t const * elf ) {
1086 :
1087 66 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
1088 66 : fd_sbpf_elf_info_t * info = &prog->info;
1089 66 : uchar * rodata = prog->rodata;
1090 :
1091 66 : fd_elf64_shdr const * shtext = &shdrs[ info->shndx_text ];
1092 66 : fd_sbpf_calldests_t * calldests = loader->calldests;
1093 :
1094 66 : uchar * ptr = rodata + shtext->sh_offset;
1095 66 : ulong insn_cnt = shdr_get_loaded_size( shtext ) / 8UL;
1096 :
1097 138303 : for( ulong i=0; i<insn_cnt; i++, ptr+=8UL ) {
1098 138237 : ulong insn = *((ulong *) ptr);
1099 :
1100 : /* Check for call instruction. If immediate is UINT_MAX, assume
1101 : that compiler generated a relocation instead. */
1102 138237 : ulong opc = insn & 0xFF;
1103 138237 : int imm = (int)(insn >> 32UL);
1104 138237 : if( (opc!=0x85) | (imm==-1) )
1105 133704 : continue;
1106 :
1107 : /* Mark function call destination */
1108 4533 : long target_pc_s;
1109 4533 : REQUIRE( 0==__builtin_saddl_overflow( (long)i+1L, imm, &target_pc_s ) );
1110 4533 : ulong target_pc = (ulong)target_pc_s;
1111 4533 : REQUIRE( target_pc<insn_cnt ); /* bounds check target */
1112 :
1113 4533 : fd_sbpf_calldests_insert( calldests, target_pc );
1114 :
1115 : /* Replace immediate with hash */
1116 4533 : uint pc_hash = fd_pchash( (uint)target_pc );
1117 : /* Check for collision with syscall ID
1118 : https://github.com/solana-labs/rbpf/blob/57139e9e1fca4f01155f7d99bc55cdcc25b0bc04/src/program.rs#L142-L146 */
1119 4533 : REQUIRE( !fd_sbpf_syscalls_query( loader->syscalls, (ulong)pc_hash, NULL ) );
1120 :
1121 4533 : FD_STORE( uint, ptr+4UL, pc_hash );
1122 4533 : }
1123 :
1124 66 : return 0;
1125 66 : }
1126 :
1127 : static int
1128 : fd_sbpf_relocate( fd_sbpf_loader_t const * loader,
1129 : fd_sbpf_elf_t const * elf,
1130 : ulong elf_sz,
1131 : uchar * rodata,
1132 66 : fd_sbpf_elf_info_t const * info ) {
1133 :
1134 66 : ulong const dt_rel = loader->dt_rel;
1135 66 : ulong const dt_relent = loader->dt_relent;
1136 66 : ulong const dt_relsz = loader->dt_relsz;
1137 :
1138 : /* Skip relocation if DT_REL is missing */
1139 :
1140 66 : if( dt_rel == 0UL ) return 0;
1141 :
1142 : /* Validate reloc table params */
1143 :
1144 51 : REQUIRE( dt_relent==sizeof(fd_elf64_rel) );
1145 51 : REQUIRE( dt_relsz !=0UL );
1146 51 : REQUIRE( (dt_relsz % sizeof(fd_elf64_rel))==0UL );
1147 :
1148 : /* Resolve DT_REL virtual address to file offset
1149 : First, attempt to find segment containing DT_REL */
1150 :
1151 51 : ulong rel_off = ULONG_MAX;
1152 :
1153 51 : fd_elf64_phdr const * phdrs = (fd_elf64_phdr const *)( elf->bin + elf->ehdr.e_phoff );
1154 51 : ulong rel_phnum;
1155 144 : for( rel_phnum=0; rel_phnum < elf->ehdr.e_phnum; rel_phnum++ ) {
1156 144 : ulong va_lo = phdrs[ rel_phnum ].p_vaddr;
1157 144 : ulong va_hi = phdrs[ rel_phnum ].p_memsz + va_lo;
1158 144 : REQUIRE( va_hi>=va_lo );
1159 144 : if( (dt_rel>=va_lo) & (dt_rel<va_hi) ) {
1160 : /* Found */
1161 51 : ulong va_off = dt_rel - va_lo;
1162 51 : ulong pa_lo = phdrs[ rel_phnum ].p_offset + va_off;
1163 : /* Overflow checks */
1164 51 : REQUIRE( (va_off<=dt_rel)
1165 51 : & (pa_lo >=va_off)
1166 51 : & (pa_lo < elf_sz) );
1167 51 : rel_off = pa_lo;
1168 51 : break;
1169 51 : }
1170 144 : }
1171 :
1172 : /* DT_REL not contained in any segment. Fallback to section header
1173 : table for finding first dynamic reloc section. */
1174 :
1175 51 : if( rel_phnum == elf->ehdr.e_phnum ) {
1176 0 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
1177 0 : ulong rel_shnum;
1178 0 : for( rel_shnum=0; rel_shnum < elf->ehdr.e_shnum; rel_shnum++ )
1179 0 : if( shdrs[ rel_shnum ].sh_addr==dt_rel )
1180 0 : break;
1181 0 : REQUIRE( rel_shnum < elf->ehdr.e_shnum );
1182 0 : rel_off = shdrs[ rel_shnum ].sh_offset;
1183 0 : }
1184 :
1185 51 : REQUIRE( fd_ulong_is_aligned( rel_off, 8UL ) );
1186 51 : REQUIRE( (rel_off < elf_sz)
1187 51 : & (dt_relsz <= elf_sz)
1188 51 : & ((rel_off+dt_relsz) <= elf_sz) );
1189 :
1190 : /* Load section and reloc tables
1191 : Assume section header already validated at this point */
1192 :
1193 51 : fd_elf64_rel const * rel = (fd_elf64_rel const *)( elf->bin + rel_off );
1194 51 : ulong rel_cnt = dt_relsz/sizeof(fd_elf64_rel);
1195 :
1196 : /* Apply each reloc */
1197 :
1198 7545 : for( ulong i=0; i<rel_cnt; i++ ) {
1199 7494 : int res = fd_sbpf_apply_reloc( loader, elf, elf_sz, rodata, info, &rel[ i ] );
1200 7494 : if( res!=0 ) return res;
1201 7494 : }
1202 :
1203 51 : return 0;
1204 51 : }
1205 :
1206 : static int
1207 : fd_sbpf_zero_rodata( fd_sbpf_elf_t * elf,
1208 : uchar * rodata,
1209 66 : fd_sbpf_elf_info_t const * info ) {
1210 :
1211 66 : fd_elf64_shdr const * shdrs = (fd_elf64_shdr const *)( elf->bin + elf->ehdr.e_shoff );
1212 :
1213 : /* memset gaps between sections to zero.
1214 : Assume section sh_addrs are monotonically increasing.
1215 : Assume section virtual address ranges equal physical address ranges.
1216 : Assume ranges are not overflowing. */
1217 : /* FIXME match Solana more closely here */
1218 :
1219 66 : ulong cursor = 0UL;
1220 717 : for( ulong i=0; i<elf->ehdr.e_shnum; i++ ) {
1221 651 : if( !( info->loaded_sections[ i>>6UL ] & (1UL<<(i&63UL)) ) ) continue;
1222 :
1223 153 : fd_elf64_shdr const * shdr = &shdrs[ i ];
1224 :
1225 : /* NOBITS sections are included in rodata, but may have invalid
1226 : offsets, thus we can't trust the shdr->sh_offset field. */
1227 153 : if( FD_UNLIKELY( shdr->sh_type==FD_ELF_SHT_NOBITS ) ) continue;
1228 :
1229 153 : ulong off = shdr->sh_offset;
1230 153 : ulong sz = shdr->sh_size;
1231 153 : assert( cursor<=off ); /* Invariant: Monotonically increasing offsets */
1232 153 : assert( off+sz>=off ); /* Invariant: No integer overflow */
1233 153 : assert( off+sz<=info->rodata_sz ); /* Invariant: No buffer overflow */
1234 :
1235 : /* Fill gap with zeros */
1236 153 : ulong gap = off - cursor;
1237 153 : fd_memset( rodata+cursor, 0, gap );
1238 :
1239 153 : cursor = off+sz;
1240 153 : }
1241 :
1242 66 : fd_memset( rodata+cursor, 0, info->rodata_sz - cursor );
1243 :
1244 66 : return 0;
1245 66 : }
1246 :
1247 : int
1248 : fd_sbpf_program_load_old( fd_sbpf_program_t * prog,
1249 : void const * _bin,
1250 : ulong elf_sz,
1251 : fd_sbpf_syscalls_t * syscalls,
1252 66 : int elf_deploy_checks ) {
1253 66 : fd_sbpf_loader_seterr( 0, 0 );
1254 :
1255 66 : int err;
1256 66 : fd_sbpf_elf_t * elf = (fd_sbpf_elf_t *)_bin;
1257 :
1258 66 : fd_sbpf_loader_t loader = {
1259 66 : .calldests = prog->calldests,
1260 66 : .syscalls = syscalls,
1261 :
1262 66 : .dyn_off = 0U,
1263 66 : .dyn_cnt = 0U,
1264 :
1265 66 : .dt_rel = 0UL,
1266 66 : .dt_relent = 0UL,
1267 66 : .dt_relsz = 0UL,
1268 66 : .dt_symtab = 0UL,
1269 :
1270 66 : .dynsym_off = 0U,
1271 66 : .dynsym_cnt = 0U,
1272 66 : .elf_deploy_checks = elf_deploy_checks
1273 66 : };
1274 :
1275 : /* Find dynamic section */
1276 66 : if( FD_UNLIKELY( (err=fd_sbpf_find_dynamic( &loader, elf, elf_sz, &prog->info ))!=0 ) )
1277 0 : return err;
1278 :
1279 : /* Load dynamic section */
1280 66 : if( FD_UNLIKELY( (err=fd_sbpf_load_dynamic( &loader, elf, elf_sz ))!=0 ) )
1281 0 : return err;
1282 :
1283 : /* Register entrypoint to calldests. */
1284 66 : fd_sbpf_calldests_insert( prog->calldests, prog->entry_pc );
1285 :
1286 : /* Copy rodata segment */
1287 66 : fd_memcpy( prog->rodata, elf->bin, prog->info.rodata_footprint );
1288 :
1289 : /* Convert calls with PC relative immediate to hashes */
1290 66 : if( FD_UNLIKELY( (err=fd_sbpf_hash_calls ( &loader, prog, elf ))!=0 ) )
1291 0 : return err;
1292 :
1293 : /* Apply relocations */
1294 66 : if( FD_UNLIKELY( (err=fd_sbpf_relocate ( &loader, elf, elf_sz, prog->rodata, &prog->info ))!=0 ) )
1295 0 : return err;
1296 :
1297 : /* Create read-only segment */
1298 66 : if( FD_UNLIKELY( (err=fd_sbpf_zero_rodata( elf, prog->rodata, &prog->info ))!=0 ) )
1299 0 : return err;
1300 :
1301 66 : return 0;
1302 66 : }
1303 :
1304 : int
1305 : fd_sbpf_program_get_sbpf_version_or_err( void const * bin,
1306 : ulong bin_sz,
1307 147 : fd_sbpf_loader_config_t const * config ) {
1308 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L376-L381 */
1309 147 : const ulong E_FLAGS_OFFSET = 48UL;
1310 147 : const uint E_FLAGS_SBPF_V2 = 0x20;
1311 :
1312 147 : if( FD_UNLIKELY( bin_sz < E_FLAGS_OFFSET+sizeof(uint) ) ) {
1313 9 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
1314 9 : }
1315 138 : uint e_flags = fd_uint_load_4( (uchar const *)bin + E_FLAGS_OFFSET );
1316 :
1317 138 : uint sbpf_version = 0U;
1318 138 : if( FD_UNLIKELY( config->sbpf_max_version==FD_SBPF_V0 ) ) {
1319 : /* https://github.com/anza-xyz/sbpf/blob/main/src/elf.rs#L384-L388 */
1320 12 : sbpf_version = e_flags==E_FLAGS_SBPF_V2 ? FD_SBPF_RESERVED : FD_SBPF_V0;
1321 126 : } else {
1322 : /* https://github.com/anza-xyz/sbpf/blob/main/src/elf.rs#L390-L396 */
1323 126 : sbpf_version = e_flags < FD_SBPF_VERSION_COUNT ? e_flags : FD_SBPF_RESERVED;
1324 126 : }
1325 :
1326 : /* https://github.com/anza-xyz/sbpf/blob/main/src/elf.rs#L399-L401 */
1327 138 : if( FD_UNLIKELY( !( config->sbpf_min_version <= sbpf_version && sbpf_version <= config->sbpf_max_version ) ) ) {
1328 42 : return FD_SBPF_ELF_ERR_UNSUPPORTED_SBPF_VERSION;
1329 42 : }
1330 :
1331 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L403-L407 */
1332 96 : return (int)sbpf_version;
1333 138 : }
1334 :
1335 : int
1336 : fd_sbpf_elf_peek_strict( fd_sbpf_elf_info_t * info,
1337 : void const * bin,
1338 : ulong bin_sz,
1339 9 : fd_sbpf_loader_config_t const * config ) {
1340 9 : (void)config;
1341 :
1342 : /* Parse file header */
1343 :
1344 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L425
1345 : https://github.com/anza-xyz/sbpf/blob/main/src/elf_parser/mod.rs#L278
1346 : (Agave does some extra checks on alignment, but they don't seem necessary) */
1347 9 : if( FD_UNLIKELY( bin_sz<sizeof(fd_elf64_ehdr) ) ) {
1348 0 : return FD_SBPF_ELF_PARSER_ERR_OUT_OF_BOUNDS;
1349 0 : }
1350 :
1351 9 : fd_elf64_ehdr ehdr = FD_LOAD( fd_elf64_ehdr, bin );
1352 :
1353 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L430-L453 */
1354 9 : ulong program_header_table_end = sizeof(fd_elf64_ehdr) + ehdr.e_phnum*sizeof(fd_elf64_phdr);
1355 :
1356 9 : int parse_ehdr_err =
1357 9 : ( fd_uint_load_4( ehdr.e_ident ) != FD_ELF_MAG_LE )
1358 9 : | ( ehdr.e_ident[ FD_ELF_EI_CLASS ] != FD_ELF_CLASS_64 )
1359 9 : | ( ehdr.e_ident[ FD_ELF_EI_DATA ] != FD_ELF_DATA_LE )
1360 9 : | ( ehdr.e_ident[ FD_ELF_EI_VERSION ] != 1 )
1361 9 : | ( ehdr.e_ident[ FD_ELF_EI_OSABI ] != FD_ELF_OSABI_NONE )
1362 9 : | ( fd_ulong_load_8( ehdr.e_ident+8 ) != 0UL )
1363 9 : | ( ehdr.e_type != FD_ELF_ET_DYN )
1364 9 : | ( ehdr.e_machine != FD_ELF_EM_SBPF )
1365 9 : | ( ehdr.e_version != 1 )
1366 : // | ( ehdr.e_entry )
1367 9 : | ( ehdr.e_phoff != sizeof(fd_elf64_ehdr) )
1368 : // | ( ehdr.e_shoff )
1369 : // | ( ehdr.e_flags )
1370 9 : | ( ehdr.e_ehsize != sizeof(fd_elf64_ehdr) )
1371 9 : | ( ehdr.e_phentsize != sizeof(fd_elf64_phdr) )
1372 9 : | ( ehdr.e_phnum < EXPECTED_PHDR_CNT ) /* SIMD-0189 says < instead of != */
1373 9 : | ( program_header_table_end >= bin_sz )
1374 9 : | ( ehdr.e_shentsize != sizeof(fd_elf64_shdr) )
1375 : // | ( ehdr.e_shnum )
1376 9 : | ( ehdr.e_shstrndx >= ehdr.e_shnum )
1377 9 : ;
1378 9 : if( FD_UNLIKELY( parse_ehdr_err ) ) {
1379 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_FILE_HEADER;
1380 0 : }
1381 :
1382 : /* Parse program headers (expecting 4 segments) */
1383 :
1384 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L455-L487
1385 : Note: Agave iterates with a zip, i.e. it cuts the loop to 4, even
1386 : though the number of phdrs is allowed to be higher. */
1387 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 };
1388 9 : uint expected_p_flags[ EXPECTED_PHDR_CNT ] = { FD_SBPF_PF_X, FD_SBPF_PF_R, FD_SBPF_PF_RW, FD_SBPF_PF_RW };
1389 9 : fd_elf64_phdr phdr[ EXPECTED_PHDR_CNT ];
1390 45 : for( uint i=0; i<EXPECTED_PHDR_CNT; i++ ) {
1391 36 : ulong phdr_off = sizeof(fd_elf64_ehdr) + i*sizeof(fd_elf64_phdr);
1392 36 : phdr[ i ] = FD_LOAD( fd_elf64_phdr, bin+phdr_off );
1393 :
1394 36 : ulong p_filesz = ( expected_p_flags[ i ] & FD_SBPF_PF_W ) ? 0UL : phdr[ i ].p_memsz;
1395 36 : int parse_phdr_err =
1396 36 : ( phdr[ i ].p_type != FD_ELF_PT_LOAD )
1397 36 : | ( phdr[ i ].p_flags != expected_p_flags[ i ] )
1398 36 : | ( phdr[ i ].p_offset < program_header_table_end )
1399 36 : | ( phdr[ i ].p_offset >= bin_sz )
1400 36 : | ( phdr[ i ].p_offset % 8UL != 0UL )
1401 36 : | ( phdr[ i ].p_vaddr != expected_p_vaddr[ i ] )
1402 36 : | ( phdr[ i ].p_paddr != expected_p_vaddr[ i ] )
1403 36 : | ( phdr[ i ].p_filesz != p_filesz )
1404 36 : | ( phdr[ i ].p_filesz > bin_sz - phdr[ i ].p_offset )
1405 36 : | ( phdr[ i ].p_filesz % 8UL != 0UL )
1406 36 : | ( phdr[ i ].p_memsz >= FD_SBPF_MM_REGION_SZ )
1407 36 : ;
1408 36 : if( FD_UNLIKELY( parse_phdr_err ) ) {
1409 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_PROGRAM_HEADER;
1410 0 : }
1411 36 : }
1412 :
1413 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L489-L506 */
1414 9 : ulong vm_range_start = phdr[ 0 ].p_vaddr;
1415 9 : ulong vm_range_end = phdr[ 0 ].p_vaddr + phdr[ 0 ].p_memsz;
1416 9 : ulong entry_chk = ehdr.e_entry + 7UL;
1417 9 : int parse_e_entry_err =
1418 9 : !( vm_range_start <= entry_chk && entry_chk < vm_range_end ) /* rust contains includes min, excludes max*/
1419 9 : | ( ehdr.e_entry % 8UL != 0UL )
1420 9 : ;
1421 9 : if( FD_UNLIKELY( parse_e_entry_err ) ) {
1422 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_FILE_HEADER;
1423 0 : }
1424 :
1425 : /* https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L507-L515 */
1426 9 : ulong entry_pc = ( ehdr.e_entry - phdr[ 0 ].p_vaddr ) / 8UL;
1427 9 : ulong insn = fd_ulong_load_8( (uchar const *) bin + phdr[ 0 ].p_offset + entry_pc*8UL );
1428 : /* Entrypoint must be a valid function start (ADD64_IMM with dst=r10)
1429 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/ebpf.rs#L588 */
1430 9 : if( FD_UNLIKELY( !fd_sbpf_is_function_start( fd_sbpf_instr( insn ) ) ) ) {
1431 0 : return FD_SBPF_ELF_PARSER_ERR_INVALID_FILE_HEADER;
1432 0 : }
1433 :
1434 : /* config.enable_symbol_and_section_labels is false in production,
1435 : so there's nothing else to do.
1436 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L519 */
1437 :
1438 9 : info->rodata_sz = (uint)phdr[ 1 ].p_memsz;
1439 9 : info->rodata_footprint = (uint)bin_sz;
1440 9 : info->entry_pc = (uint)entry_pc;
1441 9 : info->text_off = (uint)phdr[ 0 ].p_offset;
1442 9 : info->text_sz = (uint)phdr[ 0 ].p_memsz;
1443 9 : info->text_cnt = (uint)( phdr[ 0 ].p_memsz / 8UL );
1444 :
1445 9 : return 0;
1446 9 : }
1447 :
1448 : int
1449 : fd_sbpf_elf_peek_lenient( fd_sbpf_elf_info_t * info,
1450 : void const * bin,
1451 : ulong bin_sz,
1452 87 : fd_sbpf_loader_config_t const * config ) {
1453 : // FIXME
1454 87 : fd_sbpf_elf_info_t * res = fd_sbpf_elf_peek_old( info, bin, bin_sz, config->elf_deploy_checks, config->sbpf_min_version, config->sbpf_max_version );
1455 :
1456 : /* Peek (vs load) stops here
1457 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L639 */
1458 87 : return res==NULL ? FD_SBPF_ELF_PARSER_ERR_INVALID_FILE_HEADER : 0;
1459 87 : }
1460 :
1461 : int
1462 : fd_sbpf_elf_peek( fd_sbpf_elf_info_t * info,
1463 : void const * bin,
1464 : ulong bin_sz,
1465 147 : fd_sbpf_loader_config_t const * config ) {
1466 : /* Extract sbpf_version (or error)
1467 : https://github.com/anza-xyz/sbpf/blob/main/src/elf.rs#L376-L401 */
1468 147 : int maybe_sbpf_version = fd_sbpf_program_get_sbpf_version_or_err( bin, bin_sz, config );
1469 147 : if( FD_UNLIKELY( maybe_sbpf_version<0 ) ) {
1470 51 : return maybe_sbpf_version;
1471 51 : }
1472 :
1473 : /* Initialize info struct */
1474 96 : *info = (fd_sbpf_elf_info_t) {
1475 96 : .text_off = 0U,
1476 96 : .text_cnt = 0U,
1477 96 : .text_sz = 0UL,
1478 96 : .dynstr_off = 0U,
1479 96 : .dynstr_sz = 0U,
1480 96 : .rodata_sz = 0U,
1481 96 : .rodata_footprint = 0U,
1482 96 : .shndx_text = -1,
1483 96 : .shndx_symtab = -1,
1484 96 : .shndx_strtab = -1,
1485 96 : .shndx_dyn = -1,
1486 96 : .shndx_dynstr = -1,
1487 96 : .phndx_dyn = -1,
1488 96 : .entry_pc = 0U,
1489 96 : .sbpf_version = (uint)maybe_sbpf_version,
1490 : /* !!! Keep this in sync with -Werror=missing-field-initializers */
1491 96 : };
1492 :
1493 : /* Invoke strict vs lenient parser
1494 : https://github.com/anza-xyz/sbpf/blob/main/src/elf.rs#L403-L407 */
1495 96 : if( FD_UNLIKELY( fd_sbpf_enable_stricter_elf_headers( info->sbpf_version ) ) ) {
1496 9 : return fd_sbpf_elf_peek_strict( info, bin, bin_sz, config );
1497 9 : }
1498 87 : return fd_sbpf_elf_peek_lenient( info, bin, bin_sz, config );
1499 96 : }
1500 :
1501 : int
1502 : fd_sbpf_program_load_lenient( fd_sbpf_program_t * prog,
1503 : void const * bin,
1504 : ulong bin_sz,
1505 : fd_sbpf_syscalls_t * syscalls,
1506 66 : fd_sbpf_loader_config_t const * config ) {
1507 : /* Load (vs peek) starts here
1508 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/elf.rs#L639 */
1509 :
1510 : // FIXME
1511 66 : return fd_sbpf_program_load_old( prog, bin, bin_sz, syscalls, config->elf_deploy_checks );
1512 66 : }
1513 :
1514 : int
1515 : fd_sbpf_program_load( fd_sbpf_program_t * prog,
1516 : void const * bin,
1517 : ulong bin_sz,
1518 : fd_sbpf_syscalls_t * syscalls,
1519 66 : fd_sbpf_loader_config_t const * config ) {
1520 : /* Invoke strict vs lenient loader
1521 : Note: info.sbpf_version is already set by fd_sbpf_program_parse()
1522 : https://github.com/anza-xyz/sbpf/blob/main/src/elf.rs#L409-L413 */
1523 66 : if( FD_UNLIKELY( fd_sbpf_enable_stricter_elf_headers( prog->info.sbpf_version ) ) ) {
1524 : /* There is nothing else to do in the strict case*/
1525 0 : return 0;
1526 0 : }
1527 66 : return fd_sbpf_program_load_lenient( prog, bin, bin_sz, syscalls, config );
1528 66 : }
1529 :
1530 : #undef ERR
1531 : #undef FAIL
1532 : #undef REQUIRE
|