Line data Source code
1 : #include "fd_vm_syscall.h"
2 : #include "../../../ballet/ed25519/fd_curve25519.h"
3 : #include "../../../util/bits/fd_uwide.h"
4 : #include "../../runtime/fd_account.h"
5 : #include "../../runtime/fd_executor.h"
6 : #include "../../runtime/fd_account_old.h" /* FIXME: remove this and update to use new APIs */
7 : #include <stdio.h>
8 : #include <sys/mman.h>
9 : #include <unistd.h>
10 : #include <errno.h>
11 : #include "../../nanopb/pb_encode.h"
12 : #include "../../runtime/tests/generated/vm.pb.h"
13 : #include "../../runtime/tests/fd_exec_instr_test.h"
14 :
15 : #define STRINGIFY(x) TOSTRING(x)
16 : #define TOSTRING(x) #x
17 :
18 : /* Captures the state of the VM (including the instruction context).
19 : Meant to be invoked at the start of the VM_SYSCALL_CPI_ENTRYPOINT like so:
20 :
21 : ```
22 : dump_vm_cpi_state(vm, STRINGIFY(FD_EXPAND_THEN_CONCAT2(sol_invoke_signed_, VM_SYSCALL_CPI_ABI)),
23 : instruction_va, acct_infos_va, acct_info_cnt, signers_seeds_va, signers_seeds_cnt);
24 : ```
25 :
26 : Assumes that a `vm_cp_state` directory exists in the current working directory. Generates a
27 : unique dump for combination of (tile_id, caller_pubkey, instr_sz). */
28 :
29 : static FD_FN_UNUSED void
30 : dump_vm_cpi_state(fd_vm_t *vm,
31 : char const * fn_name,
32 : ulong instruction_va,
33 : ulong acct_infos_va,
34 : ulong acct_info_cnt,
35 : ulong signers_seeds_va,
36 0 : ulong signers_seeds_cnt ) {
37 0 : char filename[100];
38 0 : fd_instr_info_t const *instr = vm->instr_ctx->instr;
39 0 : sprintf(filename, "vm_cpi_state/%lu_%lu%lu_%hu.sysctx", fd_tile_id(), instr->program_id_pubkey.ul[0], instr->program_id_pubkey.ul[1], instr->data_sz);
40 0 :
41 0 : // Check if file exists
42 0 : if( access (filename, F_OK) != -1 ) {
43 0 : return;
44 0 : }
45 0 :
46 0 : fd_exec_test_syscall_context_t sys_ctx = FD_EXEC_TEST_SYSCALL_CONTEXT_INIT_ZERO;
47 0 : sys_ctx.has_instr_ctx = 1;
48 0 : sys_ctx.has_vm_ctx = 1;
49 0 : sys_ctx.has_syscall_invocation = 1;
50 0 :
51 0 : // Copy function name
52 0 : sys_ctx.syscall_invocation.function_name.size = fd_uint_min( (uint) strlen(fn_name), sizeof(sys_ctx.syscall_invocation.function_name.bytes) );
53 0 : fd_memcpy( sys_ctx.syscall_invocation.function_name.bytes,
54 0 : fn_name,
55 0 : sys_ctx.syscall_invocation.function_name.size );
56 0 :
57 0 : // VM Ctx integral fields
58 0 : sys_ctx.vm_ctx.r1 = instruction_va;
59 0 : sys_ctx.vm_ctx.r2 = acct_infos_va;
60 0 : sys_ctx.vm_ctx.r3 = acct_info_cnt;
61 0 : sys_ctx.vm_ctx.r4 = signers_seeds_va;
62 0 : sys_ctx.vm_ctx.r5 = signers_seeds_cnt;
63 0 :
64 0 : sys_ctx.vm_ctx.rodata_text_section_length = vm->text_sz;
65 0 : sys_ctx.vm_ctx.rodata_text_section_offset = vm->text_off;
66 0 :
67 0 : sys_ctx.vm_ctx.heap_max = vm->heap_max; /* should be equiv. to txn_ctx->heap_sz */
68 0 :
69 0 : FD_SCRATCH_SCOPE_BEGIN{
70 0 : sys_ctx.vm_ctx.rodata = fd_scratch_alloc( 8UL, PB_BYTES_ARRAY_T_ALLOCSIZE(vm->rodata_sz) );
71 0 : sys_ctx.vm_ctx.rodata->size = (pb_size_t) vm->rodata_sz;
72 0 : fd_memcpy( sys_ctx.vm_ctx.rodata->bytes, vm->rodata, vm->rodata_sz );
73 0 :
74 0 : pb_size_t stack_sz = (pb_size_t) ( (vm->frame_cnt + 1)*FD_VM_STACK_GUARD_SZ*2 );
75 0 : sys_ctx.syscall_invocation.stack_prefix = fd_scratch_alloc( 8UL, PB_BYTES_ARRAY_T_ALLOCSIZE(stack_sz) );
76 0 : sys_ctx.syscall_invocation.stack_prefix->size = stack_sz;
77 0 : fd_memcpy( sys_ctx.syscall_invocation.stack_prefix->bytes, vm->stack, stack_sz );
78 0 :
79 0 : sys_ctx.syscall_invocation.heap_prefix = fd_scratch_alloc( 8UL, PB_BYTES_ARRAY_T_ALLOCSIZE(vm->heap_max) );
80 0 : sys_ctx.syscall_invocation.heap_prefix->size = (pb_size_t) vm->instr_ctx->txn_ctx->heap_size;
81 0 : fd_memcpy( sys_ctx.syscall_invocation.heap_prefix->bytes, vm->heap, vm->instr_ctx->txn_ctx->heap_size );
82 0 :
83 0 : sys_ctx.vm_ctx.input_data_regions_count = vm->input_mem_regions_cnt;
84 0 : sys_ctx.vm_ctx.input_data_regions = fd_scratch_alloc( 8UL, sizeof(fd_exec_test_input_data_region_t) * vm->input_mem_regions_cnt );
85 0 : for( ulong i=0UL; i<vm->input_mem_regions_cnt; i++ ) {
86 0 : sys_ctx.vm_ctx.input_data_regions[i].content = fd_scratch_alloc( 8UL, PB_BYTES_ARRAY_T_ALLOCSIZE(vm->input_mem_regions[i].region_sz) );
87 0 : sys_ctx.vm_ctx.input_data_regions[i].content->size = (pb_size_t) vm->input_mem_regions[i].region_sz;
88 0 : fd_memcpy( sys_ctx.vm_ctx.input_data_regions[i].content->bytes, (uchar *) vm->input_mem_regions[i].haddr, vm->input_mem_regions[i].region_sz );
89 0 : sys_ctx.vm_ctx.input_data_regions[i].offset = vm->input_mem_regions[i].vaddr_offset;
90 0 : sys_ctx.vm_ctx.input_data_regions[i].is_writable = vm->input_mem_regions[i].is_writable;
91 0 : }
92 0 :
93 0 : fd_create_instr_context_protobuf_from_instructions( &sys_ctx.instr_ctx,
94 0 : vm->instr_ctx->txn_ctx,
95 0 : vm->instr_ctx->instr );
96 0 :
97 0 : // Serialize the protobuf to file (using mmap)
98 0 : size_t pb_alloc_size = 100 * 1024 * 1024; // 100MB (largest so far is 19MB)
99 0 : FILE *f = fopen(filename, "wb+");
100 0 : if( ftruncate(fileno(f), (off_t) pb_alloc_size) != 0 ) {
101 0 : FD_LOG_WARNING(("Failed to resize file %s", filename));
102 0 : fclose(f);
103 0 : return;
104 0 : }
105 0 :
106 0 : uchar *pb_alloc = mmap( NULL,
107 0 : pb_alloc_size,
108 0 : PROT_READ | PROT_WRITE,
109 0 : MAP_SHARED,
110 0 : fileno(f),
111 0 : 0 /* offset */);
112 0 : if( pb_alloc == MAP_FAILED ) {
113 0 : FD_LOG_WARNING(( "Failed to mmap file %d", errno ));
114 0 : fclose(f);
115 0 : return;
116 0 : }
117 0 :
118 0 : pb_ostream_t stream = pb_ostream_from_buffer(pb_alloc, pb_alloc_size);
119 0 : if( !pb_encode( &stream, FD_EXEC_TEST_SYSCALL_CONTEXT_FIELDS, &sys_ctx ) ) {
120 0 : FD_LOG_WARNING(( "Failed to encode instruction context" ));
121 0 : }
122 0 : // resize file to actual size
123 0 : if( ftruncate( fileno(f), (off_t) stream.bytes_written ) != 0 ) {
124 0 : FD_LOG_WARNING(( "Failed to resize file %s", filename ));
125 0 : }
126 0 :
127 0 : fclose(f);
128 0 :
129 0 : } FD_SCRATCH_SCOPE_END;
130 0 : }
131 :
132 : /* FIXME: ALGO EFFICIENCY */
133 : static inline int
134 : fd_vm_syscall_cpi_is_signer( fd_pubkey_t const * account,
135 : fd_pubkey_t const * signers,
136 225 : ulong signers_cnt ) {
137 225 : for( ulong i=0UL; i<signers_cnt; i++ ) if( !memcmp( account->uc, signers[i].uc, sizeof(fd_pubkey_t) ) ) return 1;
138 3 : return 0;
139 225 : }
140 :
141 : /*
142 : fd_vm_prepare_instruction populates instruction_accounts and instruction_accounts_cnt
143 : with the instruction accounts ready for execution.
144 :
145 : The majority of this logic is taken from
146 : https://github.com/solana-labs/solana/blob/v1.17.22/program-runtime/src/invoke_context.rs#L535,
147 : and is not vm-specific, but a part of the runtime.
148 : TODO: should we move this out of the CPI section?
149 :
150 : The bulk of the logic is concerned with unifying the privileges for each duplicated account,
151 : ensuring that each duplicate account referenced has the same privileges. It also performs some
152 : priviledge checks, for example ensuring the necessary signatures are present.
153 :
154 : TODO: instruction calling convention: const parameters after non-const.
155 :
156 : Assumptions:
157 : - We do not have more than 256 unique accounts in the callee_instr.
158 : This limit comes from the fact that a Solana transaction cannot
159 : refefence more than 256 unique accounts, due to the transaction
160 : serialization format.
161 : - callee_instr is not null.
162 : - callee_instr->acct_pubkeys is at least as long as callee_instr->acct_cnt
163 : - instr_ctx->txn_ctx->accounts_cnt is less than UCHAR_MAX.
164 : This is likely because the transaction is limited to 256 accounts.
165 : - callee_instr->program_id is set to UCHAR_MAX if account is not in instr_ctx->txn_ctx.
166 : - instruction_accounts is a 256-length empty array.
167 :
168 : Parameters:
169 : - caller_instr
170 : - callee_instr
171 : - instr_ctx
172 : - instruction_accounts
173 : - instruction_accounts_cnt
174 : - signers
175 : - signers_cnt
176 :
177 : Returns:
178 : - instruction_accounts
179 : - instruction_accounts_cnt
180 : Populated with the instruction accounts with normalized permissions.
181 :
182 : TODO: is it possible to pass the transaction indexes of the accounts in?
183 : This would allow us to make some of these algorithms more efficient.
184 : */
185 : int
186 : fd_vm_prepare_instruction( fd_instr_info_t const * caller_instr,
187 : fd_instr_info_t * callee_instr,
188 : fd_exec_instr_ctx_t * instr_ctx,
189 : fd_instruction_account_t instruction_accounts[256],
190 : ulong * instruction_accounts_cnt,
191 : fd_pubkey_t const * signers,
192 930 : ulong signers_cnt ) {
193 :
194 : /* De-duplicate the instruction accounts, using the same logic as Solana */
195 930 : ulong deduplicated_instruction_accounts_cnt = 0;
196 930 : fd_instruction_account_t deduplicated_instruction_accounts[256] = {0};
197 930 : ulong duplicate_indicies_cnt = 0;
198 930 : ulong duplicate_indices[256] = {0};
199 :
200 : /* Normalize the privileges of each instruction account in the callee, after de-duping
201 : the account references.
202 : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/program-runtime/src/invoke_context.rs#L540-L595 */
203 3579 : for( ulong i=0UL; i<callee_instr->acct_cnt; i++ ) {
204 2649 : fd_pubkey_t const * callee_pubkey = &callee_instr->acct_pubkeys[i];
205 :
206 : /* Find the corresponding transaction account index for this callee instruction account */
207 : /* TODO: passing in the transaction indicies would mean we didn't have to do this */
208 2649 : ushort index_in_transaction = USHORT_MAX;
209 23736 : for( ulong j=0UL; j<instr_ctx->txn_ctx->accounts_cnt; j++ ) {
210 23736 : if( !memcmp( instr_ctx->txn_ctx->accounts[j].uc, callee_pubkey->uc, sizeof(fd_pubkey_t) ) ) {
211 2649 : index_in_transaction = (ushort)j;
212 2649 : break;
213 2649 : }
214 23736 : }
215 2649 : if( index_in_transaction==USHORT_MAX) {
216 : /* In this case the callee instruction is referencing an unknown account not listed in the
217 : transactions accounts. */
218 0 : FD_BASE58_ENCODE_32_BYTES( callee_pubkey->uc, id_b58 );
219 0 : fd_log_collector_msg_many( instr_ctx, 2, "Unknown account ", 16UL, id_b58, id_b58_len );
220 0 : FD_TXN_ERR_FOR_LOG_INSTR( instr_ctx->txn_ctx, FD_EXECUTOR_INSTR_ERR_MISSING_ACC, instr_ctx->txn_ctx->instr_err_idx );
221 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
222 0 : }
223 :
224 : /* If there was an instruction account before this one which referenced the same
225 : transaction account index, find it's index in the deduplicated_instruction_accounts
226 : array. */
227 2649 : ulong duplicate_index = ULONG_MAX;
228 7884 : for( ulong j=0UL; j<deduplicated_instruction_accounts_cnt; j++ ) {
229 5259 : if( deduplicated_instruction_accounts[j].index_in_transaction==index_in_transaction ) {
230 24 : duplicate_index = j;
231 24 : break;
232 24 : }
233 5259 : }
234 :
235 : /* If this was account referenced in a previous iteration, update the flags to include those set
236 : in this iteration. This ensures that after all the iterations, the de-duplicated account flags
237 : for each account are the union of all the flags in all the references to that account in this instruction. */
238 :
239 : /* TODO: FD_UNLIKELY? Need to check which branch is more common by running against a larger mainnet ledger */
240 : /* TODO: this code would maybe be easier to read if we inverted the branches */
241 2649 : if( duplicate_index!=ULONG_MAX ) {
242 24 : if ( FD_UNLIKELY( duplicate_index >= deduplicated_instruction_accounts_cnt ) ) {
243 0 : return FD_EXECUTOR_INSTR_ERR_NOT_ENOUGH_ACC_KEYS;
244 0 : }
245 :
246 24 : duplicate_indices[duplicate_indicies_cnt++] = duplicate_index;
247 24 : fd_instruction_account_t * instruction_account = &deduplicated_instruction_accounts[duplicate_index];
248 24 : instruction_account->is_signer |= !!(callee_instr->acct_flags[i] & FD_INSTR_ACCT_FLAGS_IS_SIGNER);
249 24 : instruction_account->is_writable |= !!(callee_instr->acct_flags[i] & FD_INSTR_ACCT_FLAGS_IS_WRITABLE);
250 2625 : } else {
251 : /* In the case where the callee instruction is NOT a duplicate, we need to
252 : create the deduplicated_instruction_accounts fd_instruction_account_t object. */
253 :
254 : /* Find the index of the instruction account in the caller instruction */
255 2625 : ushort index_in_caller = USHORT_MAX;
256 23865 : for( ulong j=0UL; j<caller_instr->acct_cnt; j++ ) {
257 : /* TODO: passing transaction indicies in would also allow us to remove these memcmp's */
258 23865 : if( !memcmp( caller_instr->acct_pubkeys[j].uc, callee_instr->acct_pubkeys[i].uc, sizeof(fd_pubkey_t) ) ) {
259 2625 : index_in_caller = (ushort)j;
260 2625 : break;
261 2625 : }
262 23865 : }
263 :
264 2625 : if( index_in_caller==USHORT_MAX ) {
265 0 : FD_BASE58_ENCODE_32_BYTES( callee_pubkey->uc, id_b58 );
266 0 : fd_log_collector_msg_many( instr_ctx, 2, "Unknown account ", 16UL, id_b58, id_b58_len );
267 0 : FD_TXN_ERR_FOR_LOG_INSTR( instr_ctx->txn_ctx, FD_EXECUTOR_INSTR_ERR_MISSING_ACC, instr_ctx->txn_ctx->instr_err_idx );
268 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
269 0 : }
270 :
271 : /* Add the instruction account to the duplicate indicies array */
272 2625 : duplicate_indices[duplicate_indicies_cnt++] = deduplicated_instruction_accounts_cnt;
273 :
274 : /* Initialize the instruction account in the deduplicated_instruction_accounts array */
275 2625 : fd_instruction_account_t * instruction_account = &deduplicated_instruction_accounts[deduplicated_instruction_accounts_cnt++];
276 2625 : instruction_account->index_in_callee = (ushort)i;
277 2625 : instruction_account->index_in_caller = index_in_caller;
278 2625 : instruction_account->index_in_transaction = index_in_transaction;
279 2625 : instruction_account->is_signer = !!(callee_instr->acct_flags[i] & FD_INSTR_ACCT_FLAGS_IS_SIGNER);
280 2625 : instruction_account->is_writable = !!(callee_instr->acct_flags[i] & FD_INSTR_ACCT_FLAGS_IS_WRITABLE);
281 2625 : }
282 2649 : }
283 :
284 : /* Check the normalized account permissions for privilege escalation.
285 : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/program-runtime/src/invoke_context.rs#L596-L624 */
286 3495 : for( ulong i = 0; i < deduplicated_instruction_accounts_cnt; i++ ) {
287 2595 : fd_instruction_account_t * instruction_account = &deduplicated_instruction_accounts[i];
288 2595 : fd_pubkey_t const * pubkey = &caller_instr->acct_pubkeys[instruction_account->index_in_caller];
289 :
290 : /* Check that the account is not read-only in the caller but writable in the callee */
291 2595 : if( FD_UNLIKELY( instruction_account->is_writable && !fd_instr_acc_is_writable( instr_ctx->instr, pubkey ) ) ) {
292 27 : return FD_EXECUTOR_INSTR_ERR_PRIVILEGE_ESCALATION;
293 27 : }
294 :
295 : /* If the account is signed in the callee, it must be signed by the caller or the program */
296 2568 : if ( FD_UNLIKELY( instruction_account->is_signer && !( fd_instr_acc_is_signer( instr_ctx->instr, pubkey ) || fd_vm_syscall_cpi_is_signer( pubkey, signers, signers_cnt) ) ) ) {
297 3 : return FD_EXECUTOR_INSTR_ERR_PRIVILEGE_ESCALATION;
298 3 : }
299 2568 : }
300 :
301 : /* Copy the accounts with their normalized permissions over to the final instruction_accounts array,
302 : and set the callee_instr acct_flags. */
303 3489 : for (ulong i = 0; i < duplicate_indicies_cnt; i++) {
304 2589 : ulong duplicate_index = duplicate_indices[i];
305 :
306 : /* Failing this condition is technically impossible, but it is probably safest to keep this in
307 : so that we throw InstructionError::NotEnoughAccountKeys at the same point at Solana does,
308 : in the event any surrounding code is changed.
309 : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/program-runtime/src/invoke_context.rs#L625-L633 */
310 2589 : if ( FD_LIKELY( duplicate_index < deduplicated_instruction_accounts_cnt ) ) {
311 2589 : instruction_accounts[i] = deduplicated_instruction_accounts[duplicate_index];
312 2589 : callee_instr->acct_flags[i] = (uchar)
313 2589 : ( ( callee_instr->acct_flags[i] ) |
314 2589 : ( !!(instruction_accounts[i].is_writable) * FD_INSTR_ACCT_FLAGS_IS_WRITABLE ) |
315 2589 : ( !!(instruction_accounts[i].is_signer ) * FD_INSTR_ACCT_FLAGS_IS_SIGNER ) );
316 2589 : } else {
317 0 : return FD_EXECUTOR_INSTR_ERR_NOT_ENOUGH_ACC_KEYS;
318 0 : }
319 2589 : }
320 :
321 : /* Check that the program account is executable. We need to ensure that the
322 : program account is a valid instruction account.
323 : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/program-runtime/src/invoke_context.rs#L635-L648 */
324 900 : fd_borrowed_account_t * program_rec = NULL;
325 :
326 : /* Caller is in charge of setting an appropriate sentinel value (i.e., UCHAR_MAX) for callee_instr->program_id if not found. */
327 : /* We allow dead accounts to be borrowed here because that's what agave currently does.
328 : https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/program-runtime/src/invoke_context.rs#L453 */
329 900 : int err = fd_txn_borrowed_account_view_idx_allow_dead( instr_ctx->txn_ctx, callee_instr->program_id, &program_rec );
330 900 : if( FD_UNLIKELY( err ) ) {
331 : /* https://github.com/anza-xyz/agave/blob/a9ac3f55fcb2bc735db0d251eda89897a5dbaaaa/program-runtime/src/invoke_context.rs#L434 */
332 108 : FD_BASE58_ENCODE_32_BYTES( callee_instr->program_id_pubkey.uc, id_b58 );
333 108 : fd_log_collector_msg_many( instr_ctx, 2, "Unknown program ", 16UL, id_b58, id_b58_len );
334 108 : FD_TXN_ERR_FOR_LOG_INSTR( instr_ctx->txn_ctx, FD_EXECUTOR_INSTR_ERR_MISSING_ACC, instr_ctx->txn_ctx->instr_err_idx );
335 108 : return FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
336 108 : }
337 :
338 792 : if( FD_UNLIKELY( fd_account_find_idx_of_insn_account( instr_ctx, &callee_instr->program_id_pubkey )==-1 ) ) {
339 9 : FD_BASE58_ENCODE_32_BYTES( callee_instr->program_id_pubkey.uc, id_b58 );
340 9 : fd_log_collector_msg_many( instr_ctx, 2, "Unknown program ", 16UL, id_b58, id_b58_len );
341 9 : FD_TXN_ERR_FOR_LOG_INSTR( instr_ctx->txn_ctx, FD_EXECUTOR_INSTR_ERR_MISSING_ACC, instr_ctx->txn_ctx->instr_err_idx );
342 9 : return FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
343 9 : }
344 :
345 783 : fd_account_meta_t const * program_meta = program_rec->const_meta;
346 :
347 783 : if( FD_UNLIKELY( !fd_account_is_executable( program_meta ) ) ) {
348 45 : FD_BASE58_ENCODE_32_BYTES( callee_instr->program_id_pubkey.uc, id_b58 );
349 45 : fd_log_collector_msg_many( instr_ctx, 3, "Account ", 8UL, id_b58, id_b58_len, " is not executable", 18UL );
350 45 : FD_TXN_ERR_FOR_LOG_INSTR( instr_ctx->txn_ctx, FD_EXECUTOR_INSTR_ERR_ACC_NOT_EXECUTABLE, instr_ctx->txn_ctx->instr_err_idx );
351 45 : return FD_EXECUTOR_INSTR_ERR_ACC_NOT_EXECUTABLE;
352 45 : }
353 :
354 738 : *instruction_accounts_cnt = duplicate_indicies_cnt;
355 :
356 738 : return 0;
357 783 : }
358 :
359 : /**********************************************************************
360 : CROSS PROGRAM INVOCATION (Generic logic)
361 : **********************************************************************/
362 :
363 : /* FD_CPI_MAX_SIGNER_CNT is the max amount of PDA signer addresses that
364 : a cross-program invocation can include in an instruction.
365 :
366 : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/programs/bpf_loader/src/syscalls/mod.rs#L80 */
367 :
368 : #define FD_CPI_MAX_SIGNER_CNT (16UL)
369 :
370 : /* "Maximum number of account info structs that can be used in a single CPI
371 : invocation. A limit on account info structs is effectively the same as
372 : limiting the number of unique accounts. 128 was chosen to match the max
373 : number of locked accounts per transaction (MAX_TX_ACCOUNT_LOCKS)."
374 :
375 : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/sdk/program/src/syscalls/mod.rs#L25
376 : https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/programs/bpf_loader/src/syscalls/cpi.rs#L1011 */
377 :
378 0 : #define FD_CPI_MAX_ACCOUNT_INFOS (128UL)
379 : /* This is just encoding what Agave says in their code comments into a
380 : compile-time check, so if anyone ever inadvertently changes one of
381 : the limits, they will have to take a look. */
382 : FD_STATIC_ASSERT( FD_CPI_MAX_ACCOUNT_INFOS==MAX_TX_ACCOUNT_LOCKS, cpi_max_account_info );
383 : static inline ulong
384 0 : get_cpi_max_account_infos( fd_exec_slot_ctx_t const * slot_ctx ) {
385 0 : return fd_ulong_if( FD_FEATURE_ACTIVE( slot_ctx, increase_tx_account_lock_limit ), FD_CPI_MAX_ACCOUNT_INFOS, 64UL );
386 0 : }
387 :
388 : /* Maximum CPI instruction data size. 10 KiB was chosen to ensure that CPI
389 : instructions are not more limited than transaction instructions if the size
390 : of transactions is doubled in the future.
391 :
392 : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/sdk/program/src/syscalls/mod.rs#L14 */
393 :
394 : #define FD_CPI_MAX_INSTRUCTION_DATA_LEN (10240UL)
395 :
396 : /* Maximum CPI instruction accounts. 255 was chosen to ensure that instruction
397 : accounts are always within the maximum instruction account limit for BPF
398 : program instructions.
399 :
400 : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/sdk/program/src/syscalls/mod.rs#L19
401 : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/programs/bpf_loader/src/serialization.rs#L26 */
402 :
403 : #define FD_CPI_MAX_INSTRUCTION_ACCOUNTS (255UL)
404 :
405 :
406 : /* fd_vm_syscall_cpi_check_instruction contains common instruction acct
407 : count and data sz checks. Also consumes compute units proportional
408 : to instruction data size. */
409 :
410 : static int
411 : fd_vm_syscall_cpi_check_instruction( fd_vm_t const * vm,
412 : ulong acct_cnt,
413 264 : ulong data_sz ) {
414 : /* https://github.com/solana-labs/solana/blob/eb35a5ac1e7b6abe81947e22417f34508f89f091/programs/bpf_loader/src/syscalls/cpi.rs#L958-L959 */
415 264 : if( FD_FEATURE_ACTIVE( vm->instr_ctx->slot_ctx, loosen_cpi_size_restriction ) ) {
416 6 : if( FD_UNLIKELY( data_sz > FD_CPI_MAX_INSTRUCTION_DATA_LEN ) ) {
417 0 : FD_LOG_WARNING(( "cpi: data too long (%#lx)", data_sz ));
418 : // SyscallError::MaxInstructionDataLenExceeded
419 0 : return FD_VM_ERR_SYSCALL_MAX_INSTRUCTION_DATA_LEN_EXCEEDED;
420 0 : }
421 6 : if( FD_UNLIKELY( acct_cnt > FD_CPI_MAX_INSTRUCTION_ACCOUNTS ) ) {
422 0 : FD_LOG_WARNING(( "cpi: too many accounts (%#lx)", acct_cnt ));
423 : // SyscallError::MaxInstructionAccountsExceeded
424 0 : return FD_VM_ERR_SYSCALL_MAX_INSTRUCTION_ACCOUNTS_EXCEEDED;
425 0 : }
426 258 : } else {
427 : // https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/programs/bpf_loader/src/syscalls/cpi.rs#L1114
428 258 : ulong tot_sz = fd_ulong_sat_add( fd_ulong_sat_mul( FD_VM_RUST_ACCOUNT_META_SIZE, acct_cnt ), data_sz );
429 258 : if ( FD_UNLIKELY( tot_sz > FD_VM_MAX_CPI_INSTRUCTION_SIZE ) ) {
430 0 : FD_LOG_WARNING(( "cpi: instruction too long (%#lx)", tot_sz ));
431 : // SyscallError::InstructionTooLarge
432 0 : return FD_VM_ERR_SYSCALL_INSTRUCTION_TOO_LARGE;
433 0 : }
434 258 : }
435 :
436 264 : return FD_VM_SUCCESS;
437 264 : }
438 :
439 : /**********************************************************************
440 : CROSS PROGRAM INVOCATION HELPERS
441 : **********************************************************************/
442 :
443 : static inline int
444 : fd_vm_syscall_cpi_check_id( fd_pubkey_t const * program_id,
445 1848 : uchar const * loader ) {
446 1848 : return !memcmp( program_id, loader, sizeof(fd_pubkey_t) );
447 1848 : }
448 :
449 : /* fd_vm_syscall_cpi_is_precompile returns true if the given program_id
450 : corresponds to a precompile. It does this by checking against a hardcoded
451 : list of known pre-compiles.
452 :
453 : This mirrors the behaviour in solana_sdk::precompiles::is_precompile
454 : https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/sdk/src/precompiles.rs#L93
455 : */
456 : static inline int
457 264 : fd_vm_syscall_cpi_is_precompile( fd_pubkey_t const * program_id ) {
458 264 : return fd_vm_syscall_cpi_check_id(program_id, fd_solana_keccak_secp_256k_program_id.key) |
459 264 : fd_vm_syscall_cpi_check_id(program_id, fd_solana_ed25519_sig_verify_program_id.key) |
460 264 : fd_vm_syscall_cpi_check_id(program_id, fd_solana_secp256r1_program_id.key);
461 264 : }
462 :
463 : /* fd_vm_syscall_cpi_check_authorized_program corresponds to
464 : solana_bpf_loader_program::syscalls::cpi::check_authorized_program:
465 : https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L1032
466 :
467 : It determines if the given program_id is authorized to execute a CPI call.
468 :
469 : FIXME: return type
470 : */
471 : static inline ulong
472 : fd_vm_syscall_cpi_check_authorized_program( fd_pubkey_t const * program_id,
473 : fd_exec_slot_ctx_t const * slot_ctx,
474 : uchar const * instruction_data,
475 264 : ulong instruction_data_len ) {
476 : /* FIXME: do this in a branchless manner? using bitwise comparison would probably be faster */
477 264 : return ( fd_vm_syscall_cpi_check_id(program_id, fd_solana_native_loader_id.key)
478 264 : || fd_vm_syscall_cpi_check_id(program_id, fd_solana_bpf_loader_program_id.key)
479 264 : || fd_vm_syscall_cpi_check_id(program_id, fd_solana_bpf_loader_deprecated_program_id.key)
480 264 : || (fd_vm_syscall_cpi_check_id(program_id, fd_solana_bpf_loader_upgradeable_program_id.key)
481 264 : && !((instruction_data_len != 0 && instruction_data[0] == 3) /* is_upgrade_instruction() */
482 0 : || (instruction_data_len != 0 && instruction_data[0] == 4) /* is_set_authority_instruction() */
483 0 : || (FD_FEATURE_ACTIVE(slot_ctx, enable_bpf_loader_set_authority_checked_ix)
484 0 : && (instruction_data_len != 0 && instruction_data[0] == 7)) /* is_set_authority_checked_instruction() */
485 0 : || (instruction_data_len != 0 && instruction_data[0] == 5))) /* is_close_instruction */
486 264 : || fd_vm_syscall_cpi_is_precompile(program_id));
487 264 : }
488 :
489 : /**********************************************************************
490 : CROSS PROGRAM INVOCATION (C ABI)
491 : **********************************************************************/
492 :
493 : #define VM_SYSCALL_CPI_ABI c
494 12 : #define VM_SYSCALL_CPI_INSTR_T fd_vm_c_instruction_t
495 : #define VM_SYSCALL_CPI_INSTR_ALIGN (FD_VM_C_INSTRUCTION_ALIGN)
496 : #define VM_SYSCALL_CPI_INSTR_SIZE (FD_VM_C_INSTRUCTION_SIZE)
497 66 : #define VM_SYSCALL_CPI_ACC_META_T fd_vm_c_account_meta_t
498 : #define VM_SYSCALL_CPI_ACC_META_ALIGN (FD_VM_C_ACCOUNT_META_ALIGN)
499 : #define VM_SYSCALL_CPI_ACC_META_SIZE (FD_VM_C_ACCOUNT_META_SIZE)
500 12 : #define VM_SYSCALL_CPI_ACC_INFO_T fd_vm_c_account_info_t
501 : #define VM_SYSCALL_CPI_ACC_INFO_ALIGN (FD_VM_C_ACCOUNT_INFO_ALIGN)
502 : #define VM_SYSCALL_CPI_ACC_INFO_SIZE (FD_VM_C_ACCOUNT_INFO_SIZE)
503 :
504 : /* VM_SYSCALL_CPI_INSTR_T accessors */
505 : #define VM_SYSCALL_CPI_INSTR_DATA_ADDR( instr ) instr->data_addr
506 24 : #define VM_SYSCALL_CPI_INSTR_DATA_LEN( instr ) instr->data_len
507 : #define VM_SYSCALL_CPI_INSTR_ACCS_ADDR( instr ) instr->accounts_addr
508 90 : #define VM_SYSCALL_CPI_INSTR_ACCS_LEN( instr ) instr->accounts_len
509 : #define VM_SYSCALL_CPI_INSTR_PROGRAM_ID( vm, instr ) \
510 12 : FD_VM_MEM_HADDR_LD( vm, instr->program_id_addr, alignof(uchar), sizeof(fd_pubkey_t) )
511 :
512 : /* VM_SYSCALL_CPI_ACC_META_T accessors */
513 108 : #define VM_SYSCALL_CPI_ACC_META_IS_WRITABLE( acc_meta ) acc_meta->is_writable
514 108 : #define VM_SYSCALL_CPI_ACC_META_IS_SIGNER( acc_meta ) acc_meta->is_signer
515 : #define VM_SYSCALL_CPI_ACC_META_PUBKEY( vm, acc_meta ) \
516 54 : FD_VM_MEM_HADDR_LD( vm, acc_meta->pubkey_addr, alignof(uchar), sizeof(fd_pubkey_t) )
517 :
518 : /* VM_SYSCALL_CPI_ACC_INFO_T accessors */
519 : #define VM_SYSCALL_CPI_ACC_INFO_LAMPORTS( vm, acc_info, decl ) \
520 0 : ulong * decl = FD_VM_MEM_HADDR_ST( vm, acc_info->lamports_addr, alignof(ulong), sizeof(ulong) );
521 :
522 : #define VM_SYSCALL_CPI_ACC_INFO_DATA( vm, acc_info, decl ) \
523 0 : uchar * decl = FD_VM_MEM_HADDR_ST( vm, acc_info->data_addr, alignof(uchar), acc_info->data_sz ); \
524 0 : ulong FD_EXPAND_THEN_CONCAT2(decl, _vm_addr) = acc_info->data_addr; \
525 0 : ulong FD_EXPAND_THEN_CONCAT2(decl, _len) = acc_info->data_sz;
526 :
527 : #define VM_SYSCALL_CPI_ACC_INFO_METADATA( vm, acc_info, decl ) \
528 12 : ulong FD_EXPAND_THEN_CONCAT2(decl, _vm_addr) = acc_info->data_addr; \
529 12 : ulong FD_EXPAND_THEN_CONCAT2(decl, _len) = acc_info->data_sz;
530 :
531 : #define VM_SYSCALL_CPI_SET_ACC_INFO_DATA_LEN( vm, acc_info, decl, len ) \
532 0 : acc_info->data_sz = len;
533 :
534 : #include "fd_vm_syscall_cpi_common.c"
535 :
536 : #undef VM_SYSCALL_CPI_ABI
537 : #undef VM_SYSCALL_CPI_INSTR_T
538 : #undef VM_SYSCALL_CPI_INSTR_ALIGN
539 : #undef VM_SYSCALL_CPI_INSTR_SIZE
540 : #undef VM_SYSCALL_CPI_ACC_META_T
541 : #undef VM_SYSCALL_CPI_ACC_META_ALIGN
542 : #undef VM_SYSCALL_CPI_ACC_META_SIZE
543 : #undef VM_SYSCALL_CPI_ACC_INFO_T
544 : #undef VM_SYSCALL_CPI_ACC_INFO_ALIGN
545 : #undef VM_SYSCALL_CPI_ACC_INFO_SIZE
546 : #undef VM_SYSCALL_CPI_INSTR_DATA_ADDR
547 : #undef VM_SYSCALL_CPI_INSTR_DATA_LEN
548 : #undef VM_SYSCALL_CPI_INSTR_ACCS_ADDR
549 : #undef VM_SYSCALL_CPI_INSTR_ACCS_LEN
550 : #undef VM_SYSCALL_CPI_INSTR_PROGRAM_ID
551 : #undef VM_SYSCALL_CPI_ACC_META_IS_WRITABLE
552 : #undef VM_SYSCALL_CPI_ACC_META_IS_SIGNER
553 : #undef VM_SYSCALL_CPI_ACC_META_PUBKEY
554 : #undef VM_SYSCALL_CPI_ACC_INFO_LAMPORTS
555 : #undef VM_SYSCALL_CPI_ACC_INFO_DATA
556 : #undef VM_SYSCALL_CPI_ACC_INFO_METADATA
557 : #undef VM_SYSCALL_CPI_SET_ACC_INFO_DATA_LEN
558 :
559 : /**********************************************************************
560 : CROSS PROGRAM INVOCATION (Rust ABI)
561 : **********************************************************************/
562 :
563 : #define VM_SYSCALL_CPI_ABI rust
564 258 : #define VM_SYSCALL_CPI_INSTR_T fd_vm_rust_instruction_t
565 : #define VM_SYSCALL_CPI_INSTR_ALIGN (FD_VM_RUST_INSTRUCTION_ALIGN)
566 : #define VM_SYSCALL_CPI_INSTR_SIZE (FD_VM_RUST_INSTRUCTION_SIZE)
567 1407 : #define VM_SYSCALL_CPI_ACC_META_T fd_vm_rust_account_meta_t
568 : #define VM_SYSCALL_CPI_ACC_META_ALIGN (FD_VM_RUST_ACCOUNT_META_ALIGN)
569 : #define VM_SYSCALL_CPI_ACC_META_SIZE (FD_VM_RUST_ACCOUNT_META_SIZE)
570 240 : #define VM_SYSCALL_CPI_ACC_INFO_T fd_vm_rust_account_info_t
571 : #define VM_SYSCALL_CPI_ACC_INFO_ALIGN (FD_VM_RUST_ACCOUNT_INFO_ALIGN)
572 : #define VM_SYSCALL_CPI_ACC_INFO_SIZE (FD_VM_RUST_ACCOUNT_INFO_SIZE)
573 :
574 : /* VM_SYSCALL_CPI_INSTR_T accessors */
575 : #define VM_SYSCALL_CPI_INSTR_DATA_ADDR( instr ) instr->data.addr
576 504 : #define VM_SYSCALL_CPI_INSTR_DATA_LEN( instr ) instr->data.len
577 : #define VM_SYSCALL_CPI_INSTR_ACCS_ADDR( instr ) instr->accounts.addr
578 1911 : #define VM_SYSCALL_CPI_INSTR_ACCS_LEN( instr ) instr->accounts.len
579 252 : #define VM_SYSCALL_CPI_INSTR_PROGRAM_ID( vm, instr ) instr->pubkey
580 :
581 : /* VM_SYSCALL_CPI_ACC_META_T accessors */
582 2310 : #define VM_SYSCALL_CPI_ACC_META_IS_WRITABLE( acc_meta ) acc_meta->is_writable
583 2310 : #define VM_SYSCALL_CPI_ACC_META_IS_SIGNER( acc_meta ) acc_meta->is_signer
584 1155 : #define VM_SYSCALL_CPI_ACC_META_PUBKEY( vm, acc_meta ) acc_meta->pubkey
585 :
586 : /* VM_SYSCALL_CPI_ACC_INFO_T accessors */
587 : /* The lamports and the account data are stored behind RefCells,
588 : so we have an additional layer of indirection to unwrap. */
589 : #define VM_SYSCALL_CPI_ACC_INFO_LAMPORTS( vm, acc_info, decl ) \
590 : /* Translate the pointer to the RefCell */ \
591 1695 : fd_vm_rc_refcell_t * FD_EXPAND_THEN_CONCAT2(decl, _box) = \
592 1695 : FD_VM_MEM_HADDR_ST( vm, acc_info->lamports_box_addr, FD_VM_RC_REFCELL_ALIGN, sizeof(fd_vm_rc_refcell_t) ); \
593 1695 : /* Translate the pointer to the underlying data */ \
594 1695 : ulong * decl = FD_VM_MEM_HADDR_ST( vm, FD_EXPAND_THEN_CONCAT2(decl, _box)->addr, alignof(ulong), sizeof(ulong) );
595 :
596 : /* TODO: possibly define a refcell unwrapping macro to simplify this? */
597 : #define VM_SYSCALL_CPI_ACC_INFO_DATA( vm, acc_info, decl ) \
598 : /* Translate the pointer to the RefCell */ \
599 768 : fd_vm_rc_refcell_vec_t * FD_EXPAND_THEN_CONCAT2(decl, _box) = \
600 768 : FD_VM_MEM_HADDR_ST( vm, acc_info->data_box_addr, FD_VM_RC_REFCELL_ALIGN, sizeof(fd_vm_rc_refcell_vec_t) ); \
601 768 : /* Declare the vm addr of the underlying data, as we sometimes need it later */ \
602 768 : ulong FD_EXPAND_THEN_CONCAT2(decl, _vm_addr) = FD_EXPAND_THEN_CONCAT2(decl, _box)->addr; \
603 768 : /* Translate the pointer to the underlying data */ \
604 768 : uchar * decl = FD_VM_MEM_HADDR_ST( \
605 768 : vm, FD_EXPAND_THEN_CONCAT2(decl, _box)->addr, alignof(uchar), FD_EXPAND_THEN_CONCAT2(decl, _box)->len ); \
606 768 : /* Declare the size of the underlying data */ \
607 768 : ulong FD_EXPAND_THEN_CONCAT2(decl, _len) = FD_EXPAND_THEN_CONCAT2(decl, _box)->len;
608 :
609 : #define VM_SYSCALL_CPI_ACC_INFO_METADATA( vm, acc_info, decl ) \
610 : /* Translate the pointer to the RefCell */ \
611 1086 : fd_vm_rc_refcell_vec_t * FD_EXPAND_THEN_CONCAT2(decl, _box) = \
612 1086 : FD_VM_MEM_HADDR_ST( vm, acc_info->data_box_addr, FD_VM_RC_REFCELL_ALIGN, sizeof(fd_vm_rc_refcell_vec_t) ); \
613 1086 : /* Declare the vm addr of the underlying data, as we sometimes need it later */ \
614 1086 : ulong FD_EXPAND_THEN_CONCAT2(decl, _vm_addr) = FD_EXPAND_THEN_CONCAT2(decl, _box)->addr; \
615 1086 : /* Declare the size of the underlying data */ \
616 1086 : ulong FD_EXPAND_THEN_CONCAT2(decl, _len) = FD_EXPAND_THEN_CONCAT2(decl, _box)->len;
617 :
618 : #define VM_SYSCALL_CPI_SET_ACC_INFO_DATA_LEN( vm, acc_info, decl, len_ ) \
619 0 : FD_EXPAND_THEN_CONCAT2(decl, _box)->len = len_;
620 :
621 : #include "fd_vm_syscall_cpi_common.c"
622 :
623 : #undef VM_SYSCALL_CPI_ABI
624 : #undef VM_SYSCALL_CPI_INSTR_T
625 : #undef VM_SYSCALL_CPI_INSTR_ALIGN
626 : #undef VM_SYSCALL_CPI_INSTR_SIZE
627 : #undef VM_SYSCALL_CPI_ACC_META_T
628 : #undef VM_SYSCALL_CPI_ACC_META_ALIGN
629 : #undef VM_SYSCALL_CPI_ACC_META_SIZE
630 : #undef VM_SYSCALL_CPI_ACC_INFO_T
631 : #undef VM_SYSCALL_CPI_ACC_INFO_ALIGN
632 : #undef VM_SYSCALL_CPI_ACC_INFO_SIZE
633 : #undef VM_SYSCALL_CPI_INSTR_DATA_ADDR
634 : #undef VM_SYSCALL_CPI_INSTR_DATA_LEN
635 : #undef VM_SYSCALL_CPI_INSTR_ACCS_ADDR
636 : #undef VM_SYSCALL_CPI_INSTR_ACCS_LEN
637 : #undef VM_SYSCALL_CPI_INSTR_PROGRAM_ID
638 : #undef VM_SYSCALL_CPI_ACC_META_IS_WRITABLE
639 : #undef VM_SYSCALL_CPI_ACC_META_IS_SIGNER
640 : #undef VM_SYSCALL_CPI_ACC_META_PUBKEY
641 : #undef VM_SYSCALL_CPI_ACC_INFO_LAMPORTS
642 : #undef VM_SYSCALL_CPI_ACC_INFO_DATA
643 : #undef VM_SYSCALL_CPI_ACC_INFO_METADATA
644 : #undef VM_SYSCALL_CPI_SET_ACC_INFO_DATA_LEN
|