Line data Source code
1 : #include "fd_vm_syscall.h"
2 : #include "../../runtime/fd_borrowed_account.h"
3 : #include "../../runtime/fd_system_ids.h"
4 :
5 : /* FIXME: ALGO EFFICIENCY */
6 : static inline int
7 : fd_vm_syscall_cpi_is_signer( fd_pubkey_t const * account,
8 : fd_pubkey_t const * signers,
9 0 : ulong signers_cnt ) {
10 0 : for( ulong i=0UL; i<signers_cnt; i++ ) if( !memcmp( account->uc, signers[i].uc, sizeof(fd_pubkey_t) ) ) return 1;
11 0 : return 0;
12 0 : }
13 :
14 : /*
15 : fd_vm_prepare_instruction populates instruction_accounts and instruction_accounts_cnt
16 : with the instruction accounts ready for execution.
17 :
18 : The majority of this logic is taken from
19 : https://github.com/solana-labs/solana/blob/v1.17.22/program-runtime/src/invoke_context.rs#L535,
20 : and is not vm-specific, but a part of the runtime.
21 : TODO: should we move this out of the CPI section?
22 :
23 : The bulk of the logic is concerned with unifying the privileges for each duplicated account,
24 : ensuring that each duplicate account referenced has the same privileges. It also performs some
25 : priviledge checks, for example ensuring the necessary signatures are present.
26 :
27 : TODO: instruction calling convention: const parameters after non-const.
28 :
29 : Assumptions:
30 : - We do not have more than 256 unique accounts in the callee_instr.
31 : This limit comes from the fact that a Solana transaction cannot
32 : refefence more than 256 unique accounts, due to the transaction
33 : serialization format.
34 : - callee_instr is not null.
35 : - callee_instr->acct_pubkeys is at least as long as callee_instr->acct_cnt
36 : - instr_ctx->txn_ctx->accounts_cnt is less than UCHAR_MAX.
37 : This is likely because the transaction is limited to 256 accounts.
38 : - callee_instr->program_id is set to UCHAR_MAX if account is not in instr_ctx->txn_ctx.
39 : - instruction_accounts is a 256-length empty array.
40 :
41 : Parameters:
42 : - callee_instr
43 : - instr_ctx
44 : - instruction_accounts
45 : - instruction_accounts_cnt
46 : - signers
47 : - signers_cnt
48 :
49 : Returns:
50 : - instruction_accounts
51 : - instruction_accounts_cnt
52 : Populated with the instruction accounts with normalized permissions.
53 :
54 : TODO: is it possible to pass the transaction indexes of the accounts in?
55 : This would allow us to make some of these algorithms more efficient.
56 : */
57 : int
58 : fd_vm_prepare_instruction( fd_instr_info_t * callee_instr,
59 : fd_exec_instr_ctx_t * instr_ctx,
60 : fd_pubkey_t const * callee_program_id_pubkey,
61 : fd_pubkey_t const instr_acct_keys[ FD_INSTR_ACCT_MAX ],
62 : fd_instruction_account_t instruction_accounts[ FD_INSTR_ACCT_MAX ],
63 : ulong * instruction_accounts_cnt,
64 : fd_pubkey_t const * signers,
65 0 : ulong signers_cnt ) {
66 :
67 : /* De-duplicate the instruction accounts, using the same logic as Solana */
68 0 : ulong deduplicated_instruction_accounts_cnt = 0;
69 0 : fd_instruction_account_t deduplicated_instruction_accounts[256] = {0};
70 0 : ulong duplicate_indicies_cnt = 0;
71 0 : ulong duplicate_indices[256] = {0};
72 :
73 : /* Normalize the privileges of each instruction account in the callee, after de-duping
74 : the account references.
75 : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/program-runtime/src/invoke_context.rs#L540-L595 */
76 0 : for( ulong i=0UL; i<callee_instr->acct_cnt; i++ ) {
77 0 : ushort index_in_transaction = callee_instr->accounts[i].index_in_transaction;
78 0 : ushort index_in_caller = callee_instr->accounts[i].index_in_caller;
79 :
80 0 : if( index_in_transaction==USHORT_MAX ) {
81 : /* In this case the callee instruction is referencing an unknown account not listed in the
82 : transactions accounts. */
83 0 : FD_BASE58_ENCODE_32_BYTES( instr_acct_keys[i].uc, id_b58 );
84 0 : fd_log_collector_msg_many( instr_ctx, 2, "Instruction references an unknown account ", 42UL, id_b58, id_b58_len );
85 0 : FD_TXN_ERR_FOR_LOG_INSTR( instr_ctx->txn_ctx, FD_EXECUTOR_INSTR_ERR_MISSING_ACC, instr_ctx->txn_ctx->err.exec_err_idx );
86 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
87 0 : }
88 :
89 : /* If there was an instruction account before this one which referenced the same
90 : transaction account index, find it's index in the deduplicated_instruction_accounts
91 : array. */
92 0 : ulong duplicate_index = ULONG_MAX;
93 0 : for( ulong j=0UL; j<deduplicated_instruction_accounts_cnt; j++ ) {
94 0 : if( deduplicated_instruction_accounts[j].index_in_transaction==index_in_transaction ) {
95 0 : duplicate_index = j;
96 0 : break;
97 0 : }
98 0 : }
99 :
100 : /* If this was account referenced in a previous iteration, update the flags to include those set
101 : in this iteration. This ensures that after all the iterations, the de-duplicated account flags
102 : for each account are the union of all the flags in all the references to that account in this instruction. */
103 :
104 : /* TODO: FD_UNLIKELY? Need to check which branch is more common by running against a larger mainnet ledger */
105 : /* TODO: this code would maybe be easier to read if we inverted the branches */
106 0 : if( duplicate_index!=ULONG_MAX ) {
107 0 : if ( FD_UNLIKELY( duplicate_index >= deduplicated_instruction_accounts_cnt ) ) {
108 0 : FD_TXN_ERR_FOR_LOG_INSTR( instr_ctx->txn_ctx, FD_EXECUTOR_INSTR_ERR_MISSING_ACC, instr_ctx->txn_ctx->err.exec_err_idx );
109 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
110 0 : }
111 :
112 0 : duplicate_indices[duplicate_indicies_cnt++] = duplicate_index;
113 0 : fd_instruction_account_t * instruction_account = &deduplicated_instruction_accounts[duplicate_index];
114 0 : instruction_account->is_signer = !!(instruction_account->is_signer | callee_instr->accounts[i].is_signer);
115 0 : instruction_account->is_writable = !!(instruction_account->is_writable | callee_instr->accounts[i].is_writable);
116 0 : } else {
117 : /* In the case where the callee instruction is NOT a duplicate, we need to
118 : create the deduplicated_instruction_accounts fd_instruction_account_t object. */
119 :
120 : /* Add the instruction account to the duplicate indicies array */
121 0 : duplicate_indices[duplicate_indicies_cnt++] = deduplicated_instruction_accounts_cnt;
122 :
123 : /* Initialize the instruction account in the deduplicated_instruction_accounts array */
124 0 : fd_instruction_account_t * instruction_account = &deduplicated_instruction_accounts[deduplicated_instruction_accounts_cnt++];
125 0 : *instruction_account = fd_instruction_account_init( index_in_transaction,
126 0 : index_in_caller,
127 0 : (ushort)i,
128 0 : !!(callee_instr->accounts[i].is_writable),
129 0 : !!(callee_instr->accounts[i].is_signer) );
130 0 : }
131 0 : }
132 :
133 : /* Check the normalized account permissions for privilege escalation.
134 : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/program-runtime/src/invoke_context.rs#L596-L624 */
135 0 : for( ulong i = 0; i < deduplicated_instruction_accounts_cnt; i++ ) {
136 0 : fd_instruction_account_t * instruction_account = &deduplicated_instruction_accounts[i];
137 :
138 : /* https://github.com/anza-xyz/agave/blob/v2.1.14/program-runtime/src/invoke_context.rs#L390-L393 */
139 0 : fd_guarded_borrowed_account_t borrowed_caller_acct = {0};
140 0 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( instr_ctx, instruction_account->index_in_caller, &borrowed_caller_acct );
141 :
142 : /* Check that the account is not read-only in the caller but writable in the callee */
143 0 : if( FD_UNLIKELY( instruction_account->is_writable && !fd_borrowed_account_is_writable( &borrowed_caller_acct ) ) ) {
144 0 : FD_BASE58_ENCODE_32_BYTES( borrowed_caller_acct.acct->pubkey->uc, id_b58 );
145 0 : fd_log_collector_msg_many( instr_ctx, 2, id_b58, id_b58_len, "'s writable privilege escalated", 31UL );
146 0 : FD_TXN_ERR_FOR_LOG_INSTR( instr_ctx->txn_ctx, FD_EXECUTOR_INSTR_ERR_PRIVILEGE_ESCALATION, instr_ctx->txn_ctx->err.exec_err_idx );
147 0 : return FD_EXECUTOR_INSTR_ERR_PRIVILEGE_ESCALATION;
148 0 : }
149 :
150 : /* If the account is signed in the callee, it must be signed by the caller or the program */
151 0 : if ( FD_UNLIKELY( instruction_account->is_signer && !( fd_borrowed_account_is_signer( &borrowed_caller_acct ) || fd_vm_syscall_cpi_is_signer( borrowed_caller_acct.acct->pubkey, signers, signers_cnt) ) ) ) {
152 0 : FD_BASE58_ENCODE_32_BYTES( borrowed_caller_acct.acct->pubkey->uc, id_b58 );
153 0 : fd_log_collector_msg_many( instr_ctx, 2, id_b58, id_b58_len, "'s signer privilege escalated", 29UL );
154 0 : FD_TXN_ERR_FOR_LOG_INSTR( instr_ctx->txn_ctx, FD_EXECUTOR_INSTR_ERR_PRIVILEGE_ESCALATION, instr_ctx->txn_ctx->err.exec_err_idx );
155 0 : return FD_EXECUTOR_INSTR_ERR_PRIVILEGE_ESCALATION;
156 0 : }
157 0 : }
158 :
159 : /* Copy the accounts with their normalized permissions over to the final instruction_accounts array,
160 : and set the callee_instr acct_flags. */
161 0 : for (ulong i = 0; i < duplicate_indicies_cnt; i++) {
162 0 : ulong duplicate_index = duplicate_indices[i];
163 :
164 : /* Failing this condition is technically impossible, but it is probably safest to keep this in
165 : so that we throw InstructionError::NotEnoughAccountKeys at the same point at Solana does,
166 : in the event any surrounding code is changed.
167 : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/program-runtime/src/invoke_context.rs#L625-L633 */
168 0 : if ( FD_LIKELY( duplicate_index < deduplicated_instruction_accounts_cnt ) ) {
169 0 : instruction_accounts[i] = deduplicated_instruction_accounts[duplicate_index];
170 0 : callee_instr->accounts[i].is_writable = !!(instruction_accounts[i].is_writable);
171 0 : callee_instr->accounts[i].is_signer = !!(instruction_accounts[i].is_signer);
172 0 : } else {
173 0 : FD_TXN_ERR_FOR_LOG_INSTR( instr_ctx->txn_ctx, FD_EXECUTOR_INSTR_ERR_MISSING_ACC, instr_ctx->txn_ctx->err.exec_err_idx );
174 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
175 0 : }
176 0 : }
177 :
178 : /* Obtain the program account index and return a MissingAccount error if not found.
179 : https://github.com/anza-xyz/agave/blob/v2.1.14/program-runtime/src/invoke_context.rs#L430-L435 */
180 0 : int program_idx = fd_exec_instr_ctx_find_idx_of_instr_account( instr_ctx, callee_program_id_pubkey );
181 0 : if( FD_UNLIKELY( program_idx == -1 ) ) {
182 0 : FD_BASE58_ENCODE_32_BYTES( callee_program_id_pubkey->uc, id_b58 );
183 0 : fd_log_collector_msg_many( instr_ctx, 2, "Unknown program ", 16UL, id_b58, id_b58_len );
184 0 : FD_TXN_ERR_FOR_LOG_INSTR( instr_ctx->txn_ctx, FD_EXECUTOR_INSTR_ERR_MISSING_ACC, instr_ctx->txn_ctx->err.exec_err_idx );
185 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
186 0 : }
187 :
188 : /* Caller is in charge of setting an appropriate sentinel value (i.e., UCHAR_MAX) for callee_instr->program_id if not found.
189 : Borrow the program account here.
190 : https://github.com/anza-xyz/agave/blob/v2.1.14/program-runtime/src/invoke_context.rs#L436-L437 */
191 0 : fd_guarded_borrowed_account_t borrowed_program_account = {0};
192 0 : int err = fd_exec_instr_ctx_try_borrow_instr_account( instr_ctx, (ushort)program_idx, &borrowed_program_account );
193 0 : if( FD_UNLIKELY( err ) ) {
194 0 : FD_TXN_ERR_FOR_LOG_INSTR( instr_ctx->txn_ctx, err, instr_ctx->txn_ctx->err.exec_err_idx );
195 0 : return err;
196 0 : }
197 :
198 0 : if( FD_UNLIKELY( err ) ) {
199 : /* https://github.com/anza-xyz/agave/blob/a9ac3f55fcb2bc735db0d251eda89897a5dbaaaa/program-runtime/src/invoke_context.rs#L434 */
200 0 : FD_BASE58_ENCODE_32_BYTES( callee_program_id_pubkey->uc, id_b58 );
201 0 : fd_log_collector_msg_many( instr_ctx, 2, "Unknown program ", 16UL, id_b58, id_b58_len );
202 0 : FD_TXN_ERR_FOR_LOG_INSTR( instr_ctx->txn_ctx, FD_EXECUTOR_INSTR_ERR_MISSING_ACC, instr_ctx->txn_ctx->err.exec_err_idx );
203 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
204 0 : }
205 : /* Check that the program account is executable. We need to ensure that the
206 : program account is a valid instruction account.
207 : https://github.com/anza-xyz/agave/blob/v2.1.14/program-runtime/src/invoke_context.rs#L438 */
208 0 : if( !FD_FEATURE_ACTIVE_BANK( instr_ctx->txn_ctx->bank, remove_accounts_executable_flag_checks ) ) {
209 0 : if( FD_UNLIKELY( !fd_borrowed_account_is_executable( &borrowed_program_account ) ) ) {
210 0 : FD_BASE58_ENCODE_32_BYTES( callee_program_id_pubkey->uc, id_b58 );
211 0 : fd_log_collector_msg_many( instr_ctx, 3, "Account ", 8UL, id_b58, id_b58_len, " is not executable", 18UL );
212 0 : FD_TXN_ERR_FOR_LOG_INSTR( instr_ctx->txn_ctx, FD_EXECUTOR_INSTR_ERR_ACC_NOT_EXECUTABLE, instr_ctx->txn_ctx->err.exec_err_idx );
213 0 : return FD_EXECUTOR_INSTR_ERR_ACC_NOT_EXECUTABLE;
214 0 : }
215 0 : }
216 :
217 0 : *instruction_accounts_cnt = duplicate_indicies_cnt;
218 :
219 0 : return 0;
220 0 : }
221 :
222 : /**********************************************************************
223 : CROSS PROGRAM INVOCATION (Generic logic)
224 : **********************************************************************/
225 :
226 : /* FD_CPI_MAX_SIGNER_CNT is the max amount of PDA signer addresses that
227 : a cross-program invocation can include in an instruction.
228 :
229 : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/programs/bpf_loader/src/syscalls/mod.rs#L80 */
230 :
231 : #define FD_CPI_MAX_SIGNER_CNT (16UL)
232 :
233 : /* "Maximum number of account info structs that can be used in a single CPI
234 : invocation. A limit on account info structs is effectively the same as
235 : limiting the number of unique accounts. 128 was chosen to match the max
236 : number of locked accounts per transaction (MAX_TX_ACCOUNT_LOCKS)."
237 :
238 : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/sdk/program/src/syscalls/mod.rs#L25
239 : https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/programs/bpf_loader/src/syscalls/cpi.rs#L1011 */
240 :
241 0 : #define FD_CPI_MAX_ACCOUNT_INFOS (128UL)
242 : /* This is just encoding what Agave says in their code comments into a
243 : compile-time check, so if anyone ever inadvertently changes one of
244 : the limits, they will have to take a look. */
245 : FD_STATIC_ASSERT( FD_CPI_MAX_ACCOUNT_INFOS==MAX_TX_ACCOUNT_LOCKS, cpi_max_account_info );
246 : static inline ulong
247 0 : get_cpi_max_account_infos( fd_exec_txn_ctx_t const * txn_ctx ) {
248 0 : return fd_ulong_if( FD_FEATURE_ACTIVE_BANK( txn_ctx->bank, increase_tx_account_lock_limit ), FD_CPI_MAX_ACCOUNT_INFOS, 64UL );
249 0 : }
250 :
251 : /* Maximum CPI instruction data size. 10 KiB was chosen to ensure that CPI
252 : instructions are not more limited than transaction instructions if the size
253 : of transactions is doubled in the future.
254 :
255 : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/sdk/program/src/syscalls/mod.rs#L14 */
256 :
257 : #define FD_CPI_MAX_INSTRUCTION_DATA_LEN (10240UL)
258 :
259 : /* Maximum CPI instruction accounts. 255 was chosen to ensure that instruction
260 : accounts are always within the maximum instruction account limit for BPF
261 : program instructions.
262 :
263 : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/sdk/program/src/syscalls/mod.rs#L19
264 : https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/programs/bpf_loader/src/serialization.rs#L26 */
265 :
266 : #define FD_CPI_MAX_INSTRUCTION_ACCOUNTS (255UL)
267 :
268 :
269 : /* fd_vm_syscall_cpi_check_instruction contains common instruction acct
270 : count and data sz checks. Also consumes compute units proportional
271 : to instruction data size. */
272 :
273 : static int
274 : fd_vm_syscall_cpi_check_instruction( fd_vm_t const * vm,
275 : ulong acct_cnt,
276 0 : ulong data_sz ) {
277 : /* https://github.com/solana-labs/solana/blob/eb35a5ac1e7b6abe81947e22417f34508f89f091/programs/bpf_loader/src/syscalls/cpi.rs#L958-L959 */
278 0 : if( FD_FEATURE_ACTIVE_BANK( vm->instr_ctx->txn_ctx->bank, loosen_cpi_size_restriction ) ) {
279 0 : if( FD_UNLIKELY( data_sz > FD_CPI_MAX_INSTRUCTION_DATA_LEN ) ) {
280 0 : FD_LOG_WARNING(( "cpi: data too long (%#lx)", data_sz ));
281 : // SyscallError::MaxInstructionDataLenExceeded
282 0 : return FD_VM_SYSCALL_ERR_MAX_INSTRUCTION_DATA_LEN_EXCEEDED;
283 0 : }
284 0 : if( FD_UNLIKELY( acct_cnt > FD_CPI_MAX_INSTRUCTION_ACCOUNTS ) ) {
285 0 : FD_LOG_WARNING(( "cpi: too many accounts (%#lx)", acct_cnt ));
286 : // SyscallError::MaxInstructionAccountsExceeded
287 0 : return FD_VM_SYSCALL_ERR_MAX_INSTRUCTION_ACCOUNTS_EXCEEDED;
288 0 : }
289 0 : } else {
290 : // https://github.com/solana-labs/solana/blob/dbf06e258ae418097049e845035d7d5502fe1327/programs/bpf_loader/src/syscalls/cpi.rs#L1114
291 0 : ulong tot_sz = fd_ulong_sat_add( fd_ulong_sat_mul( FD_VM_RUST_ACCOUNT_META_SIZE, acct_cnt ), data_sz );
292 0 : if ( FD_UNLIKELY( tot_sz > FD_VM_MAX_CPI_INSTRUCTION_SIZE ) ) {
293 0 : FD_LOG_WARNING(( "cpi: instruction too long (%#lx)", tot_sz ));
294 : // SyscallError::InstructionTooLarge
295 0 : return FD_VM_SYSCALL_ERR_INSTRUCTION_TOO_LARGE;
296 0 : }
297 0 : }
298 :
299 0 : return FD_VM_SUCCESS;
300 0 : }
301 :
302 : /* https://github.com/anza-xyz/agave/blob/v3.0.1/syscalls/src/cpi.rs#L1134-L1169 */
303 : static inline int
304 : fd_vm_cpi_update_caller_account_region( fd_vm_t * vm,
305 : ulong instr_acc_idx,
306 : fd_vm_cpi_caller_account_t * caller_account,
307 0 : fd_borrowed_account_t * borrowed_account ) {
308 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1141-L1148 */
309 0 : ulong address_space_reserved_for_account;
310 0 : if( vm->stricter_abi_and_runtime_constraints && vm->is_deprecated ) {
311 0 : address_space_reserved_for_account = caller_account->orig_data_len;
312 0 : } else {
313 0 : address_space_reserved_for_account = fd_ulong_sat_add( caller_account->orig_data_len, MAX_PERMITTED_DATA_INCREASE );
314 0 : }
315 :
316 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1159-L1164 */
317 0 : if( address_space_reserved_for_account > 0UL ) {
318 : /* Note that we don't special-case direct mapping here, as Agave does,
319 : because we do not create regions using CoW upon resize like Agave does.
320 :
321 : Therefore we do not need the logic in the Agave code to create a new
322 : region, as we have already created all the regions for each account.
323 :
324 : Therefore we do not have equivalents of Agave's
325 : modify_memory_region_of_account and create_memory_region_of_account
326 : functions, but we instead inline this logic directly below. */
327 0 : fd_vm_acc_region_meta_t * acc_region_meta = &vm->acc_region_metas[instr_acc_idx];
328 0 : fd_vm_input_region_t * region = &vm->input_mem_regions[acc_region_meta->region_idx + 1UL];
329 :
330 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L1159-L1165 */
331 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/program-runtime/src/serialization.rs#L23-L35 */
332 0 : region->region_sz = (uint)fd_borrowed_account_get_data_len( borrowed_account );
333 :
334 0 : int err;
335 0 : int is_writable = fd_borrowed_account_can_data_be_changed( borrowed_account, &err );
336 :
337 0 : region->is_writable = (uchar)is_writable && ( err == FD_EXECUTOR_INSTR_SUCCESS );
338 0 : }
339 :
340 0 : return FD_VM_SUCCESS;
341 0 : }
342 :
343 : /**********************************************************************
344 : CROSS PROGRAM INVOCATION HELPERS
345 : **********************************************************************/
346 :
347 : static inline int
348 : fd_vm_syscall_cpi_check_id( fd_pubkey_t const * program_id,
349 0 : uchar const * loader ) {
350 0 : return !memcmp( program_id, loader, sizeof(fd_pubkey_t) );
351 0 : }
352 :
353 : /* fd_vm_syscall_cpi_is_precompile returns true if the given program_id
354 : corresponds to a precompile. It does this by checking against a hardcoded
355 : list of known pre-compiles.
356 :
357 : This mirrors the behaviour in solana_sdk::precompiles::is_precompile
358 : https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/sdk/src/precompiles.rs#L93
359 : */
360 : static inline int
361 0 : fd_vm_syscall_cpi_is_precompile( fd_pubkey_t const * program_id, fd_exec_txn_ctx_t const * txn_ctx ) {
362 0 : return fd_vm_syscall_cpi_check_id(program_id, fd_solana_keccak_secp_256k_program_id.key) |
363 0 : fd_vm_syscall_cpi_check_id(program_id, fd_solana_ed25519_sig_verify_program_id.key) |
364 0 : ( fd_vm_syscall_cpi_check_id(program_id, fd_solana_secp256r1_program_id.key) &&
365 0 : FD_FEATURE_ACTIVE_BANK( txn_ctx->bank, enable_secp256r1_precompile ) );
366 0 : }
367 :
368 : /* fd_vm_syscall_cpi_check_authorized_program corresponds to
369 : solana_bpf_loader_program::syscalls::cpi::check_authorized_program:
370 : https://github.com/solana-labs/solana/blob/2afde1b028ed4593da5b6c735729d8994c4bfac6/programs/bpf_loader/src/syscalls/cpi.rs#L1032
371 :
372 : It determines if the given program_id is authorized to execute a CPI call.
373 :
374 : FIXME: return type
375 : */
376 : static inline ulong
377 : fd_vm_syscall_cpi_check_authorized_program( fd_pubkey_t const * program_id,
378 : fd_exec_txn_ctx_t const * txn_ctx,
379 : uchar const * instruction_data,
380 0 : ulong instruction_data_len ) {
381 : /* FIXME: do this in a branchless manner? using bitwise comparison would probably be faster */
382 0 : return ( fd_vm_syscall_cpi_check_id(program_id, fd_solana_native_loader_id.key) ||
383 0 : fd_vm_syscall_cpi_check_id(program_id, fd_solana_bpf_loader_program_id.key) ||
384 0 : fd_vm_syscall_cpi_check_id(program_id, fd_solana_bpf_loader_deprecated_program_id.key) ||
385 0 : ( fd_vm_syscall_cpi_check_id(program_id, fd_solana_bpf_loader_upgradeable_program_id.key) &&
386 0 : !(( instruction_data_len != 0 && instruction_data[0] == fd_bpf_upgradeable_loader_program_instruction_enum_upgrade ) ||
387 0 : ( instruction_data_len != 0 && instruction_data[0] == fd_bpf_upgradeable_loader_program_instruction_enum_set_authority ) ||
388 0 : ( FD_FEATURE_ACTIVE_BANK( txn_ctx->bank, enable_bpf_loader_set_authority_checked_ix ) &&
389 0 : ( instruction_data_len != 0 && instruction_data[0] == fd_bpf_upgradeable_loader_program_instruction_enum_set_authority_checked )) ||
390 0 : ( FD_FEATURE_ACTIVE_BANK( txn_ctx->bank, enable_extend_program_checked ) &&
391 0 : ( instruction_data_len != 0 && instruction_data[0] == fd_bpf_upgradeable_loader_program_instruction_enum_extend_program_checked )) ||
392 0 : ( instruction_data_len != 0 && instruction_data[0] == fd_bpf_upgradeable_loader_program_instruction_enum_close ))) ||
393 0 : fd_vm_syscall_cpi_is_precompile( program_id, txn_ctx ) );
394 0 : }
395 :
396 : /* The data and lamports fields are in an Rc<Refcell<T>> in the Rust ABI AccountInfo.
397 : These macros perform the equivalent of Rc<Refcell<T>>.as_ptr() in Agave.
398 : This function doesn't actually touch any memory.
399 : It performs pointer arithmetic.
400 : */
401 : FD_FN_CONST static inline
402 0 : ulong vm_syscall_cpi_acc_info_rc_refcell_as_ptr( ulong rc_refcell_vaddr ) {
403 0 : return (ulong) &(((fd_vm_rc_refcell_t *)rc_refcell_vaddr)->payload);
404 0 : }
405 :
406 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L310-L316
407 : */
408 : FD_FN_CONST static inline
409 0 : ulong vm_syscall_cpi_data_len_vaddr_c( ulong acct_info_vaddr, ulong data_len_haddr, ulong acct_info_haddr ) {
410 0 : return fd_ulong_sat_sub( fd_ulong_sat_add( acct_info_vaddr, data_len_haddr ), acct_info_haddr );
411 0 : }
412 :
413 : /**********************************************************************
414 : CROSS PROGRAM INVOCATION (C ABI)
415 : **********************************************************************/
416 :
417 : #define VM_SYSCALL_CPI_ABI c
418 0 : #define VM_SYSCALL_CPI_INSTR_T fd_vm_c_instruction_t
419 : #define VM_SYSCALL_CPI_INSTR_ALIGN (FD_VM_C_INSTRUCTION_ALIGN)
420 : #define VM_SYSCALL_CPI_INSTR_SIZE (FD_VM_C_INSTRUCTION_SIZE)
421 0 : #define VM_SYSCALL_CPI_ACC_META_T fd_vm_c_account_meta_t
422 : #define VM_SYSCALL_CPI_ACC_META_ALIGN (FD_VM_C_ACCOUNT_META_ALIGN)
423 : #define VM_SYSCALL_CPI_ACC_META_SIZE (FD_VM_C_ACCOUNT_META_SIZE)
424 0 : #define VM_SYSCALL_CPI_ACC_INFO_T fd_vm_c_account_info_t
425 : #define VM_SYSCALL_CPI_ACC_INFO_ALIGN (FD_VM_C_ACCOUNT_INFO_ALIGN)
426 0 : #define VM_SYSCALL_CPI_ACC_INFO_SIZE (FD_VM_C_ACCOUNT_INFO_SIZE)
427 :
428 : /* VM_SYSCALL_CPI_INSTR_T accessors */
429 : #define VM_SYSCALL_CPI_INSTR_DATA_ADDR( instr ) instr->data_addr
430 0 : #define VM_SYSCALL_CPI_INSTR_DATA_LEN( instr ) instr->data_len
431 : #define VM_SYSCALL_CPI_INSTR_ACCS_ADDR( instr ) instr->accounts_addr
432 0 : #define VM_SYSCALL_CPI_INSTR_ACCS_LEN( instr ) instr->accounts_len
433 : #define VM_SYSCALL_CPI_INSTR_PROGRAM_ID( vm, instr ) \
434 0 : FD_VM_MEM_HADDR_LD( vm, instr->program_id_addr, alignof(uchar), sizeof(fd_pubkey_t) )
435 :
436 : /* VM_SYSCALL_CPI_ACC_META_T accessors */
437 0 : #define VM_SYSCALL_CPI_ACC_META_IS_WRITABLE( acc_meta ) acc_meta->is_writable
438 0 : #define VM_SYSCALL_CPI_ACC_META_IS_SIGNER( acc_meta ) acc_meta->is_signer
439 : #define VM_SYSCALL_CPI_ACC_META_PUBKEY( vm, acc_meta ) \
440 0 : FD_VM_MEM_HADDR_LD( vm, acc_meta->pubkey_addr, alignof(uchar), sizeof(fd_pubkey_t) )
441 :
442 : /* VM_SYSCALL_CPI_ACC_INFO_T accessors */
443 : #define VM_SYSCALL_CPI_ACC_INFO_LAMPORTS_VADDR( vm, acc_info, decl ) \
444 0 : ulong decl = acc_info->lamports_addr;
445 : #define VM_SYSCALL_CPI_ACC_INFO_LAMPORTS( vm, acc_info, decl ) \
446 0 : ulong * decl = FD_VM_MEM_HADDR_ST( vm, acc_info->lamports_addr, alignof(ulong), sizeof(ulong) );
447 :
448 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L304 */
449 : #define VM_SYSCALL_CPI_ACC_INFO_DATA_VADDR( vm, acc_info, decl ) \
450 0 : ulong decl = acc_info->data_addr;
451 : #define VM_SYSCALL_CPI_ACC_INFO_DATA( vm, acc_info, decl ) \
452 0 : uchar * decl = FD_VM_MEM_SLICE_HADDR_ST( vm, acc_info->data_addr, alignof(uchar), acc_info->data_sz ); \
453 0 : ulong FD_EXPAND_THEN_CONCAT2(decl, _vm_addr) = acc_info->data_addr; \
454 0 : ulong FD_EXPAND_THEN_CONCAT2(decl, _len) = acc_info->data_sz;
455 :
456 : #define VM_SYSCALL_CPI_ACC_INFO_METADATA( vm, acc_info, decl ) \
457 : ulong FD_EXPAND_THEN_CONCAT2(decl, _vm_addr) = acc_info->data_addr; \
458 : ulong FD_EXPAND_THEN_CONCAT2(decl, _len) = acc_info->data_sz;
459 :
460 : #define VM_SYSCALL_CPI_SET_ACC_INFO_DATA_GET_LEN( vm, acc_info, decl ) \
461 0 : ulong FD_EXPAND_THEN_CONCAT2(decl, _len) = acc_info->data_sz;
462 :
463 : #include "fd_vm_syscall_cpi_common.c"
464 :
465 : #undef VM_SYSCALL_CPI_ABI
466 : #undef VM_SYSCALL_CPI_INSTR_T
467 : #undef VM_SYSCALL_CPI_INSTR_ALIGN
468 : #undef VM_SYSCALL_CPI_INSTR_SIZE
469 : #undef VM_SYSCALL_CPI_ACC_META_T
470 : #undef VM_SYSCALL_CPI_ACC_META_ALIGN
471 : #undef VM_SYSCALL_CPI_ACC_META_SIZE
472 : #undef VM_SYSCALL_CPI_ACC_INFO_T
473 : #undef VM_SYSCALL_CPI_ACC_INFO_ALIGN
474 : #undef VM_SYSCALL_CPI_ACC_INFO_SIZE
475 : #undef VM_SYSCALL_CPI_INSTR_DATA_ADDR
476 : #undef VM_SYSCALL_CPI_INSTR_DATA_LEN
477 : #undef VM_SYSCALL_CPI_INSTR_ACCS_ADDR
478 : #undef VM_SYSCALL_CPI_INSTR_ACCS_LEN
479 : #undef VM_SYSCALL_CPI_INSTR_PROGRAM_ID
480 : #undef VM_SYSCALL_CPI_ACC_META_IS_WRITABLE
481 : #undef VM_SYSCALL_CPI_ACC_META_IS_SIGNER
482 : #undef VM_SYSCALL_CPI_ACC_META_PUBKEY
483 : #undef VM_SYSCALL_CPI_ACC_INFO_LAMPORTS_VADDR
484 : #undef VM_SYSCALL_CPI_ACC_INFO_LAMPORTS
485 : #undef VM_SYSCALL_CPI_ACC_INFO_DATA_VADDR
486 : #undef VM_SYSCALL_CPI_ACC_INFO_DATA
487 : #undef VM_SYSCALL_CPI_ACC_INFO_METADATA
488 : #undef VM_SYSCALL_CPI_SET_ACC_INFO_DATA_GET_LEN
489 :
490 : /**********************************************************************
491 : CROSS PROGRAM INVOCATION (Rust ABI)
492 : **********************************************************************/
493 :
494 : #define VM_SYSCALL_CPI_ABI rust
495 0 : #define VM_SYSCALL_CPI_INSTR_T fd_vm_rust_instruction_t
496 : #define VM_SYSCALL_CPI_INSTR_ALIGN (FD_VM_RUST_INSTRUCTION_ALIGN)
497 : #define VM_SYSCALL_CPI_INSTR_SIZE (FD_VM_RUST_INSTRUCTION_SIZE)
498 0 : #define VM_SYSCALL_CPI_ACC_META_T fd_vm_rust_account_meta_t
499 : #define VM_SYSCALL_CPI_ACC_META_ALIGN (FD_VM_RUST_ACCOUNT_META_ALIGN)
500 : #define VM_SYSCALL_CPI_ACC_META_SIZE (FD_VM_RUST_ACCOUNT_META_SIZE)
501 0 : #define VM_SYSCALL_CPI_ACC_INFO_T fd_vm_rust_account_info_t
502 : #define VM_SYSCALL_CPI_ACC_INFO_ALIGN (FD_VM_RUST_ACCOUNT_INFO_ALIGN)
503 0 : #define VM_SYSCALL_CPI_ACC_INFO_SIZE (FD_VM_RUST_ACCOUNT_INFO_SIZE)
504 :
505 : /* VM_SYSCALL_CPI_INSTR_T accessors */
506 : #define VM_SYSCALL_CPI_INSTR_DATA_ADDR( instr ) instr->data.addr
507 0 : #define VM_SYSCALL_CPI_INSTR_DATA_LEN( instr ) instr->data.len
508 : #define VM_SYSCALL_CPI_INSTR_ACCS_ADDR( instr ) instr->accounts.addr
509 0 : #define VM_SYSCALL_CPI_INSTR_ACCS_LEN( instr ) instr->accounts.len
510 0 : #define VM_SYSCALL_CPI_INSTR_PROGRAM_ID( vm, instr ) instr->pubkey
511 :
512 : /* VM_SYSCALL_CPI_ACC_META_T accessors */
513 0 : #define VM_SYSCALL_CPI_ACC_META_IS_WRITABLE( acc_meta ) acc_meta->is_writable
514 0 : #define VM_SYSCALL_CPI_ACC_META_IS_SIGNER( acc_meta ) acc_meta->is_signer
515 0 : #define VM_SYSCALL_CPI_ACC_META_PUBKEY( vm, acc_meta ) acc_meta->pubkey
516 :
517 : /* VM_SYSCALL_CPI_ACC_INFO_T accessors */
518 :
519 : /* The lamports and the account data are stored behind RefCells,
520 : so we have an additional layer of indirection to unwrap. */
521 : #define VM_SYSCALL_CPI_ACC_INFO_LAMPORTS_VADDR( vm, acc_info, decl ) \
522 0 : ulong const * FD_EXPAND_THEN_CONCAT2(decl, _hptr_) = \
523 0 : FD_VM_MEM_HADDR_LD( vm, vm_syscall_cpi_acc_info_rc_refcell_as_ptr( acc_info->lamports_box_addr ), FD_VM_RC_REFCELL_ALIGN, sizeof(ulong) ); \
524 0 : /* Extract the vaddr embedded in the RefCell */ \
525 0 : ulong decl = *FD_EXPAND_THEN_CONCAT2(decl, _hptr_);
526 :
527 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L184-L195 */
528 : #define VM_SYSCALL_CPI_ACC_INFO_LAMPORTS( vm, acc_info, decl ) \
529 0 : ulong FD_EXPAND_THEN_CONCAT2(decl, _vaddr_) = \
530 0 : *((ulong const *)FD_VM_MEM_HADDR_LD( vm, vm_syscall_cpi_acc_info_rc_refcell_as_ptr( acc_info->lamports_box_addr ), FD_VM_RC_REFCELL_ALIGN, sizeof(ulong) )); \
531 0 : ulong * decl = FD_VM_MEM_HADDR_ST( vm, FD_EXPAND_THEN_CONCAT2(decl, _vaddr_), alignof(ulong), sizeof(ulong) );
532 :
533 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L184-L195 */
534 : #define VM_SYSCALL_CPI_ACC_INFO_DATA_VADDR( vm, acc_info, decl ) \
535 0 : if( FD_UNLIKELY( vm->stricter_abi_and_runtime_constraints && acc_info->data_box_addr >= FD_VM_MEM_MAP_INPUT_REGION_START ) ) { \
536 0 : FD_VM_ERR_FOR_LOG_SYSCALL( vm, FD_VM_SYSCALL_ERR_INVALID_POINTER ); \
537 0 : return FD_VM_SYSCALL_ERR_INVALID_POINTER; \
538 0 : } \
539 0 : /* Translate the vaddr to the slice */ \
540 0 : fd_vm_vec_t const * FD_EXPAND_THEN_CONCAT2(decl, _hptr_) = \
541 0 : FD_VM_MEM_HADDR_LD( vm, vm_syscall_cpi_acc_info_rc_refcell_as_ptr( acc_info->data_box_addr ), FD_VM_RC_REFCELL_ALIGN, sizeof(fd_vm_vec_t) ); \
542 0 : /* Extract the vaddr embedded in the slice */ \
543 0 : ulong decl = FD_EXPAND_THEN_CONCAT2(decl, _hptr_)->addr;
544 :
545 : /* https://github.com/anza-xyz/agave/blob/v3.0.4/syscalls/src/cpi.rs#L212-L221 */
546 : #define VM_SYSCALL_CPI_ACC_INFO_DATA_LEN_VADDR( vm, acc_info, decl ) \
547 0 : ulong decl = fd_ulong_sat_add( vm_syscall_cpi_acc_info_rc_refcell_as_ptr( acc_info->data_box_addr ), sizeof(ulong) );
548 :
549 : #define VM_SYSCALL_CPI_ACC_INFO_DATA( vm, acc_info, decl ) \
550 : /* Translate the vaddr to the slice */ \
551 0 : fd_vm_vec_t const * FD_EXPAND_THEN_CONCAT2(decl, _hptr_) = \
552 0 : FD_VM_MEM_HADDR_LD( vm, vm_syscall_cpi_acc_info_rc_refcell_as_ptr( acc_info->data_box_addr ), FD_VM_RC_REFCELL_ALIGN, sizeof(fd_vm_vec_t) ); \
553 0 : /* Declare the vaddr of the slice's underlying byte array */ \
554 0 : ulong FD_EXPAND_THEN_CONCAT2(decl, _vm_addr) = FD_EXPAND_THEN_CONCAT2(decl, _hptr_)->addr; \
555 0 : /* Declare the size of the slice's underlying byte array */ \
556 0 : ulong FD_EXPAND_THEN_CONCAT2(decl, _len) = FD_EXPAND_THEN_CONCAT2(decl, _hptr_)->len; \
557 0 : /* Translate the vaddr to the underlying byte array */ \
558 0 : uchar * decl = FD_VM_MEM_SLICE_HADDR_ST( \
559 0 : vm, FD_EXPAND_THEN_CONCAT2(decl, _hptr_)->addr, alignof(uchar), FD_EXPAND_THEN_CONCAT2(decl, _hptr_)->len );
560 :
561 : #define VM_SYSCALL_CPI_ACC_INFO_METADATA( vm, acc_info, decl ) \
562 : /* Translate the vaddr to the slice */ \
563 : fd_vm_vec_t const * FD_EXPAND_THEN_CONCAT2(decl, _hptr_) = \
564 : FD_VM_MEM_HADDR_LD( vm, vm_syscall_cpi_acc_info_rc_refcell_as_ptr( acc_info->data_box_addr ), FD_VM_RC_REFCELL_ALIGN, sizeof(fd_vm_vec_t) ); \
565 : /* Declare the vaddr of the slice's underlying byte array */ \
566 : ulong FD_EXPAND_THEN_CONCAT2(decl, _vm_addr) = FD_EXPAND_THEN_CONCAT2(decl, _hptr_)->addr; \
567 : /* Declare the size of the slice's underlying byte array */ \
568 : ulong FD_EXPAND_THEN_CONCAT2(decl, _len) = FD_EXPAND_THEN_CONCAT2(decl, _hptr_)->len;
569 :
570 : #define VM_SYSCALL_CPI_ACC_INFO_LAMPORTS_RC_REFCELL_VADDR( vm, acc_info, decl ) \
571 0 : ulong decl = vm_syscall_cpi_acc_info_rc_refcell_as_ptr( acc_info->lamports_box_addr );
572 :
573 : #define VM_SYSCALL_CPI_ACC_INFO_DATA_RC_REFCELL_VADDR( vm, acc_info, decl ) \
574 : ulong decl = vm_syscall_cpi_acc_info_rc_refcell_as_ptr( acc_info->data_box_addr );
575 :
576 : #define VM_SYSCALL_CPI_SET_ACC_INFO_DATA_GET_LEN( vm, acc_info, decl ) \
577 0 : ulong FD_EXPAND_THEN_CONCAT2(decl, _len) = FD_EXPAND_THEN_CONCAT2(decl, _hptr_)->len;
578 :
579 : #include "fd_vm_syscall_cpi_common.c"
580 :
581 : #undef VM_SYSCALL_CPI_ABI
582 : #undef VM_SYSCALL_CPI_INSTR_T
583 : #undef VM_SYSCALL_CPI_INSTR_ALIGN
584 : #undef VM_SYSCALL_CPI_INSTR_SIZE
585 : #undef VM_SYSCALL_CPI_ACC_META_T
586 : #undef VM_SYSCALL_CPI_ACC_META_ALIGN
587 : #undef VM_SYSCALL_CPI_ACC_META_SIZE
588 : #undef VM_SYSCALL_CPI_ACC_INFO_T
589 : #undef VM_SYSCALL_CPI_ACC_INFO_ALIGN
590 : #undef VM_SYSCALL_CPI_ACC_INFO_SIZE
591 : #undef VM_SYSCALL_CPI_INSTR_DATA_ADDR
592 : #undef VM_SYSCALL_CPI_INSTR_DATA_LEN
593 : #undef VM_SYSCALL_CPI_INSTR_ACCS_ADDR
594 : #undef VM_SYSCALL_CPI_INSTR_ACCS_LEN
595 : #undef VM_SYSCALL_CPI_INSTR_PROGRAM_ID
596 : #undef VM_SYSCALL_CPI_ACC_META_IS_WRITABLE
597 : #undef VM_SYSCALL_CPI_ACC_META_IS_SIGNER
598 : #undef VM_SYSCALL_CPI_ACC_META_PUBKEY
599 : #undef VM_SYSCALL_CPI_ACC_INFO_LAMPORTS_VADDR
600 : #undef VM_SYSCALL_CPI_ACC_INFO_LAMPORTS
601 : #undef VM_SYSCALL_CPI_ACC_INFO_DATA_VADDR
602 : #undef VM_SYSCALL_CPI_ACC_INFO_DATA
603 : #undef VM_SYSCALL_CPI_ACC_INFO_DATA_LEN_VADDR
604 : #undef VM_SYSCALL_CPI_ACC_INFO_METADATA
605 : #undef VM_SYSCALL_CPI_ACC_INFO_LAMPORTS_RC_REFCELL_VADDR
606 : #undef VM_SYSCALL_CPI_ACC_INFO_DATA_RC_REFCELL_VADDR
607 : #undef VM_SYSCALL_CPI_SET_ACC_INFO_DATA_GET_LEN
|