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