Line data Source code
1 : #include "fd_vm_syscall.h"
2 :
3 : #include "../../../ballet/base64/fd_base64.h"
4 : #include "../../../ballet/utf8/fd_utf8.h"
5 : #include "../../runtime/sysvar/fd_sysvar.h"
6 : #include "../../runtime/sysvar/fd_sysvar_clock.h"
7 : #include "../../runtime/sysvar/fd_sysvar_epoch_schedule.h"
8 : #include "../../runtime/context/fd_exec_txn_ctx.h"
9 : #include "../../runtime/context/fd_exec_instr_ctx.h"
10 :
11 : int
12 : fd_vm_syscall_abort( /**/ void * _vm,
13 : FD_PARAM_UNUSED ulong r1,
14 : FD_PARAM_UNUSED ulong r2,
15 : FD_PARAM_UNUSED ulong r3,
16 : FD_PARAM_UNUSED ulong r4,
17 : FD_PARAM_UNUSED ulong r5,
18 0 : FD_PARAM_UNUSED ulong * _ret ) {
19 : /* https://github.com/anza-xyz/agave/blob/v2.0.6/programs/bpf_loader/src/syscalls/mod.rs#L630 */
20 0 : fd_vm_t * vm = (fd_vm_t *)_vm;
21 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_ABORT );
22 0 : return FD_VM_SYSCALL_ERR_ABORT;
23 0 : }
24 :
25 : /* FD_TRANSLATE_STRING returns a read only pointer to the host address of
26 : a valid utf8 string, or it errors.
27 :
28 : Analogous of Agave's translate_string_and_do().
29 : https://github.com/anza-xyz/agave/blob/v2.0.6/programs/bpf_loader/src/syscalls/mod.rs#L601
30 :
31 : As of v0.2.6, the only two usages are in syscall panic and syscall log. */
32 9 : #define FD_TRANSLATE_STRING( vm, vaddr, msg_sz ) (__extension__({ \
33 9 : char const * msg = FD_VM_MEM_SLICE_HADDR_LD( vm, vaddr, FD_VM_ALIGN_RUST_U8, msg_sz ); \
34 9 : if( FD_UNLIKELY( !fd_utf8_verify( msg, msg_sz ) ) ) { \
35 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_STRING ); \
36 0 : return FD_VM_SYSCALL_ERR_INVALID_STRING; \
37 0 : } \
38 9 : msg; \
39 9 : }))
40 :
41 : int
42 : fd_vm_syscall_sol_panic( /**/ void * _vm,
43 : /**/ ulong file_vaddr,
44 : /**/ ulong file_sz,
45 : /**/ ulong line,
46 : /**/ ulong column,
47 : FD_PARAM_UNUSED ulong r5,
48 0 : FD_PARAM_UNUSED ulong * _ret ) {
49 0 : fd_vm_t * vm = (fd_vm_t *)_vm;
50 :
51 : /* https://github.com/anza-xyz/agave/blob/v2.0.6/programs/bpf_loader/src/syscalls/mod.rs#L637
52 :
53 : Note: this syscall is not used by the Rust SDK, only by the C SDK.
54 : Rust transforms `panic!()` into a log, followed by an abort.
55 : It's unclear if this syscall actually makes any sense... */
56 0 : FD_VM_CU_UPDATE( vm, file_sz );
57 :
58 : /* Validate string */
59 0 : FD_TRANSLATE_STRING( vm, file_vaddr, file_sz );
60 :
61 : /* Note: we truncate the log, ignoring file, line, column.
62 : As mentioned above, it's unclear if anyone is even using this syscall,
63 : so dealing with the complexity of Agave's log is a waste of time. */
64 0 : (void)line;
65 0 : (void)column;
66 :
67 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_PANIC );
68 0 : return FD_VM_SYSCALL_ERR_PANIC;
69 0 : }
70 :
71 : int
72 : fd_vm_syscall_sol_log( /**/ void * _vm,
73 : /**/ ulong msg_vaddr,
74 : /**/ ulong msg_sz,
75 : FD_PARAM_UNUSED ulong r3,
76 : FD_PARAM_UNUSED ulong r4,
77 : FD_PARAM_UNUSED ulong r5,
78 9 : /**/ ulong * _ret ) {
79 9 : fd_vm_t * vm = (fd_vm_t *)_vm;
80 :
81 : /* https://github.com/anza-xyz/agave/blob/v2.0.6/programs/bpf_loader/src/syscalls/logging.rs#L5 */
82 :
83 9 : FD_VM_CU_UPDATE( vm, fd_ulong_max( msg_sz, FD_VM_SYSCALL_BASE_COST ) );
84 :
85 : /* Note: when msg_sz==0, msg can be undefined. fd_log_collector_program_log() handles it.
86 : FIXME: Macro invocation in function invocation? */
87 9 : fd_log_collector_program_log( vm->instr_ctx, FD_TRANSLATE_STRING( vm, msg_vaddr, msg_sz ), msg_sz );
88 :
89 0 : *_ret = 0UL;
90 9 : return FD_VM_SUCCESS;
91 18 : }
92 :
93 : int
94 : fd_vm_syscall_sol_log_64( void * _vm,
95 : ulong r1,
96 : ulong r2,
97 : ulong r3,
98 : ulong r4,
99 : ulong r5,
100 3 : ulong * _ret ) {
101 3 : fd_vm_t * vm = (fd_vm_t *)_vm;
102 :
103 : /* https://github.com/anza-xyz/agave/blob/v2.0.6/programs/bpf_loader/src/syscalls/logging.rs#L37 */
104 :
105 3 : FD_VM_CU_UPDATE( vm, FD_VM_LOG_64_UNITS );
106 :
107 : /* Max msg_sz: 46 - 15 + 16*5 = 111 < 127 => we can use printf */
108 0 : fd_log_collector_printf_dangerous_max_127( vm->instr_ctx,
109 3 : "Program log: 0x%lx, 0x%lx, 0x%lx, 0x%lx, 0x%lx", r1, r2, r3, r4, r5 );
110 :
111 3 : *_ret = 0UL;
112 3 : return FD_VM_SUCCESS;
113 3 : }
114 :
115 : int
116 : fd_vm_syscall_sol_log_compute_units( /**/ void * _vm,
117 : FD_PARAM_UNUSED ulong r1,
118 : FD_PARAM_UNUSED ulong r2,
119 : FD_PARAM_UNUSED ulong r3,
120 : FD_PARAM_UNUSED ulong r4,
121 : FD_PARAM_UNUSED ulong r5,
122 0 : /**/ ulong * _ret ) {
123 0 : fd_vm_t * vm = (fd_vm_t *)_vm;
124 :
125 : /* https://github.com/anza-xyz/agave/blob/v2.0.6/programs/bpf_loader/src/syscalls/logging.rs#L60 */
126 :
127 0 : FD_VM_CU_UPDATE( vm, FD_VM_SYSCALL_BASE_COST );
128 :
129 : /* Max msg_sz: 40 - 3 + 20 = 57 < 127 => we can use printf */
130 0 : fd_log_collector_printf_dangerous_max_127( vm->instr_ctx,
131 0 : "Program consumption: %lu units remaining", vm->cu );
132 :
133 0 : *_ret = 0UL;
134 0 : return FD_VM_SUCCESS;
135 0 : }
136 :
137 : int
138 : fd_vm_syscall_sol_log_pubkey( /**/ void * _vm,
139 : /**/ ulong pubkey_vaddr,
140 : FD_PARAM_UNUSED ulong r2,
141 : FD_PARAM_UNUSED ulong r3,
142 : FD_PARAM_UNUSED ulong r4,
143 : FD_PARAM_UNUSED ulong r5,
144 0 : /**/ ulong * _ret ) {
145 0 : fd_vm_t * vm = (fd_vm_t *)_vm;
146 :
147 : /* https://github.com/anza-xyz/agave/blob/v2.0.6/programs/bpf_loader/src/syscalls/logging.rs#L84 */
148 :
149 0 : FD_VM_CU_UPDATE( vm, FD_VM_LOG_PUBKEY_UNITS );
150 :
151 0 : void const * pubkey = FD_VM_MEM_HADDR_LD( vm, pubkey_vaddr, FD_VM_ALIGN_RUST_PUBKEY, sizeof(fd_pubkey_t) );
152 :
153 0 : char msg[ FD_BASE58_ENCODED_32_SZ ]; ulong msg_sz;
154 0 : if( FD_UNLIKELY( fd_base58_encode_32( pubkey, &msg_sz, msg )==NULL ) ) {
155 0 : return FD_VM_SYSCALL_ERR_INVALID_STRING;
156 0 : }
157 :
158 0 : fd_log_collector_program_log( vm->instr_ctx, msg, msg_sz );
159 :
160 0 : *_ret = 0UL;
161 0 : return FD_VM_SUCCESS;
162 0 : }
163 :
164 : int
165 : fd_vm_syscall_sol_log_data( /**/ void * _vm,
166 : /**/ ulong slice_vaddr,
167 : /**/ ulong slice_cnt,
168 : FD_PARAM_UNUSED ulong r3,
169 : FD_PARAM_UNUSED ulong r4,
170 : FD_PARAM_UNUSED ulong r5,
171 3 : /**/ ulong * _ret ) {
172 3 : fd_vm_t * vm = (fd_vm_t *)_vm;
173 :
174 : /* https://github.com/anza-xyz/agave/blob/v2.0.6/programs/bpf_loader/src/syscalls/logging.rs#L109
175 :
176 : Note: this is implemented following Agave's perverse behavior.
177 : We need to loop the slice multiple times to match the exact error,
178 : first compute budget, then memory mapping.
179 : And finally we can loop to log. */
180 :
181 : /* https://github.com/anza-xyz/agave/blob/v2.0.6/programs/bpf_loader/src/syscalls/logging.rs#L121 */
182 :
183 3 : FD_VM_CU_UPDATE( vm, FD_VM_SYSCALL_BASE_COST );
184 :
185 : /* https://github.com/anza-xyz/agave/blob/v2.0.6/programs/bpf_loader/src/syscalls/logging.rs#L123-L128 */
186 :
187 6 : fd_vm_vec_t const * slice = (fd_vm_vec_t const *)FD_VM_MEM_SLICE_HADDR_LD( vm, slice_vaddr, FD_VM_ALIGN_RUST_SLICE_U8_REF,
188 6 : fd_ulong_sat_mul( slice_cnt, sizeof(fd_vm_vec_t) ) );
189 :
190 : /* https://github.com/anza-xyz/agave/blob/v2.0.6/programs/bpf_loader/src/syscalls/logging.rs#L130-L135 */
191 :
192 3 : FD_VM_CU_UPDATE( vm, fd_ulong_sat_mul( FD_VM_SYSCALL_BASE_COST, slice_cnt ) );
193 :
194 : /* https://github.com/anza-xyz/agave/blob/v2.0.6/programs/bpf_loader/src/syscalls/logging.rs#L136-L141 */
195 :
196 18 : for( ulong i=0UL; i<slice_cnt; i++ ) {
197 15 : FD_VM_CU_UPDATE( vm, slice[i].len );
198 15 : }
199 :
200 : /* https://github.com/anza-xyz/agave/blob/v2.0.6/programs/bpf_loader/src/syscalls/logging.rs#L145-L152 */
201 :
202 3 : ulong msg_sz = 14UL; /* "Program data: ", with space */
203 18 : for( ulong i=0UL; i<slice_cnt; i++ ) {
204 15 : ulong cur_len = slice[i].len;
205 : /* This fails the syscall in case of memory mapping issues */
206 30 : FD_VM_MEM_SLICE_HADDR_LD( vm, slice[i].addr, FD_VM_ALIGN_RUST_U8, cur_len );
207 : /* Every buffer will be base64 encoded + space separated */
208 0 : msg_sz += (slice[i].len + 2)/3*4 + (i > 0);
209 30 : }
210 :
211 : /* https://github.com/anza-xyz/agave/blob/v2.0.6/programs/bpf_loader/src/syscalls/logging.rs#L156 */
212 :
213 3 : char msg[ FD_LOG_COLLECTOR_MAX ];
214 3 : ulong bytes_written = fd_log_collector_check_and_truncate( &vm->instr_ctx->txn_ctx->log_collector, msg_sz );
215 3 : if( FD_LIKELY( bytes_written < ULONG_MAX ) ) {
216 3 : fd_memcpy( msg, "Program data: ", 14 );
217 3 : char * buf = msg + 14;
218 :
219 18 : for( ulong i=0UL; i<slice_cnt; i++ ) {
220 15 : ulong cur_len = slice[i].len;
221 30 : void const * bytes = FD_VM_MEM_SLICE_HADDR_LD( vm, slice[i].addr, FD_VM_ALIGN_RUST_U8, cur_len );
222 :
223 15 : if( i ) { *buf = ' '; ++buf; } /* skip first */
224 30 : buf += fd_base64_encode( buf, bytes, cur_len );
225 30 : }
226 3 : FD_TEST( (ulong)(buf-msg)==msg_sz );
227 :
228 3 : fd_log_collector_msg( vm->instr_ctx, msg, msg_sz );
229 3 : }
230 :
231 3 : *_ret = 0;
232 3 : return FD_VM_SUCCESS;
233 3 : }
234 :
235 : int
236 : fd_vm_syscall_sol_alloc_free( /**/ void * _vm,
237 : /**/ ulong sz,
238 : /**/ ulong free_vaddr,
239 : FD_PARAM_UNUSED ulong r3,
240 : FD_PARAM_UNUSED ulong r4,
241 : FD_PARAM_UNUSED ulong r5,
242 0 : /**/ ulong * _ret ) {
243 0 : fd_vm_t * vm = (fd_vm_t *)_vm;
244 :
245 : /* https://github.com/anza-xyz/agave/blob/v2.0.8/programs/bpf_loader/src/syscalls/mod.rs#L666 */
246 :
247 : /* This syscall is ... uh ... problematic. But the community has
248 : already recognized this and deprecated it:
249 :
250 : https://github.com/solana-labs/solana/blob/v1.17.23/sdk/src/feature_set.rs#L846
251 :
252 : Unfortunately, old code never dies so, practically, this will need
253 : to be supported until the heat death of the universe.
254 :
255 : The most serious issue is that there is nothing to stop VM code
256 : making a decision based on the _location_ of the returned
257 : allocation. If different validator implementations use different
258 : allocator algorithms, though each implementation would behave
259 : functionally correct in isolation, the VM code that uses it would
260 : actually break consensus.
261 :
262 : As a result, every validator needs to use a bit-for-bit identical
263 : allocation algorithm. Fortunately, Solana is just using a basic
264 : bump allocator:
265 :
266 : https://github.com/solana-labs/solana/blob/v1.17.23/program-runtime/src/invoke_context.rs#L122-L148
267 :
268 : vm->heap_{sz,max} and the below replicate this exactly.
269 :
270 : Another major issue is that this alloc doesn't always conform
271 : typical malloc/free semantics (e.g. C/C++ requires malloc to have
272 : an alignment safe for primitive types ... 8 for the Solana machine
273 : model). This is clearly to support backward compat with older VM
274 : code (though ideally a malloc syscall should have behaved like ...
275 : well ... malloc from day 1). So the alignment behavior below is a
276 : bug-for-bug replication of that:
277 :
278 : https://github.com/solana-labs/solana/blob/v1.17.23/programs/bpf_loader/src/syscalls/mod.rs#L645-L681
279 : https://github.com/solana-labs/solana/blob/v1.17.23/sdk/program/src/entrypoint.rs#L265-L266
280 :
281 : More generally and already ranted about elsewhere, any code that
282 : uses malloc/free style dynamic allocation is inherently broken. So
283 : this syscall should have never existed in the first place ... it
284 : just feeds the trolls. The above is just additional implementation
285 : horror because people consistent think malloc/free is much simpler
286 : than it actually is. This is also an example of how quickly
287 : mistakes fossilize and become a thorn-in-the-side forever.
288 :
289 : IMPORTANT SAFETY TIP! heap_start must be non zero and both
290 : heap_start and heap_end should have an alignment of at least 8.
291 : This existing runtime policies around heap implicitly satisfy this.
292 :
293 : IMPORTANT SAFETY TIP! The specification for Rust's align_offset
294 : doesn't seem to provide a strong guarantee that it will return the
295 : minimal positive offset necessary to align pointers. It is
296 : possible for a "conforming" Rust compiler to break consensus by
297 : using a different align_offset implementation that aligned pointer
298 : between different compilations of the Solana validator and the
299 : below. */
300 :
301 : /* https://github.com/anza-xyz/agave/blob/v2.0.8/programs/bpf_loader/src/syscalls/mod.rs#L676-L680 */
302 :
303 0 : ulong align = fd_vm_is_check_align_enabled( vm ) ? 8UL : FD_VM_ALIGN_RUST_U8;
304 :
305 : /* https://github.com/anza-xyz/agave/blob/v2.0.8/programs/bpf_loader/src/syscalls/mod.rs#L681-L683
306 : Nothing to do. This section can't error, see:
307 : https://doc.rust-lang.org/1.81.0/src/core/alloc/layout.rs.html#70
308 : https://doc.rust-lang.org/1.81.0/src/core/alloc/layout.rs.html#100 */
309 :
310 :
311 : /* https://github.com/anza-xyz/agave/blob/v2.0.8/programs/bpf_loader/src/syscalls/mod.rs#L684
312 : Nothing to do.
313 : TODO: unclear if it throw InstructionError::CallDepth
314 : https://github.com/anza-xyz/agave/blob/v2.0.8/program-runtime/src/invoke_context.rs#L662 */
315 :
316 : /* https://github.com/anza-xyz/agave/blob/v2.0.8/programs/bpf_loader/src/syscalls/mod.rs#L685-L693 */
317 :
318 : /* Non-zero free address implies that this is a free() call. Since
319 : this is a bump allocator, free is a no-op. */
320 0 : if( FD_UNLIKELY( free_vaddr ) ) {
321 0 : *_ret = 0UL;
322 0 : return FD_VM_SUCCESS;
323 0 : }
324 :
325 :
326 0 : ulong heap_sz = fd_ulong_align_up( vm->heap_sz, align );
327 0 : ulong heap_vaddr = fd_ulong_sat_add ( heap_sz, FD_VM_MEM_MAP_HEAP_REGION_START );
328 0 : /**/ heap_sz = fd_ulong_sat_add ( heap_sz, sz );
329 :
330 0 : if( FD_UNLIKELY( heap_sz > vm->heap_max ) ) { /* Not enough free memory */
331 0 : *_ret = 0UL;
332 0 : return FD_VM_SUCCESS;
333 0 : }
334 :
335 0 : vm->heap_sz = heap_sz;
336 :
337 0 : *_ret = heap_vaddr;
338 0 : return FD_VM_SUCCESS;
339 0 : }
340 :
341 : /* https://github.com/anza-xyz/agave/blob/v2.0.8/programs/bpf_loader/src/syscalls/mem_ops.rs#L145 */
342 : int
343 : fd_vm_memmove( fd_vm_t * vm,
344 : ulong dst_vaddr,
345 : ulong src_vaddr,
346 66 : ulong sz ) {
347 66 : if( FD_UNLIKELY( !sz ) ) {
348 0 : return FD_VM_SUCCESS;
349 0 : }
350 :
351 66 : if( !vm->direct_mapping ) {
352 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/programs/bpf_loader/src/syscalls/mem_ops.rs#L188-L192 */
353 24 : fd_vm_haddr_query_t dst_ref_mut_query = {
354 24 : .vaddr = dst_vaddr,
355 24 : .align = FD_VM_ALIGN_RUST_U8,
356 24 : .sz = sz,
357 24 : .is_slice = 1,
358 24 : };
359 :
360 24 : fd_vm_haddr_query_t * queries[] = { &dst_ref_mut_query };
361 24 : FD_VM_TRANSLATE_MUT( vm, queries );
362 :
363 48 : void const * src = FD_VM_MEM_HADDR_LD( vm, src_vaddr, FD_VM_ALIGN_RUST_U8, sz );
364 0 : memmove( dst_ref_mut_query.haddr, src, sz );
365 48 : } else {
366 : /* If the src and dst vaddrs overlap and src_vaddr < dst_vaddr, Agave iterates through input regions backwards
367 : to maintain correct memmove behavior for overlapping cases. Although this logic should only apply to the src and dst
368 : vaddrs being in the input data region (since that is the only possible case you could have overlapping, chunked-up memmoves),
369 : Agave will iterate backwards in ANY region. If it eventually reaches the end of a region after iterating backwards and
370 : hits an access violation, the bytes from [region_begin, start_vaddr] will still be written to, causing fuzzing mismatches.
371 : In this case, if we didn't have the reverse flag, we would have thrown an access violation before any bytes were copied.
372 : The same logic applies to memmoves that go past the high end of a region - reverse iteration logic would throw an access
373 : violation before any bytes were copied, while the current logic would copy the bytes until the end of the region.
374 : https://github.com/anza-xyz/agave/blob/v2.1.0/programs/bpf_loader/src/syscalls/mem_ops.rs#L184 */
375 42 : uchar reverse = !!( dst_vaddr >= src_vaddr && dst_vaddr - src_vaddr < sz );
376 :
377 : /* In reverse calculations, start from the rightmost vaddr that will be accessed (note the - 1). */
378 42 : ulong dst_vaddr_begin = reverse ? fd_ulong_sat_add( dst_vaddr, sz - 1UL ) : dst_vaddr;
379 42 : ulong src_vaddr_begin = reverse ? fd_ulong_sat_add( src_vaddr, sz - 1UL ) : src_vaddr;
380 :
381 : /* Find the correct src and dst haddrs to start operating from. If the src or dst vaddrs
382 : belong to the input data region (4), keep track of region statistics to memmove in chunks. */
383 42 : ulong dst_region = FD_VADDR_TO_REGION( dst_vaddr_begin );
384 42 : uchar dst_is_input_mem_region = ( dst_region==FD_VM_INPUT_REGION );
385 42 : ulong dst_offset = dst_vaddr_begin & FD_VM_OFFSET_MASK;
386 42 : ulong dst_region_idx = 0UL;
387 42 : ulong dst_bytes_rem_in_cur_region;
388 42 : uchar * dst_haddr;
389 42 : if( dst_is_input_mem_region ) {
390 18 : FD_VM_MEM_HADDR_AND_REGION_IDX_FROM_INPUT_REGION_CHECKED( vm, dst_offset, dst_region_idx, dst_haddr );
391 18 : if( FD_UNLIKELY( !vm->input_mem_regions[ dst_region_idx ].is_writable ) ) {
392 0 : FD_VM_ERR_FOR_LOG_EBPF( vm, FD_VM_ERR_EBPF_ACCESS_VIOLATION );
393 0 : return FD_VM_SYSCALL_ERR_SEGFAULT;
394 0 : }
395 18 : if( FD_UNLIKELY( reverse ) ) {
396 : /* Bytes remaining between region begin and current position (+ 1 for inclusive region beginning). */
397 6 : dst_bytes_rem_in_cur_region = fd_ulong_sat_sub( dst_offset + 1UL, vm->input_mem_regions[ dst_region_idx ].vaddr_offset );
398 12 : } else {
399 : /* Bytes remaining between current position and region end. */
400 12 : dst_bytes_rem_in_cur_region = fd_ulong_sat_sub( vm->input_mem_regions[ dst_region_idx ].region_sz, ( dst_offset - vm->input_mem_regions[ dst_region_idx ].vaddr_offset ) );
401 12 : }
402 24 : } else {
403 24 : dst_haddr = (uchar*)FD_VM_MEM_HADDR_ST_NO_SZ_CHECK( vm, dst_vaddr_begin, FD_VM_ALIGN_RUST_U8 );
404 :
405 18 : if( FD_UNLIKELY( reverse ) ) {
406 : /* Bytes remaining is minimum of the offset from the beginning of the current
407 : region (+1 for inclusive region beginning) and the number of storable bytes in the region. */
408 3 : dst_bytes_rem_in_cur_region = fd_ulong_min( vm->region_st_sz[ dst_region ], dst_offset + 1UL );
409 :
410 15 : } else {
411 : /* Bytes remaining is the number of writable bytes left in the region */
412 15 : dst_bytes_rem_in_cur_region = fd_ulong_sat_sub( vm->region_st_sz[ dst_region ], dst_offset );
413 15 : }
414 18 : }
415 :
416 : /* Logic for src vaddr translation is similar to above excluding any writable checks. */
417 36 : ulong src_region = FD_VADDR_TO_REGION( src_vaddr_begin );
418 36 : uchar src_is_input_mem_region = ( src_region==FD_VM_INPUT_REGION );
419 36 : ulong src_offset = src_vaddr_begin & FD_VM_OFFSET_MASK;
420 36 : ulong src_region_idx = 0UL;
421 36 : ulong src_bytes_rem_in_cur_region;
422 36 : uchar * src_haddr;
423 36 : if( src_is_input_mem_region ) {
424 18 : FD_VM_MEM_HADDR_AND_REGION_IDX_FROM_INPUT_REGION_CHECKED( vm, src_offset, src_region_idx, src_haddr );
425 18 : if( FD_UNLIKELY( reverse ) ) {
426 6 : src_bytes_rem_in_cur_region = fd_ulong_sat_sub( src_offset + 1UL, vm->input_mem_regions[ src_region_idx ].vaddr_offset );
427 12 : } else {
428 12 : src_bytes_rem_in_cur_region = fd_ulong_sat_sub( vm->input_mem_regions[ src_region_idx ].region_sz, ( src_offset - vm->input_mem_regions[ src_region_idx ].vaddr_offset ) );
429 12 : }
430 18 : } else {
431 54 : src_haddr = (uchar*)FD_VM_MEM_HADDR_LD_NO_SZ_CHECK( vm, src_vaddr_begin, FD_VM_ALIGN_RUST_U8 );
432 :
433 18 : if( FD_UNLIKELY( reverse ) ) {
434 3 : src_bytes_rem_in_cur_region = fd_ulong_min( vm->region_ld_sz[ src_region ], src_offset + 1UL );
435 :
436 15 : } else {
437 15 : src_bytes_rem_in_cur_region = fd_ulong_sat_sub( vm->region_ld_sz[ src_region ], src_offset );
438 15 : }
439 54 : }
440 :
441 : /* Short circuit: if the number of copyable bytes stays within all memory regions,
442 : just memmove and return. This is a majority case in mainnet, devnet, and testnet.
443 : Someone would have to be very crafty and clever to construct a transaction that
444 : deploys and invokes a custom program that does not fall into this branch. */
445 36 : if( FD_LIKELY( sz<=dst_bytes_rem_in_cur_region && sz<=src_bytes_rem_in_cur_region ) ) {
446 21 : if( FD_UNLIKELY( reverse ) ) {
447 : /* In the reverse iteration case, the haddrs point to the end of the region here. Since the
448 : above checks guarantee that there are enough bytes left in the src and dst regions to do
449 : a direct memmove, we can just subtract (sz-1) from the haddrs, memmove, and return. */
450 3 : memmove( dst_haddr - sz + 1UL, src_haddr - sz + 1UL, sz );
451 18 : } else {
452 : /* In normal iteration, the haddrs correspond to the correct starting point for the memcpy,
453 : so no further translation has to be done. */
454 18 : memmove( dst_haddr, src_haddr, sz );
455 18 : }
456 21 : return FD_VM_SUCCESS;
457 21 : }
458 :
459 : /* Copy over the bytes from each region in chunks. */
460 57 : while( sz>0UL ) {
461 : /* End of region case */
462 45 : if( FD_UNLIKELY( src_bytes_rem_in_cur_region==0UL ) ) {
463 : /* Same as above, except no writable checks. */
464 30 : if( FD_LIKELY( !reverse &&
465 30 : src_is_input_mem_region &&
466 30 : src_region_idx+1UL<vm->input_mem_regions_cnt ) ) {
467 12 : if( FD_UNLIKELY( vm->input_mem_regions[ src_region_idx+1UL ].is_acct_data != vm->input_mem_regions[ src_region_idx ].is_acct_data ) ) {
468 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_LENGTH );
469 0 : return FD_VM_SYSCALL_ERR_SEGFAULT;
470 0 : }
471 12 : src_region_idx++;
472 12 : src_haddr = (uchar*)vm->input_mem_regions[ src_region_idx ].haddr;
473 18 : } else if( FD_LIKELY( reverse && src_region_idx>0UL ) ) {
474 15 : if( FD_UNLIKELY( vm->input_mem_regions[ src_region_idx-1UL ].is_acct_data != vm->input_mem_regions[ src_region_idx ].is_acct_data ) ) {
475 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_LENGTH );
476 0 : return FD_VM_SYSCALL_ERR_SEGFAULT;
477 0 : }
478 15 : src_region_idx--;
479 15 : src_haddr = (uchar*)vm->input_mem_regions[ src_region_idx ].haddr + vm->input_mem_regions[ src_region_idx ].region_sz - 1UL;
480 15 : } else {
481 3 : FD_VM_ERR_FOR_LOG_EBPF( vm, FD_VM_ERR_EBPF_ACCESS_VIOLATION );
482 3 : return FD_VM_SYSCALL_ERR_SEGFAULT;
483 3 : }
484 27 : src_bytes_rem_in_cur_region = vm->input_mem_regions[ src_region_idx ].region_sz;
485 27 : }
486 42 : if( FD_UNLIKELY( dst_bytes_rem_in_cur_region==0UL ) ) {
487 : /* Only proceed if:
488 : - We are in the input memory region
489 : - There are remaining input memory regions to copy from (for both regular and reverse iteration orders)
490 : - The next input memory region is writable
491 : Fail otherwise. */
492 9 : if( FD_LIKELY( !reverse &&
493 9 : dst_is_input_mem_region &&
494 9 : dst_region_idx+1UL<vm->input_mem_regions_cnt &&
495 9 : vm->input_mem_regions[ dst_region_idx+1UL ].is_writable ) ) {
496 6 : if( FD_UNLIKELY( vm->input_mem_regions[ dst_region_idx+1UL ].is_acct_data != vm->input_mem_regions[ dst_region_idx ].is_acct_data ) ) {
497 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_LENGTH );
498 0 : return FD_VM_SYSCALL_ERR_SEGFAULT;
499 0 : }
500 : /* In normal iteration, we move the haddr to the beginning of the next region. */
501 6 : dst_region_idx++;
502 6 : dst_haddr = (uchar*)vm->input_mem_regions[ dst_region_idx ].haddr;
503 6 : } else if( FD_LIKELY( reverse &&
504 3 : dst_region_idx>0UL &&
505 3 : vm->input_mem_regions[ dst_region_idx-1UL ].is_writable ) ) {
506 3 : if( FD_UNLIKELY( vm->input_mem_regions[ dst_region_idx-1UL ].is_acct_data != vm->input_mem_regions[ dst_region_idx ].is_acct_data ) ) {
507 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_LENGTH );
508 0 : return FD_VM_SYSCALL_ERR_SEGFAULT;
509 0 : }
510 : /* Note that when reverse iterating, we set the haddr to the END of the PREVIOUS region. */
511 3 : dst_region_idx--;
512 3 : dst_haddr = (uchar*)vm->input_mem_regions[ dst_region_idx ].haddr + vm->input_mem_regions[ dst_region_idx ].region_sz - 1UL;
513 3 : } else {
514 0 : FD_VM_ERR_FOR_LOG_EBPF( vm, FD_VM_ERR_EBPF_ACCESS_VIOLATION );
515 0 : return FD_VM_SYSCALL_ERR_SEGFAULT;
516 0 : }
517 9 : dst_bytes_rem_in_cur_region = vm->input_mem_regions[ dst_region_idx ].region_sz;
518 9 : }
519 :
520 : /* Number of bytes to operate on in this iteration is the min of:
521 : - number of bytes left to copy
522 : - bytes left in the current src region
523 : - bytes left in the current dst region */
524 42 : ulong num_bytes_to_copy = fd_ulong_min( sz, fd_ulong_min( src_bytes_rem_in_cur_region, dst_bytes_rem_in_cur_region ) );
525 42 : if( FD_UNLIKELY( reverse ) ) {
526 21 : memmove( dst_haddr - num_bytes_to_copy + 1UL, src_haddr - num_bytes_to_copy + 1UL, num_bytes_to_copy );
527 21 : dst_haddr -= num_bytes_to_copy;
528 21 : src_haddr -= num_bytes_to_copy;
529 21 : } else {
530 21 : memmove( dst_haddr, src_haddr, num_bytes_to_copy );
531 21 : dst_haddr += num_bytes_to_copy;
532 21 : src_haddr += num_bytes_to_copy;
533 21 : }
534 :
535 : /* Update size trackers */
536 42 : sz -= num_bytes_to_copy;
537 42 : src_bytes_rem_in_cur_region -= num_bytes_to_copy;
538 42 : dst_bytes_rem_in_cur_region -= num_bytes_to_copy;
539 42 : }
540 15 : }
541 :
542 27 : return FD_VM_SUCCESS;
543 66 : }
544 :
545 : /* https://github.com/anza-xyz/agave/blob/v2.0.8/programs/bpf_loader/src/syscalls/mem_ops.rs#L41 */
546 : int
547 : fd_vm_syscall_sol_memmove( /**/ void * _vm,
548 : /**/ ulong dst_vaddr,
549 : /**/ ulong src_vaddr,
550 : /**/ ulong sz,
551 : FD_PARAM_UNUSED ulong r4,
552 : FD_PARAM_UNUSED ulong r5,
553 36 : /**/ ulong * _ret ) {
554 36 : *_ret = 0;
555 36 : fd_vm_t * vm = (fd_vm_t *)_vm;
556 :
557 36 : FD_VM_CU_MEM_OP_UPDATE( vm, sz );
558 :
559 : /* No overlap check for memmove. */
560 0 : return fd_vm_memmove( vm, dst_vaddr, src_vaddr, sz );
561 36 : }
562 :
563 : /* https://github.com/anza-xyz/agave/blob/v2.0.8/programs/bpf_loader/src/syscalls/mem_ops.rs#L18 */
564 : int
565 : fd_vm_syscall_sol_memcpy( /**/ void * _vm,
566 : /**/ ulong dst_vaddr,
567 : /**/ ulong src_vaddr,
568 : /**/ ulong sz,
569 : FD_PARAM_UNUSED ulong r4,
570 : FD_PARAM_UNUSED ulong r5,
571 48 : /**/ ulong * _ret ) {
572 48 : *_ret = 0;
573 48 : fd_vm_t * vm = (fd_vm_t *)_vm;
574 :
575 48 : FD_VM_CU_MEM_OP_UPDATE( vm, sz );
576 :
577 : /* Exact same as memmove, except also check overlap.
578 : https://github.com/anza-xyz/agave/blob/v2.2.17/programs/bpf_loader/src/syscalls/mem_ops.rs#L45 */
579 48 : FD_VM_MEM_CHECK_NON_OVERLAPPING( vm, src_vaddr, sz, dst_vaddr, sz );
580 :
581 30 : return fd_vm_memmove( vm, dst_vaddr, src_vaddr, sz );
582 48 : }
583 :
584 : int
585 : fd_vm_syscall_sol_memcmp( /**/ void * _vm,
586 : /**/ ulong m0_vaddr,
587 : /**/ ulong m1_vaddr,
588 : /**/ ulong sz,
589 : /**/ ulong out_vaddr,
590 : FD_PARAM_UNUSED ulong r5,
591 15 : /**/ ulong * _ret ) {
592 15 : *_ret = 0;
593 15 : fd_vm_t * vm = (fd_vm_t *)_vm;
594 :
595 : /* https://github.com/anza-xyz/agave/blob/v2.2.17/programs/bpf_loader/src/syscalls/mem_ops.rs#L84 */
596 :
597 15 : FD_VM_CU_MEM_OP_UPDATE( vm, sz );
598 :
599 : /* Note: though this behaves like a normal C-style memcmp, we can't
600 : use the compilers / libc memcmp directly because the specification
601 : doesn't provide strong enough guarantees about the return value (it
602 : only promises the sign). */
603 :
604 15 : if( !vm->direct_mapping ) {
605 0 : uchar const * m0 = (uchar const *)FD_VM_MEM_SLICE_HADDR_LD( vm, m0_vaddr, FD_VM_ALIGN_RUST_U8, sz );
606 0 : uchar const * m1 = (uchar const *)FD_VM_MEM_SLICE_HADDR_LD( vm, m1_vaddr, FD_VM_ALIGN_RUST_U8, sz );
607 :
608 : /* Silly that this doesn't use r0 to return ... slower, more edge
609 : case, different from libc style memcmp, harder to callers to use,
610 : etc ... probably too late to do anything about it now ... sigh */
611 :
612 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/programs/bpf_loader/src/syscalls/mem_ops.rs#L121-L125 */
613 0 : fd_vm_haddr_query_t cmp_result_ref_mut_query = {
614 0 : .vaddr = out_vaddr,
615 0 : .align = FD_VM_ALIGN_RUST_I32,
616 0 : .sz = 4UL,
617 0 : .is_slice = 0,
618 0 : };
619 :
620 0 : fd_vm_haddr_query_t * queries[] = { &cmp_result_ref_mut_query };
621 0 : FD_VM_TRANSLATE_MUT( vm, queries );
622 :
623 0 : int out = 0;
624 0 : for( ulong i=0UL; i<sz; i++ ) {
625 0 : int i0 = (int)m0[i];
626 0 : int i1 = (int)m1[i];
627 0 : if( i0!=i1 ) {
628 0 : out = i0 - i1;
629 0 : break;
630 0 : }
631 0 : }
632 :
633 0 : fd_memcpy( cmp_result_ref_mut_query.haddr, &out, 4UL ); /* Sigh ... see note above (and might be unaligned ... double sigh) */
634 :
635 0 : return FD_VM_SUCCESS;
636 15 : } else {
637 : /* In the case that direct mapping is enabled, the behavior for memcmps
638 : differ significantly from the non-dm case. The key difference is that
639 : invalid loads will instantly lead to errors in the non-dm case. However,
640 : when direct mapping is enabled, we will first try to memcmp the largest
641 : size valid chunk first, and will exit successfully if a difference is
642 : found without aborting from the VM. A chunk is defined as the largest
643 : valid vaddr range in both memory regions that doesn't span multiple
644 : regions.
645 :
646 : Example:
647 : fd_vm_syscall_sol_memcmp( vm, m0_addr : 0x4000, m1_vaddr : 0x2000, 0x200, ... );
648 : m0's region: m0_addr 0x4000 -> 0x4000 + 0x50 (region sz 0x50)
649 : m1's region: m1_addr 0x2000 -> 0x2000 + 0x100 (region sz 0x100)
650 : sz: 0x200
651 :
652 : Case 1: 0x4000 -> 0x4050 does have the same bytes as 0x2000 -> 0x2050
653 : Case 2: 0x4000 -> 0x4050 does NOT have the same bytes as 0x2000 -> 0x2050
654 :
655 : Pre-DM:
656 : This will fail out before any bytes are compared because the memory
657 : translation is done first.
658 :
659 : Post-DM:
660 : For case 1, the memcmp will return an error and the VM will exit because
661 : the memcmp will eventually try to access 0x4051 which is invalid. First
662 : 0x50 bytes are compared, but the next chunk will lead to an invalid
663 : access.
664 :
665 : For case 2, the memcmp will first translate the first 0x50 bytes and will
666 : see that the bytes are not the same. This will lead to the syscall
667 : exiting out successfully without detecting the access violation.
668 :
669 : https://github.com/anza-xyz/agave/blob/v2.0.10/programs/bpf_loader/src/syscalls/mem_ops.rs#L213
670 : */
671 :
672 : /* TODO: Refactor to use `FD_VM_TRANSLATE_MUT` macro when direct mapping is rewritten */
673 15 : void * _out = FD_VM_MEM_HADDR_ST( vm, out_vaddr, FD_VM_ALIGN_RUST_I32, 4UL );
674 0 : int out = 0;
675 :
676 : /* Lookup host address chunks. Try to do a standard memcpy if the regions
677 : do not cross memory regions. The translation logic is different if the
678 : the virtual address region is the input region vs. not. See the comment
679 : in fd_bpf_loader_serialization for more details on how the input
680 : region is different from other regions. The input data region will try
681 : to lookup the number of remaining bytes in the specific data region. If
682 : the memory access is not in the input data region, assume the bytes in
683 : the current region are bound by the size of the remaining bytes in the
684 : region. */
685 :
686 12 : ulong m0_region = FD_VADDR_TO_REGION( m0_vaddr );
687 12 : ulong m0_offset = m0_vaddr & FD_VM_OFFSET_MASK;
688 12 : ulong m0_region_idx = 0UL;
689 12 : ulong m0_bytes_in_cur_region = sz;
690 12 : uchar * m0_haddr = NULL;
691 12 : if( m0_region==FD_VM_INPUT_REGION ) {
692 6 : m0_region_idx = fd_vm_get_input_mem_region_idx( vm, m0_offset );
693 6 : m0_haddr = (uchar*)(vm->input_mem_regions[ m0_region_idx ].haddr + m0_offset - vm->input_mem_regions[ m0_region_idx ].vaddr_offset);
694 6 : m0_bytes_in_cur_region = fd_ulong_min( sz, fd_ulong_sat_sub( vm->input_mem_regions[ m0_region_idx ].region_sz,
695 6 : ((ulong)m0_haddr - vm->input_mem_regions[ m0_region_idx ].haddr) ) );
696 6 : } else {
697 : /* We can safely load a slice of 1 byte here because we know that we will
698 : not ever read more than the number of bytes that are left in the
699 : region. */
700 6 : m0_bytes_in_cur_region = fd_ulong_min( sz, vm->region_ld_sz[ m0_region ] - m0_offset );
701 12 : m0_haddr = (uchar *)FD_VM_MEM_SLICE_HADDR_LD_SZ_UNCHECKED( vm, m0_vaddr, FD_VM_ALIGN_RUST_U8 );
702 12 : }
703 :
704 12 : ulong m1_region = FD_VADDR_TO_REGION( m1_vaddr );
705 12 : ulong m1_offset = m1_vaddr & FD_VM_OFFSET_MASK;
706 12 : ulong m1_region_idx = 0UL;
707 12 : ulong m1_bytes_in_cur_region = sz;
708 12 : uchar * m1_haddr = NULL;
709 12 : if( m1_region==FD_VM_INPUT_REGION ) {
710 6 : m1_region_idx = fd_vm_get_input_mem_region_idx( vm, m1_offset );
711 6 : m1_haddr = (uchar*)(vm->input_mem_regions[ m1_region_idx ].haddr + m1_offset - vm->input_mem_regions[ m1_region_idx ].vaddr_offset);
712 6 : m1_bytes_in_cur_region = fd_ulong_min( sz, fd_ulong_sat_sub( vm->input_mem_regions[ m1_region_idx ].region_sz,
713 6 : ((ulong)m1_haddr - vm->input_mem_regions[ m1_region_idx ].haddr) ) );
714 6 : } else {
715 6 : m1_bytes_in_cur_region = fd_ulong_min( sz, vm->region_ld_sz[ m1_region ] - m1_offset );
716 12 : m1_haddr = (uchar *)FD_VM_MEM_SLICE_HADDR_LD_SZ_UNCHECKED( vm, m1_vaddr, FD_VM_ALIGN_RUST_U8 );
717 12 : }
718 :
719 : /* Case where the operation spans multiple regions. Copy over the bytes
720 : from each region while iterating to the next one. */
721 : /* TODO: An optimization would be to memcmp chunks at once */
722 12 : ulong m0_idx = 0UL;
723 12 : ulong m1_idx = 0UL;
724 612 : for( ulong i=0UL; i<sz; i++ ) {
725 609 : if( FD_UNLIKELY( !m0_bytes_in_cur_region ) ) {
726 : /* If the memory is not in the input region or it is the last input
727 : memory region, that means that if we don't exit now we will have
728 : an access violation. */
729 6 : if( FD_UNLIKELY( m0_region!=FD_VM_INPUT_REGION || ++m0_region_idx>=vm->input_mem_regions_cnt ) ) {
730 0 : FD_VM_ERR_FOR_LOG_EBPF( vm, FD_VM_ERR_EBPF_ACCESS_VIOLATION );
731 0 : return FD_VM_SYSCALL_ERR_SEGFAULT;
732 0 : }
733 6 : if( FD_UNLIKELY( vm->input_mem_regions[ m0_region_idx-1UL ].is_acct_data != vm->input_mem_regions[ m0_region_idx ].is_acct_data ) ) {
734 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_LENGTH );
735 0 : return FD_VM_SYSCALL_ERR_SEGFAULT;
736 0 : }
737 : /* Otherwise, query the next input region. */
738 6 : m0_haddr = (uchar*)vm->input_mem_regions[ m0_region_idx ].haddr;
739 6 : m0_idx = 0UL;
740 6 : m0_bytes_in_cur_region = vm->input_mem_regions[ m0_region_idx ].region_sz;
741 6 : }
742 609 : if( FD_UNLIKELY( !m1_bytes_in_cur_region ) ) {
743 0 : if( FD_UNLIKELY( m1_region!=FD_VM_INPUT_REGION || ++m1_region_idx>=vm->input_mem_regions_cnt ) ) {
744 0 : FD_VM_ERR_FOR_LOG_EBPF( vm, FD_VM_ERR_EBPF_ACCESS_VIOLATION );
745 0 : return FD_VM_SYSCALL_ERR_SEGFAULT;
746 0 : }
747 0 : if( FD_UNLIKELY( vm->input_mem_regions[ m1_region_idx-1UL ].is_acct_data != vm->input_mem_regions[ m1_region_idx ].is_acct_data ) ) {
748 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_LENGTH );
749 0 : return FD_VM_SYSCALL_ERR_SEGFAULT;
750 0 : }
751 0 : m1_haddr = (uchar*)vm->input_mem_regions[ m1_region_idx ].haddr;
752 0 : m1_idx = 0UL;
753 0 : m1_bytes_in_cur_region = vm->input_mem_regions[ m1_region_idx ].region_sz;
754 0 : }
755 :
756 609 : int i0 = (int)m0_haddr[ m0_idx ];
757 609 : int i1 = (int)m1_haddr[ m1_idx ];
758 609 : if( i0!=i1 ) {
759 9 : out = i0 - i1;
760 9 : break;
761 9 : }
762 :
763 600 : m0_bytes_in_cur_region--;
764 600 : m1_bytes_in_cur_region--;
765 600 : m0_idx++;
766 600 : m1_idx++;
767 600 : }
768 12 : fd_memcpy( _out, &out, 4UL ); /* Sigh ... see note above (and might be unaligned ... double sigh) */
769 12 : return FD_VM_SUCCESS;
770 12 : }
771 15 : }
772 :
773 : int
774 : fd_vm_syscall_sol_memset( /**/ void * _vm,
775 : /**/ ulong dst_vaddr,
776 : /**/ ulong c,
777 : /**/ ulong sz,
778 : FD_PARAM_UNUSED ulong r4,
779 : FD_PARAM_UNUSED ulong r5,
780 27 : /**/ ulong * _ret ) {
781 27 : fd_vm_t * vm = (fd_vm_t *)_vm;
782 27 : *_ret = 0;
783 :
784 : /* https://github.com/anza-xyz/agave/blob/v2.2.17/programs/bpf_loader/src/syscalls/mem_ops.rs#L142 */
785 :
786 27 : FD_VM_CU_MEM_OP_UPDATE( vm, sz );
787 :
788 27 : if( FD_UNLIKELY( !sz ) ) {
789 0 : return FD_VM_SUCCESS;
790 0 : }
791 :
792 27 : ulong region = FD_VADDR_TO_REGION( dst_vaddr );
793 27 : ulong offset = dst_vaddr & FD_VM_OFFSET_MASK;
794 27 : uchar * haddr;
795 :
796 27 : int b = (int)(c & 255UL);
797 :
798 27 : if( !vm->direct_mapping ) {
799 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/programs/bpf_loader/src/syscalls/mem_ops.rs#L155-L159 */
800 9 : fd_vm_haddr_query_t haddr_query = {
801 9 : .vaddr = dst_vaddr,
802 9 : .align = FD_VM_ALIGN_RUST_U8,
803 9 : .sz = sz,
804 9 : .is_slice = 1,
805 9 : };
806 :
807 9 : fd_vm_haddr_query_t * queries[] = { &haddr_query };
808 9 : FD_VM_TRANSLATE_MUT( vm, queries );
809 6 : fd_memset( haddr_query.haddr, b, sz );
810 18 : } else if( region!=FD_VM_INPUT_REGION ) {
811 : /* Here we special case non-input region memsets: we try to memset
812 : as many bytes as possible until it reaches an unwritable section.
813 : This is done in order to ensure error-code conformance with
814 : Agave. */
815 9 : haddr = (uchar*)FD_VM_MEM_HADDR_ST_FAST( vm, dst_vaddr );
816 9 : ulong bytes_in_cur_region = fd_ulong_sat_sub( vm->region_st_sz[ region ], offset );
817 9 : ulong bytes_to_set = fd_ulong_min( sz, bytes_in_cur_region );
818 9 : fd_memset( haddr, b, bytes_to_set );
819 9 : if( FD_UNLIKELY( bytes_to_set<sz ) ) {
820 3 : FD_VM_ERR_FOR_LOG_EBPF( vm, FD_VM_ERR_EBPF_ACCESS_VIOLATION );
821 3 : return FD_VM_SYSCALL_ERR_SEGFAULT;
822 3 : }
823 9 : } else {
824 : /* In this case, we are in the input region AND direct mapping is
825 : enabled. Get the haddr and input region and check if it's
826 : writable. This means that we may potentially iterate over
827 : multiple regions. */
828 9 : ulong region_idx;
829 9 : FD_VM_MEM_HADDR_AND_REGION_IDX_FROM_INPUT_REGION_CHECKED( vm, offset, region_idx, haddr );
830 0 : ulong offset_in_cur_region = offset - vm->input_mem_regions[ region_idx ].vaddr_offset;
831 9 : ulong bytes_in_cur_region = fd_ulong_sat_sub( vm->input_mem_regions[ region_idx ].region_sz, offset_in_cur_region );
832 :
833 : /* Check that current region is writable */
834 9 : if( FD_UNLIKELY( !vm->input_mem_regions[ region_idx ].is_writable ) ) {
835 0 : FD_VM_ERR_FOR_LOG_EBPF( vm, FD_VM_ERR_EBPF_ACCESS_VIOLATION );
836 0 : return FD_VM_SYSCALL_ERR_SEGFAULT;
837 0 : }
838 :
839 : /* Memset goes into multiple regions. */
840 30 : while( sz>0UL ) {
841 :
842 : /* Memset bytes */
843 30 : ulong num_bytes_to_set = fd_ulong_min( sz, bytes_in_cur_region );
844 30 : fd_memset( haddr, b, num_bytes_to_set );
845 30 : sz -= num_bytes_to_set;
846 :
847 30 : if( !sz ) {
848 6 : break;
849 6 : }
850 :
851 : /* If no more regions left, break. */
852 24 : if( ++region_idx==vm->input_mem_regions_cnt ) {
853 0 : break;
854 0 : }
855 :
856 : /* Check that new region is writable. */
857 24 : if( FD_UNLIKELY( !vm->input_mem_regions[ region_idx ].is_writable ) ) {
858 3 : break;
859 3 : }
860 :
861 : /* If new region crosses into/out of account region, error out. */
862 21 : if( FD_UNLIKELY( vm->input_mem_regions[ region_idx ].is_acct_data !=
863 21 : vm->input_mem_regions[ region_idx-1UL ].is_acct_data ) ) {
864 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_LENGTH );
865 0 : return FD_VM_SYSCALL_ERR_SEGFAULT;
866 0 : }
867 :
868 : /* Move haddr to next region. */
869 21 : haddr = (uchar*)vm->input_mem_regions[ region_idx ].haddr;
870 21 : bytes_in_cur_region = vm->input_mem_regions[ region_idx ].region_sz;
871 21 : }
872 :
873 : /* If we were not able to successfully set all the bytes, throw an error. */
874 9 : if( FD_UNLIKELY( sz>0 ) ) {
875 3 : FD_VM_ERR_FOR_LOG_EBPF( vm, FD_VM_ERR_EBPF_ACCESS_VIOLATION );
876 3 : return FD_VM_SYSCALL_ERR_SEGFAULT;
877 3 : }
878 9 : }
879 18 : return FD_VM_SUCCESS;
880 27 : }
|