Line data Source code
1 : #ifndef HEADER_fd_src_flamenco_vm_fd_vm_private_h
2 : #define HEADER_fd_src_flamenco_vm_fd_vm_private_h
3 :
4 : #include "fd_vm.h"
5 :
6 : #include "../runtime/fd_runtime_const.h"
7 : #include "../runtime/fd_runtime.h"
8 : #include "fd_vm_base.h"
9 :
10 : /* FD_VM_ALIGN_RUST_{} define the alignments for relevant rust types.
11 : Alignments are derived with std::mem::align_of::<T>() and are enforced
12 : by the VM (with the exception of v1 loader).
13 :
14 : In our implementation, when calling FD_VM_MEM_HADDR_ST / FD_VM_MEM_HADDR_LD,
15 : we need to make sure we're passing the correct alignment based on the Rust
16 : type in the corresponding mapping in Agave.
17 :
18 : FD_VM_ALIGN_RUST_{} has been generated with this Rust code:
19 : ```rust
20 : pub type Epoch = u64;
21 : pub struct Pubkey(pub [u8; 32]);
22 : pub struct AccountMeta {
23 : pub lamports: u64,
24 : pub rent_epoch: Epoch,
25 : pub owner: Pubkey,
26 : pub executable: bool,
27 : }
28 :
29 : pub struct PodScalar(pub [u8; 32]);
30 :
31 : fn main() {
32 : println!("u8: {}", std::mem::align_of::<u8>());
33 : println!("u32: {}", std::mem::align_of::<u32>());
34 : println!("u64: {}", std::mem::align_of::<u64>());
35 : println!("u128: {}", std::mem::align_of::<u128>());
36 : println!("&[u8]: {}", std::mem::align_of::<&[u8]>());
37 : println!("AccountMeta: {}", std::mem::align_of::<AccountMeta>());
38 : println!("PodScalar: {}", std::mem::align_of::<PodScalar>());
39 : println!("Pubkey: {}", std::mem::align_of::<Pubkey>());
40 : }
41 : ``` */
42 :
43 8055 : #define FD_VM_ALIGN_RUST_U8 (1UL)
44 : #define FD_VM_ALIGN_RUST_U32 (4UL)
45 1647 : #define FD_VM_ALIGN_RUST_I32 (4UL)
46 : #define FD_VM_ALIGN_RUST_U64 (8UL)
47 : #define FD_VM_ALIGN_RUST_SLICE_U8_REF (8UL)
48 1052 : #define FD_VM_ALIGN_RUST_POD_U8_ARRAY (1UL)
49 36 : #define FD_VM_ALIGN_RUST_PUBKEY (1UL)
50 143 : #define FD_VM_ALIGN_RUST_SYSVAR_CLOCK (8UL)
51 73 : #define FD_VM_ALIGN_RUST_SYSVAR_EPOCH_SCHEDULE (8UL)
52 147 : #define FD_VM_ALIGN_RUST_SYSVAR_RENT (8UL)
53 90 : #define FD_VM_ALIGN_RUST_SYSVAR_LAST_RESTART_SLOT (8UL)
54 : #define FD_VM_ALIGN_RUST_SYSVAR_EPOCH_REWARDS (16UL)
55 :
56 : /* fd_vm_vec_t is the in-memory representation of a vector descriptor.
57 : Equal in layout to the Rust slice header &[_] and various vector
58 : types in the C version of the syscall API. */
59 : /* FIXME: WHEN IS VADDR NULL AND/OR SZ 0 OKAY? */
60 : /* FIXME: MOVE FD_VM_RUST_VEC_T FROM SYSCALL/FD_VM_CPI.H HERE TOO? */
61 :
62 : #define FD_VM_VEC_ALIGN FD_VM_ALIGN_RUST_SLICE_U8_REF
63 0 : #define FD_VM_VEC_SIZE (16UL)
64 :
65 : struct __attribute__((packed)) fd_vm_vec {
66 : ulong addr; /* FIXME: NAME -> VADDR */
67 : ulong len; /* FIXME: NAME -> SZ */
68 : };
69 :
70 : typedef struct fd_vm_vec fd_vm_vec_t;
71 :
72 : FD_STATIC_ASSERT( sizeof(fd_vm_vec_t)==FD_VM_VEC_SIZE, fd_vm_vec size mismatch );
73 :
74 : /* SBPF version and features
75 : https://github.com/anza-xyz/sbpf/blob/v0.12.2/src/program.rs#L28
76 : Note: SIMDs enable or disable features, e.g. BPF instructions.
77 : If we have macros with names ENABLE vs DISABLE, we have the advantage that
78 : the condition is always pretty clear: sbpf_version <= activation_version,
79 : but the disadvantage of inconsistent names.
80 : Vice versa, calling everything ENABLE has the risk to invert a <= with a >=
81 : and create a huge mess.
82 : We define both, so hopefully it's foolproof. */
83 :
84 : /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/program.rs#L28-L93 */
85 : /* SIMD-0166 */
86 12270560 : #define FD_VM_SBPF_MANUAL_STACK_FRAME_BUMP(v) ( v == FD_SBPF_V1 || v == FD_SBPF_V2 ) /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/program.rs#L32-L34 */
87 : #define FD_VM_SBPF_STACK_FRAME_GAPS(v) ( v == FD_SBPF_V0 ) /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/program.rs#L36-L38 */
88 : /* SIMD-0174 */
89 844318 : #define FD_VM_SBPF_ENABLE_PQR(v) ( v == FD_SBPF_V2 ) /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/program.rs#L41-L43 */
90 67696 : #define FD_VM_SBPF_EXPLICIT_SIGN_EXTENSION_OF_RESULTS(v) ( v == FD_SBPF_V2 ) /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/program.rs#L45-L47 */
91 33848 : #define FD_VM_SBPF_SWAP_SUB_REG_IMM_OPERANDS(v) ( v == FD_SBPF_V2 ) /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/program.rs#L49-L51 */
92 21242 : #define FD_VM_SBPF_DISABLE_NEG(v) ( v == FD_SBPF_V2 ) /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/program.rs#L53-L55 */
93 : /* SIMD-0173 */
94 4318 : #define FD_VM_SBPF_CALLX_USES_SRC_REG(v) ( v == FD_SBPF_V2 ) /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/program.rs#L58-L60 */
95 42484 : #define FD_VM_SBPF_DISABLE_LDDW(v) ( v == FD_SBPF_V2 ) /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/program.rs#L62-L64 */
96 21242 : #define FD_VM_SBPF_DISABLE_LE(v) ( v == FD_SBPF_V2 ) /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/program.rs#L66-L68 */
97 509808 : #define FD_VM_SBPF_MOVE_MEMORY_IX_CLASSES(v) ( v == FD_SBPF_V2 ) /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/program.rs#L70-L72 */
98 : /* SIMD-0178 */
99 16924 : #define FD_VM_SBPF_STATIC_SYSCALLS(v) ( v >= FD_SBPF_V3 ) /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/program.rs#L75-L77 */
100 : /* SIMD-0189 */
101 : #define FD_VM_SBPF_ENABLE_STRICTER_ELF_HEADERS(v) ( v >= FD_SBPF_V3 ) /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/program.rs#L79-L81 */
102 32201 : #define FD_VM_SBPF_ENABLE_LOWER_RODATA_VADDR(v) ( v >= FD_SBPF_V3 ) /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/program.rs#L83-L85 */
103 : /* SIMD-0377 */
104 376646 : #define FD_VM_SBPF_ENABLE_JMP32(v) ( v >= FD_SBPF_V3 ) /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/program.rs#L87-L89 */
105 3122 : #define FD_VM_SBPF_CALLX_USES_DST_REG(v) ( v >= FD_SBPF_V3 ) /* https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/program.rs#L91-L93 */
106 :
107 1184965 : #define FD_VM_OFFSET_MASK (0xffffffffUL)
108 :
109 : /* https://github.com/anza-xyz/agave/blob/v3.0.1/transaction-context/src/lib.rs#L32 */
110 0 : #define FD_MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION ((long)(FD_RUNTIME_ACC_SZ_MAX * 2UL))
111 :
112 : /* Kept equal to the canonical constant in fd_runtime_const.h, which the
113 : bpf loader serialization buffer bound relies on. */
114 : FD_STATIC_ASSERT( FD_MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION==(long)FD_RUNTIME_ACC_DATA_GROWTH_MAX_PER_TXN, account_data_growth_cap );
115 :
116 : FD_PROTOTYPES_BEGIN
117 :
118 : /* Error logging handholding assertions */
119 :
120 : #ifdef FD_RUNTIME_ERR_HANDHOLDING
121 : /* Asserts that the error and error kind are populated (non-zero) */
122 : #define FD_VM_TEST_ERR_EXISTS( vm ) \
123 : FD_TEST( vm->instr_ctx->txn_out->err.exec_err ); \
124 : FD_TEST( vm->instr_ctx->txn_out->err.exec_err_kind )
125 :
126 : /* Used prior to a FD_VM_ERR_FOR_LOG_INSTR call to deliberately
127 : bypass overwrite handholding checks.
128 : Only use this if you know what you're doing. */
129 : #define FD_VM_PREPARE_ERR_OVERWRITE( vm ) \
130 : vm->instr_ctx->txn_out->err.exec_err = 0; \
131 : vm->instr_ctx->txn_out->err.exec_err_kind = 0
132 :
133 : /* Asserts that the error and error kind are not populated (zero) */
134 : #define FD_VM_TEST_ERR_OVERWRITE( vm ) \
135 : FD_TEST( !vm->instr_ctx->txn_out->err.exec_err ); \
136 : FD_TEST( !vm->instr_ctx->txn_out->err.exec_err_kind )
137 : #else
138 951 : #define FD_VM_TEST_ERR_EXISTS( vm ) ( ( void )0 )
139 2589 : #define FD_VM_PREPARE_ERR_OVERWRITE( vm ) ( ( void )0 )
140 4021 : #define FD_VM_TEST_ERR_OVERWRITE( vm ) ( ( void )0 )
141 : #endif
142 :
143 : /* Log error within the instr_ctx to match Agave/Rust error. */
144 :
145 2228 : #define FD_VM_ERR_FOR_LOG_EBPF( vm, err_ ) (__extension__({ \
146 2228 : FD_VM_TEST_ERR_OVERWRITE( vm ); \
147 2228 : vm->instr_ctx->txn_out->err.exec_err = err_; \
148 2228 : vm->instr_ctx->txn_out->err.exec_err_kind = FD_EXECUTOR_ERR_KIND_EBPF; \
149 2228 : }))
150 :
151 1048 : #define FD_VM_ERR_FOR_LOG_SYSCALL( vm, err_ ) (__extension__({ \
152 1048 : FD_VM_TEST_ERR_OVERWRITE( vm ); \
153 1048 : vm->instr_ctx->txn_out->err.exec_err = err_; \
154 1048 : vm->instr_ctx->txn_out->err.exec_err_kind = FD_EXECUTOR_ERR_KIND_SYSCALL; \
155 1048 : }))
156 :
157 745 : #define FD_VM_ERR_FOR_LOG_INSTR( vm, err_ ) (__extension__({ \
158 745 : FD_VM_TEST_ERR_OVERWRITE( vm ); \
159 745 : vm->instr_ctx->txn_out->err.exec_err = err_; \
160 745 : vm->instr_ctx->txn_out->err.exec_err_kind = FD_EXECUTOR_ERR_KIND_INSTR; \
161 745 : }))
162 :
163 1183598 : #define FD_VADDR_TO_REGION( _vaddr ) fd_ulong_min( (_vaddr) >> FD_VM_MEM_MAP_REGION_VIRT_ADDR_BITS, FD_VM_HIGH_REGION )
164 :
165 : /* fd_vm_instr APIs ***************************************************/
166 :
167 : /* FIXME: MIGRATE FD_SBPF_INSTR_T STUFF TO THIS API */
168 :
169 : /* fd_vm_instr returns the SBPF instruction word corresponding to the
170 : given fields. */
171 :
172 : FD_FN_CONST static inline ulong
173 : fd_vm_instr( ulong opcode, /* Assumed valid */
174 : ulong dst, /* Assumed in [0,FD_VM_REG_CNT) */
175 : ulong src, /* Assumed in [0,FD_VM_REG_CNT) */
176 : short offset,
177 0 : uint imm ) {
178 0 : return opcode | (dst<<8) | (src<<12) | (((ulong)(ushort)offset)<<16) | (((ulong)imm)<<32);
179 0 : }
180 :
181 : /* fd_vm_instr_* return the SBPF instruction field for the given word.
182 : fd_vm_instr_{normal,mem}_* only apply to {normal,mem} opclass
183 : instructions. */
184 :
185 11621709 : FD_FN_CONST static inline ulong fd_vm_instr_opcode( ulong instr ) { return instr & 255UL; } /* In [0,256) */
186 11590507 : FD_FN_CONST static inline ulong fd_vm_instr_dst ( ulong instr ) { return ((instr>> 8) & 15UL); } /* In [0,16) */
187 11611540 : FD_FN_CONST static inline ulong fd_vm_instr_src ( ulong instr ) { return ((instr>>12) & 15UL); } /* In [0,16) */
188 11591893 : FD_FN_CONST static inline ulong fd_vm_instr_offset( ulong instr ) { return (ulong)(long)(short)(ushort)(instr>>16); }
189 12257794 : FD_FN_CONST static inline uint fd_vm_instr_imm ( ulong instr ) { return (uint)(instr>>32); }
190 :
191 0 : FD_FN_CONST static inline ulong fd_vm_instr_opclass ( ulong instr ) { return instr & 7UL; } /* In [0,8) */
192 0 : FD_FN_CONST static inline ulong fd_vm_instr_normal_opsrc ( ulong instr ) { return (instr>>3) & 1UL; } /* In [0,2) */
193 0 : FD_FN_CONST static inline ulong fd_vm_instr_normal_opmode ( ulong instr ) { return (instr>>4) & 15UL; } /* In [0,16) */
194 0 : FD_FN_CONST static inline ulong fd_vm_instr_mem_opsize ( ulong instr ) { return (instr>>3) & 3UL; } /* In [0,4) */
195 0 : FD_FN_CONST static inline ulong fd_vm_instr_mem_opaddrmode( ulong instr ) { return (instr>>5) & 7UL; } /* In [0,16) */
196 :
197 : /* fd_vm_mem API ******************************************************/
198 :
199 : /* fd_vm_mem APIs support the fast mapping of virtual address ranges to
200 : host address ranges. The SBPF virtual address space consists of
201 : 5 consecutive 4 GiB regions (see fd_vm_base.h for layout). The
202 : mappable size of each region is less than 4 GiB (as implied by
203 : FD_VM_MEM_MAP_REGION_SZ==2^32-1 and that Solana protocol limits are
204 : much smaller still), so a valid virtual address range cannot span
205 : multiple regions. */
206 :
207 : /* fd_vm_mem_cfg configures the vm's tlb arrays. Assumes vm is valid
208 : and vm already has configured the rodata, stack, heap and input
209 : regions. Returns vm. */
210 :
211 : static inline fd_vm_t *
212 16103 : fd_vm_mem_cfg( fd_vm_t * vm ) {
213 16103 : if( FD_VM_SBPF_ENABLE_LOWER_RODATA_VADDR( vm->sbpf_version ) ) {
214 : /* In SBPF V3, rodata is at vaddr 0:
215 : [rodata@0, empty@0x100000000, stack@0x200000000, heap@0x300000000, input@0x400000000]
216 :
217 : This is so that we don't need to do any relocations - all rodata
218 : accesses are direct offsets from 0.
219 :
220 : https://github.com/anza-xyz/sbpf/blob/v0.14.4/src/elf.rs#L358-L362
221 : https://github.com/anza-xyz/agave/blob/v4.0.0-beta.4/syscalls/src/lib.rs#L346 */
222 1420 : vm->region_haddr[0] = (ulong)vm->rodata; vm->region_ld_sz[0] = (uint)vm->rodata_sz; vm->region_st_sz[0] = (uint)0UL;
223 1420 : vm->region_haddr[FD_VM_PROG_REGION] = 0UL; vm->region_ld_sz[FD_VM_PROG_REGION] = (uint)0UL; vm->region_st_sz[FD_VM_PROG_REGION] = (uint)0UL;
224 14683 : } else {
225 : /* V0-V2: region 0 unused, rodata at region 1 (vaddr 0x100000000) */
226 14683 : vm->region_haddr[0] = 0UL; vm->region_ld_sz[0] = (uint)0UL; vm->region_st_sz[0] = (uint)0UL;
227 14683 : vm->region_haddr[FD_VM_PROG_REGION] = (ulong)vm->rodata; vm->region_ld_sz[FD_VM_PROG_REGION] = (uint)vm->rodata_sz; vm->region_st_sz[FD_VM_PROG_REGION] = (uint)0UL;
228 14683 : }
229 16103 : vm->region_haddr[FD_VM_STACK_REGION] = (ulong)vm->stack; vm->region_ld_sz[FD_VM_STACK_REGION] = (uint)vm->stack_clean; vm->region_st_sz[FD_VM_STACK_REGION] = (uint)vm->stack_clean;
230 16103 : vm->region_haddr[FD_VM_HEAP_REGION] = (ulong)vm->heap; vm->region_ld_sz[FD_VM_HEAP_REGION] = (uint)vm->heap_clean; vm->region_st_sz[FD_VM_HEAP_REGION] = (uint)vm->heap_clean;
231 16103 : vm->region_haddr[5] = 0UL; vm->region_ld_sz[5] = (uint)0UL; vm->region_st_sz[5] = (uint)0UL;
232 16103 : return vm;
233 16103 : }
234 :
235 : /* Simplified version of Agave's `generate_access_violation()` function
236 : that simply returns either FD_VM_ERR_EBPF_ACCESS_VIOLATION or
237 : FD_VM_ERR_EBPF_STACK_ACCESS_VIOLATION. This has no consensus
238 : effects and is purely for logging purposes for fuzzing. Returns
239 : FD_VM_ERR_EBPF_STACK_ACCESS_VIOLATION if the provided vaddr is in the
240 : stack (0x200000000) and FD_VM_ERR_EBPF_ACCESS_VIOLATION otherwise.
241 :
242 : https://github.com/anza-xyz/sbpf/blob/v0.11.1/src/memory_region.rs#L834-L869 */
243 : static FD_FN_PURE inline int
244 769 : fd_vm_generate_access_violation( ulong vaddr, ulong sbpf_version ) {
245 : /* rel_offset can be negative because there is an edge case where the
246 : first "frame" right before the stack region should also throw a
247 : stack access violation. */
248 769 : long rel_offset = fd_long_sat_sub( (long)vaddr, (long)FD_VM_MEM_MAP_STACK_REGION_START );
249 769 : long stack_frame = rel_offset / (long)FD_VM_STACK_FRAME_SZ;
250 769 : if( !fd_sbpf_manual_stack_frame_bump_enabled( sbpf_version ) &&
251 769 : stack_frame>=-1L && stack_frame<=(long)FD_VM_MAX_CALL_DEPTH ) {
252 39 : return FD_VM_ERR_EBPF_STACK_ACCESS_VIOLATION;
253 39 : }
254 730 : return FD_VM_ERR_EBPF_ACCESS_VIOLATION;
255 769 : }
256 :
257 : /* fd_vm_mem_haddr translates the vaddr range [vaddr,vaddr+sz) (in
258 : infinite precision math) into the non-wrapping haddr range
259 : [haddr,haddr+sz). On success, returns haddr and every byte in the
260 : haddr range is a valid address. On failure, returns sentinel and
261 : there was at least one byte in the virtual address range that did not
262 : have a corresponding byte in the host address range.
263 :
264 : IMPORTANT SAFETY TIP! When sz==0, the return value currently is
265 : arbitrary. This is often fine as there should be no
266 : actual accesses to a sz==0 region. However, this also means that
267 : testing return for sentinel is insufficient to tell if mapping
268 : failed. That is, assuming sentinel is a location that could never
269 : happen on success:
270 :
271 : sz!=0 and ret!=sentinel -> success
272 : sz!=0 and ret==sentinel -> failure
273 : sz==0 -> ignore ret, application specific handling
274 :
275 : With ~O(2) extra fast branchless instructions, the below could be
276 : tweaked in the sz==0 case to return NULL or return a non-NULL
277 : sentinel value. What is most optimal practically depends on how
278 : empty ranges and NULL vaddr handling is defined in the application. */
279 :
280 :
281 : /* fd_vm_get_input_mem_region_idx returns the index into the input memory
282 : region array with the largest region offset that is <= the offset that
283 : is passed in. This function makes NO guarantees about the input being
284 : a valid input region offset; the caller is responsible for safely handling
285 : it. */
286 : static inline ulong
287 71149 : fd_vm_get_input_mem_region_idx( fd_vm_t const * vm, ulong offset ) {
288 71149 : uint left = 0U;
289 71149 : uint right = vm->input_mem_regions_cnt - 1U;
290 71149 : uint mid = 0U;
291 :
292 85561 : while( left<right ) {
293 14412 : mid = (left+right) / 2U;
294 14412 : if( offset>=vm->input_mem_regions[ mid ].vaddr_offset+vm->input_mem_regions[ mid ].address_space_reserved ) {
295 6078 : left = mid + 1U;
296 8334 : } else {
297 8334 : right = mid;
298 8334 : }
299 14412 : }
300 71149 : return left;
301 71149 : }
302 :
303 : /* If the region is an account, handle the resizing logic. This logic
304 : corresponds to
305 : solana_transaction_context::TransactionContext::access_violation_handler
306 :
307 : https://github.com/anza-xyz/agave/blob/v3.0.1/transaction-context/src/lib.rs#L510-L581 */
308 : static inline void
309 : fd_vm_handle_input_mem_region_oob( fd_vm_t const * vm,
310 : ulong offset,
311 : ulong sz,
312 : ulong region_idx,
313 167 : uchar write ) {
314 : /* If virtual_address_space_adjustments is not enabled, we don't need to
315 : do anything */
316 167 : if( FD_UNLIKELY( !vm->virtual_address_space_adjustments ) ) {
317 152 : return;
318 152 : }
319 :
320 : /* If the access is not a write, we don't need to do anything
321 : https://github.com/anza-xyz/agave/blob/v3.0.1/transaction-context/src/lib.rs#L523-L525 */
322 15 : if( FD_UNLIKELY( !write ) ) {
323 15 : return;
324 15 : }
325 :
326 0 : fd_vm_input_region_t * region = &vm->input_mem_regions[ region_idx ];
327 : /* If the region is not writable, we don't need to do anything
328 : https://github.com/anza-xyz/agave/blob/v3.0.1/transaction-context/src/lib.rs#L526-L529 */
329 0 : if( FD_UNLIKELY( !region->is_writable ) ) {
330 0 : return;
331 0 : }
332 :
333 : /* Calculate the requested length
334 : https://github.com/anza-xyz/agave/blob/v3.0.1/transaction-context/src/lib.rs#L532-L535 */
335 0 : ulong requested_len = fd_ulong_sat_sub( fd_ulong_sat_add( offset, sz ), region->vaddr_offset );
336 0 : if( FD_UNLIKELY( requested_len > region->address_space_reserved ) ) {
337 0 : return;
338 0 : }
339 :
340 : /* Calculate the remaining allowed growth
341 : https://github.com/anza-xyz/agave/blob/v3.0.1/transaction-context/src/lib.rs#L549-L551 */
342 0 : long remaining_growth_signed = fd_long_sat_sub(
343 0 : FD_MAX_ACCOUNT_DATA_GROWTH_PER_TRANSACTION,
344 0 : vm->instr_ctx->txn_out->details.accounts_resize_delta );
345 0 : ulong remaining_allowed_growth = (remaining_growth_signed > 0L)
346 0 : ? (ulong)remaining_growth_signed
347 0 : : 0UL;
348 :
349 : /* If the requested length is greater than the size of the region,
350 : resize the region
351 : https://github.com/anza-xyz/agave/blob/v3.0.1/transaction-context/src/lib.rs#L553-L571 */
352 0 : if( FD_UNLIKELY( requested_len > region->region_sz ) ) {
353 : /* Calculate the new region size
354 : https://github.com/anza-xyz/agave/blob/v3.0.1/transaction-context/src/lib.rs#L558-L560 */
355 0 : ulong new_region_sz = fd_ulong_min(
356 0 : fd_ulong_min( region->address_space_reserved, FD_RUNTIME_ACC_SZ_MAX ),
357 0 : fd_ulong_sat_add( region->region_sz, remaining_allowed_growth ) );
358 :
359 : /* Resize the account and the region
360 : https://github.com/anza-xyz/agave/blob/v3.0.1/transaction-context/src/lib.rs#L569-L570 */
361 0 : if( FD_UNLIKELY( new_region_sz > region->region_sz ) ) {
362 : /* Safe because new_region_sz > region->region_sz */
363 0 : long growth = (long)(new_region_sz - region->region_sz);
364 0 : vm->instr_ctx->txn_out->details.accounts_resize_delta = fd_long_sat_add(
365 0 : vm->instr_ctx->txn_out->details.accounts_resize_delta, growth );
366 :
367 0 : ulong memset_sz = fd_ulong_sat_sub( new_region_sz, vm->acc_region_metas[ region->acc_region_meta_idx ].acc->data_len );
368 0 : fd_memset( vm->acc_region_metas[ region->acc_region_meta_idx ].acc->data+vm->acc_region_metas[ region->acc_region_meta_idx ].acc->data_len, 0, memset_sz );
369 0 : vm->acc_region_metas[ region->acc_region_meta_idx ].acc->data_len = new_region_sz;
370 : /* https://github.com/anza-xyz/agave/blob/v4.2.0-beta.1/transaction-context/src/transaction.rs#L546 */
371 0 : *vm->acc_region_metas[ region->acc_region_meta_idx ].touched = 1;
372 0 : region->region_sz = (uint)new_region_sz;
373 0 : }
374 0 : }
375 0 : }
376 :
377 : /* fd_vm_find_input_mem_region returns the translated haddr for a given
378 : offset into the input region. If an offset/sz is invalid or if an
379 : illegal write is performed, the sentinel value is returned. The
380 : access must fit within a single input memory region. */
381 : static inline ulong
382 : fd_vm_find_input_mem_region( fd_vm_t const * vm,
383 : ulong offset,
384 : ulong sz,
385 : uchar write,
386 71147 : ulong sentinel ) {
387 71147 : if( FD_UNLIKELY( vm->input_mem_regions_cnt==0 ) ) {
388 0 : return sentinel; /* Access is too large */
389 0 : }
390 :
391 : /* Binary search to find the correct memory region. If direct mapping is not
392 : enabled, then there is only 1 memory region which spans the input region. */
393 71147 : ulong region_idx = fd_vm_get_input_mem_region_idx( vm, offset );
394 71147 : if( FD_UNLIKELY( region_idx>=vm->input_mem_regions_cnt ) ) {
395 0 : return sentinel; /* Region not found */
396 0 : }
397 :
398 71147 : ulong bytes_in_region = fd_ulong_sat_sub( vm->input_mem_regions[ region_idx ].region_sz,
399 71147 : fd_ulong_sat_sub( offset, vm->input_mem_regions[ region_idx ].vaddr_offset ) );
400 :
401 : /* If the access is out of bounds, invoke the callback to handle the out of bounds access.
402 : This potentially resizes the region if necessary. */
403 71147 : if( FD_UNLIKELY( sz>bytes_in_region ) ) {
404 167 : fd_vm_handle_input_mem_region_oob( vm, offset, sz, region_idx, write );
405 167 : }
406 :
407 : /* After potentially resizing, re-check the bounds */
408 71147 : bytes_in_region = fd_ulong_sat_sub( vm->input_mem_regions[ region_idx ].region_sz,
409 71147 : fd_ulong_sat_sub( offset, vm->input_mem_regions[ region_idx ].vaddr_offset ) );
410 : /* If the access is still out of bounds, return the sentinel */
411 71147 : if( FD_UNLIKELY( sz>bytes_in_region ) ) {
412 167 : return sentinel;
413 167 : }
414 :
415 70980 : if( FD_UNLIKELY( write && vm->input_mem_regions[ region_idx ].is_writable==0U ) ) {
416 142 : return sentinel; /* Illegal write */
417 142 : }
418 :
419 : /* https://github.com/anza-xyz/agave/blob/v4.2.0-beta.1/transaction-context/src/transaction.rs#L546 */
420 70838 : if( FD_UNLIKELY( write && vm->acc_region_metas &&
421 70838 : vm->input_mem_regions[ region_idx ].acc_region_meta_idx!=ULONG_MAX ) ) {
422 1 : uchar * touched = vm->acc_region_metas[ vm->input_mem_regions[ region_idx ].acc_region_meta_idx ].touched;
423 1 : if( FD_LIKELY( touched ) ) *touched = 1;
424 1 : }
425 :
426 70838 : ulong start_region_idx = region_idx;
427 :
428 70838 : ulong adjusted_haddr = vm->input_mem_regions[ start_region_idx ].haddr + offset - vm->input_mem_regions[ start_region_idx ].vaddr_offset;
429 70838 : return adjusted_haddr;
430 70980 : }
431 :
432 : /* FD_VM_GROW_MIN amortizes {stack,heap} growth by rounding up request
433 : sizes. */
434 :
435 20886 : #define FD_VM_GROW_MIN (16384UL)
436 :
437 : /* fd_vm_{stack,heap}_grow lazily grow the stack and heap */
438 :
439 : static inline void
440 : fd_vm_region_grow( fd_vm_t * vm,
441 : ulong region,
442 : uchar * base,
443 : ulong * pclean,
444 : ulong cap,
445 20901 : ulong sz ) {
446 20901 : ulong clean = *pclean;
447 20901 : if( FD_LIKELY( sz<=clean ) ) return; /* nothing to do */
448 :
449 20886 : sz = fd_ulong_min( fd_ulong_align_up( sz, FD_VM_GROW_MIN ), cap );
450 :
451 20886 : fd_memset( base + clean, 0, sz-clean );
452 :
453 20886 : *pclean = sz;
454 20886 : vm->region_ld_sz[ region ] = (uint)sz;
455 20886 : vm->region_st_sz[ region ] = (uint)sz;
456 20886 : }
457 :
458 : static inline void
459 : fd_vm_stack_grow( fd_vm_t * vm,
460 10482 : ulong sz ) {
461 10482 : fd_vm_region_grow( vm, FD_VM_STACK_REGION, vm->stack, &vm->stack_clean, FD_VM_STACK_MAX, sz );
462 10482 : }
463 :
464 : static inline void
465 : fd_vm_heap_grow( fd_vm_t * vm,
466 10426 : ulong sz ) {
467 10426 : fd_vm_region_grow( vm, FD_VM_HEAP_REGION, vm->heap, &vm->heap_clean, vm->heap_max, sz );
468 10426 : }
469 :
470 : /* fd_vm_mem_init_full eagerly initializes the whole stack and heap. */
471 :
472 : static inline void
473 7546 : fd_vm_mem_init_full( fd_vm_t * vm ) {
474 7546 : fd_vm_stack_grow( vm, FD_VM_STACK_MAX );
475 7546 : fd_vm_heap_grow ( vm, vm->heap_max );
476 7546 : }
477 :
478 : static inline ulong
479 : fd_vm_mem_haddr( fd_vm_t * vm,
480 : ulong vaddr,
481 : ulong sz,
482 : ulong const * vm_region_haddr, /* indexed [0,6) */
483 : uint const * vm_region_sz, /* indexed [0,6) */
484 : uchar write, /* 1 if the access is a write, 0 if it is a read */
485 1183598 : ulong sentinel ) {
486 1183598 : ulong region = FD_VADDR_TO_REGION( vaddr );
487 1183598 : ulong offset = vaddr & FD_VM_OFFSET_MASK;
488 :
489 : /* Some configurations of the vm have unmapped gaps between each
490 : stack frame. If this is the case, we need to check that the access
491 : is not in a gap region. */
492 1183598 : int stack_frame_gaps_enabled = vm->stack_push_frame_count > 1;
493 1183598 : if( FD_UNLIKELY( region==FD_VM_STACK_REGION && stack_frame_gaps_enabled ) ) {
494 : /* If an access starts in a gap region, that is an access violation */
495 569944 : if( FD_UNLIKELY( !!(vaddr & 0x1000) ) ) {
496 49 : return sentinel;
497 49 : }
498 :
499 : /* To account for the fact that we have gaps in the virtual address space but not in the
500 : physical address space, we need to subtract from the offset the size of all the virtual
501 : gap frames underneath it.
502 :
503 : https://github.com/solana-labs/rbpf/blob/b503a1867a9cfa13f93b4d99679a17fe219831de/src/memory_region.rs#L147-L149 */
504 569895 : ulong gap_mask = 0xFFFFFFFFFFFFF000;
505 569895 : offset = ( ( offset & gap_mask ) >> 1 ) | ( offset & ~gap_mask );
506 569895 : }
507 :
508 : /* If the region is an account, handle the resizing logic. This logic corresponds to
509 : solana_transaction_context::TransactionContext::access_violation_handler
510 :
511 : https://github.com/anza-xyz/agave/blob/v3.0.1/transaction-context/src/lib.rs#L510-L581 */
512 1183549 : if( region==FD_VM_INPUT_REGION ) {
513 71140 : return fd_vm_find_input_mem_region( vm, offset, sz, write, sentinel );
514 71140 : }
515 :
516 1112409 : ulong region_sz = (ulong)vm_region_sz[ region ];
517 1112409 : ulong sz_max = region_sz - fd_ulong_min( offset, region_sz );
518 :
519 1112409 : if( FD_UNLIKELY( sz>sz_max && (region==FD_VM_STACK_REGION || region==FD_VM_HEAP_REGION) ) ) {
520 : /* {stack,heap} are lazily initialized. faults here mean that we
521 : need to do some zero initialization work. */
522 5886 : ulong need = fd_ulong_sat_add( offset, sz );
523 5886 : ulong cap = fd_ulong_if( region==FD_VM_STACK_REGION, FD_VM_STACK_MAX, vm->heap_max );
524 5886 : if( FD_LIKELY( need<=cap ) ) {
525 5778 : if( region==FD_VM_STACK_REGION ) fd_vm_stack_grow( vm, need );
526 2851 : else fd_vm_heap_grow ( vm, need );
527 5778 : region_sz = (ulong)vm_region_sz[ region ]; /* republished by grow */
528 5778 : sz_max = region_sz - fd_ulong_min( offset, region_sz );
529 5778 : }
530 5886 : }
531 :
532 1112409 : return fd_ulong_if( sz<=sz_max, vm_region_haddr[ region ] + offset, sentinel );
533 1183549 : }
534 :
535 :
536 96656 : FD_FN_PURE static inline ulong fd_vm_mem_ld_1( ulong haddr ) {
537 96656 : return (ulong)*(uchar const *)haddr;
538 96656 : }
539 :
540 2930 : FD_FN_PURE static inline ulong fd_vm_mem_ld_2( ulong haddr ) {
541 2930 : ushort t;
542 2930 : memcpy( &t, (void const *)haddr, sizeof(ushort) );
543 2930 : return (ulong)t;
544 2930 : }
545 :
546 8502 : FD_FN_PURE static inline ulong fd_vm_mem_ld_4( ulong haddr ) {
547 8502 : uint t;
548 8502 : memcpy( &t, (void const *)haddr, sizeof(uint) );
549 8502 : return (ulong)t;
550 8502 : }
551 :
552 288585 : FD_FN_PURE static inline ulong fd_vm_mem_ld_8( ulong haddr ) {
553 288585 : ulong t;
554 288585 : memcpy( &t, (void const *)haddr, sizeof(ulong) );
555 288585 : return t;
556 288585 : }
557 :
558 121102 : static inline void fd_vm_mem_st_1( ulong haddr, uchar val ) {
559 121102 : *(uchar *)haddr = val;
560 121102 : }
561 :
562 : static inline void fd_vm_mem_st_2( ulong haddr,
563 38499 : ushort val ) {
564 38499 : memcpy( (void *)haddr, &val, sizeof(ushort) );
565 38499 : }
566 :
567 : static inline void fd_vm_mem_st_4( ulong haddr,
568 18656 : uint val ) {
569 18656 : memcpy( (void *)haddr, &val, sizeof(uint) );
570 18656 : }
571 :
572 : static inline void fd_vm_mem_st_8( ulong haddr,
573 543679 : ulong val ) {
574 543679 : memcpy( (void *)haddr, &val, sizeof(ulong) );
575 543679 : }
576 :
577 : FD_PROTOTYPES_END
578 :
579 : #endif /* HEADER_fd_src_flamenco_vm_fd_vm_private_h */
|