Line data Source code
1 : #include "fd_config_program.h" 2 : #include "../fd_borrowed_account.h" 3 : #include "../fd_executor.h" 4 : #include "../fd_system_ids.h" 5 : #include "../context/fd_exec_instr_ctx.h" 6 : #include "../../log_collector/fd_log_collector.h" 7 : 8 : /* Useful links: 9 : 10 : https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs */ 11 : 12 : #define DEFAULT_COMPUTE_UNITS 450UL 13 : 14 : /* The bound on the number of keys that could get passed into the config 15 : program is bounded by the TXN_MTU of 1232 bytes. Assuming that the 16 : vector of config keys comprises the entire transaction, then we can 17 : have 1232(bytes)/(33 bytes/key) = 37 keys. So our bound is equal to 18 : sizeof(fd_config_keys_t) + 37*sizeof(fd_config_keys_pair_t) = 1237 bytes. */ 19 : 20 : #define CONFIG_INSTRUCTION_KEYS_FOOTPRINT (1237UL) 21 : 22 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L16 */ 23 : 24 : static int 25 0 : _process_config_instr( fd_exec_instr_ctx_t * ctx ) { 26 : 27 0 : # define ACC_IDX_CONFIG ((uchar)0) 28 : 29 0 : int err; 30 : 31 : /* Deserialize the Config Program instruction data, which consists only of the ConfigKeys 32 : https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L21 */ 33 0 : if( FD_UNLIKELY( ctx->instr->data_sz>FD_TXN_MTU ) ) { 34 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA; 35 0 : } 36 : 37 0 : int decode_result; 38 0 : uchar key_list_mem[ CONFIG_INSTRUCTION_KEYS_FOOTPRINT ] __attribute__((aligned(FD_CONFIG_KEYS_ALIGN))); 39 0 : fd_config_keys_t * key_list = fd_bincode_decode_static( 40 0 : config_keys, key_list_mem, 41 0 : ctx->instr->data, 42 0 : ctx->instr->data_sz, 43 0 : &decode_result ); 44 0 : if( FD_UNLIKELY( decode_result != FD_BINCODE_SUCCESS ) ) { 45 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA; 46 0 : } 47 : 48 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L22-L26 */ 49 : 50 0 : int is_config_account_signer = 0; 51 0 : fd_pubkey_t const * config_account_key = NULL; 52 : 53 : /* https://github.com/anza-xyz/agave/blob/v2.1.4/programs/config/src/config_processor.rs#L26 */ 54 0 : fd_guarded_borrowed_account_t config_acc_rec = {0}; 55 0 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( ctx, ACC_IDX_CONFIG, &config_acc_rec ); 56 : 57 0 : config_account_key = config_acc_rec.acct->pubkey; 58 : 59 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L27 */ 60 : 61 0 : is_config_account_signer = fd_instr_acc_is_signer_idx( ctx->instr, ACC_IDX_CONFIG, NULL ); 62 : 63 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L29-L31 */ 64 : 65 0 : if( FD_UNLIKELY( 0!=memcmp( fd_borrowed_account_get_owner( &config_acc_rec ), fd_solana_config_program_id.key, sizeof(fd_pubkey_t) ) ) ) { 66 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_OWNER; 67 0 : } 68 : 69 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L33-L40 */ 70 : 71 0 : uchar current_data_mem[ CONFIG_INSTRUCTION_KEYS_FOOTPRINT ] __attribute__((aligned(FD_CONFIG_KEYS_ALIGN))); 72 0 : fd_config_keys_t * current_data = fd_bincode_decode_static( 73 0 : config_keys, current_data_mem, 74 0 : fd_borrowed_account_get_data( &config_acc_rec ), 75 0 : fd_borrowed_account_get_data_len( &config_acc_rec ), 76 0 : &decode_result ); 77 0 : if( FD_UNLIKELY( decode_result!=FD_BINCODE_SUCCESS ) ) { 78 : //TODO: full log, including err 79 0 : fd_log_collector_msg_literal( ctx, "Unable to deserialize config account" ); 80 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA; 81 0 : } 82 : 83 : /* https://github.com/anza-xyz/agave/blob/v2.1.4/programs/config/src/config_processor.rs#L43 */ 84 : 85 0 : fd_borrowed_account_drop( &config_acc_rec ); 86 : 87 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L44-L49 */ 88 : 89 0 : fd_pubkey_t current_signer_keys[ 37UL ]; 90 0 : ulong current_signer_key_cnt = 0UL; 91 : 92 0 : for( ulong i=0UL; i < current_data->keys_len; i++ ) { 93 0 : if( current_data->keys[i].signer ) { 94 0 : current_signer_keys[ current_signer_key_cnt++ ] = current_data->keys[i].key; 95 0 : } 96 0 : } 97 : 98 : /* If we have no keys in the account, require the config account to have signed the transaction 99 : https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L50-L56 */ 100 : 101 0 : if( FD_UNLIKELY( current_signer_key_cnt==0UL && !is_config_account_signer ) ) { 102 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE; 103 0 : } 104 : 105 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L58 */ 106 : 107 0 : ulong counter = 0UL; 108 : /* Invariant: counter <= key_list.keys_len */ 109 : 110 0 : for( ulong i=0UL; i<key_list->keys_len; i++ ) { 111 0 : if( !key_list->keys[i].signer ) continue; 112 0 : fd_pubkey_t const * signer = &key_list->keys[i].key; 113 : 114 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L60 */ 115 : 116 0 : counter++; 117 : 118 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L61 */ 119 : 120 0 : if( 0!=memcmp( signer, config_account_key, sizeof(fd_pubkey_t) ) ) { 121 : 122 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L62-L71 */ 123 : 124 : /* Intentionally don't use the scoping macro here because Anza maps the 125 : error to missing required signature if the try borrow fails */ 126 0 : fd_borrowed_account_t signer_account; 127 0 : int borrow_err = fd_exec_instr_ctx_try_borrow_instr_account( ctx, (uchar)counter, &signer_account ); 128 0 : if( FD_UNLIKELY( borrow_err ) ) { 129 : /* Max msg_sz: 33 - 2 + 45 = 76 < 127 => we can use printf */ 130 0 : FD_BASE58_ENCODE_32_BYTES( signer->uc, signer_b58 ); 131 0 : fd_log_collector_printf_dangerous_max_127( ctx, 132 0 : "account %s is not in account list", signer_b58 ); 133 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE; 134 0 : } 135 : 136 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L72-L79 */ 137 : 138 0 : if( FD_UNLIKELY( !fd_instr_acc_is_signer_idx( ctx->instr, (uchar)counter, NULL ) ) ) { 139 : /* Max msg_sz: 33 - 2 + 45 = 76 < 127 => we can use printf */ 140 0 : FD_BASE58_ENCODE_32_BYTES( signer->uc, signer_b58 ); 141 0 : fd_log_collector_printf_dangerous_max_127( ctx, 142 0 : "account %s signer_key().is_none()", signer_b58 ); 143 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE; 144 0 : } 145 : 146 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L80-L87 */ 147 : 148 0 : if( FD_UNLIKELY( 0!=memcmp( signer_account.acct->pubkey, signer, sizeof(fd_pubkey_t) ) ) ) { 149 : /* Max msg_sz: 53 - 3 + 20 = 70 < 127 => we can use printf */ 150 0 : fd_log_collector_printf_dangerous_max_127( ctx, 151 0 : "account[%lu].signer_key() does not match Config data)", counter+1 ); 152 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE; 153 0 : } 154 : 155 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L89-L98 */ 156 : 157 0 : if( current_data->keys_len>0UL ) { 158 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L90 */ 159 0 : int is_signer = 0; 160 0 : for( ulong j=0UL; j<current_signer_key_cnt; j++ ) { 161 0 : if( 0==memcmp( ¤t_signer_keys[j], signer, sizeof(fd_pubkey_t) ) ) { 162 0 : is_signer = 1; 163 0 : break; 164 0 : } 165 0 : } 166 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L97 */ 167 0 : if( FD_UNLIKELY( !is_signer ) ) { 168 : /* Max msg_sz: 39 - 2 + 45 = 82 < 127 => we can use printf */ 169 0 : FD_BASE58_ENCODE_32_BYTES( signer->uc, signer_b58 ); 170 0 : fd_log_collector_printf_dangerous_max_127( ctx, 171 0 : "account %s is not in stored signer list", signer_b58 ); 172 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE; 173 0 : } 174 0 : } 175 : 176 : /* implicit drop of signer account */ 177 : 178 0 : } else if( !is_config_account_signer ) { 179 : 180 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L101 */ 181 0 : fd_log_collector_msg_literal( ctx, "account[0].signer_key().is_none()" ); 182 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE; 183 : 184 0 : } 185 0 : } 186 : 187 : /* Disallow duplicate keys 188 : https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L105-L115 189 : 190 : TODO: Agave uses a O(n log n) algorithm here */ 191 0 : for( ulong i = 0; i < key_list->keys_len; i++ ) { 192 0 : for( ulong j = 0; j < key_list->keys_len; j++ ) { 193 0 : if( i == j ) continue; 194 : 195 0 : if( FD_UNLIKELY( memcmp( &key_list->keys[i].key, &key_list->keys[j].key, sizeof(fd_pubkey_t) ) == 0 && 196 0 : key_list->keys[i].signer == key_list->keys[j].signer ) ) { 197 0 : fd_log_collector_msg_literal( ctx, "new config contains duplicate keys" ); 198 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ARG; 199 0 : } 200 0 : } 201 0 : } 202 : 203 : /* https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L118-L126 */ 204 : 205 0 : if( FD_UNLIKELY( current_signer_key_cnt>counter ) ) { 206 : /* Max msg_sz: 35 - 6 + 2*20 = 69 < 127 => we can use printf */ 207 0 : fd_log_collector_printf_dangerous_max_127( ctx, 208 0 : "too few signers: %lu; expected: %lu", counter, current_signer_key_cnt ); 209 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE; 210 0 : } 211 : 212 : /* Upgrade to writable handle 213 : https://github.com/anza-xyz/agave/blob/v2.1.4/programs/config/src/config_processor.rs#L125-L126 */ 214 0 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( ctx, ACC_IDX_CONFIG, &config_acc_rec ); 215 : 216 : /* Upgrade to writable handle 217 : https://github.com/solana-labs/solana/blob/v1.17.17/programs/config/src/config_processor.rs#L130-L133 */ 218 : 219 0 : if( FD_UNLIKELY( fd_borrowed_account_get_data_len( &config_acc_rec )<ctx->instr->data_sz ) ) { 220 0 : fd_log_collector_msg_literal( ctx, "instruction data too large" ); 221 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_INSTR_DATA; 222 0 : } 223 : 224 : /* https://github.com/anza-xyz/agave/blob/v2.1.14/programs/config/src/config_processor.rs#L131 */ 225 0 : uchar * data = NULL; 226 0 : ulong dlen = 0UL; 227 0 : err = fd_borrowed_account_get_data_mut( &config_acc_rec, &data, &dlen ); 228 0 : if( FD_UNLIKELY( err ) ) { 229 0 : return err; 230 0 : } 231 : 232 : /* copy_from_slice */ 233 0 : fd_memcpy( data, ctx->instr->data, ctx->instr->data_sz ); 234 : 235 : /* Implicitly dropped */ 236 : 237 0 : return FD_EXECUTOR_INSTR_SUCCESS; 238 0 : # undef ACC_IDX_CONFIG 239 : 240 0 : } 241 : 242 : int 243 0 : fd_config_program_execute( fd_exec_instr_ctx_t * ctx ) { 244 : /* Prevent execution of migrated native programs */ 245 0 : if( FD_UNLIKELY( FD_FEATURE_ACTIVE_BANK( ctx->bank, migrate_config_program_to_core_bpf ) ) ) { 246 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_PROGRAM_ID; 247 0 : } 248 : 249 : /* https://github.com/solana-labs/solana/blob/v1.17.27/programs/config/src/config_processor.rs#L14 250 : See DEFAULT_COMPUTE_UNITS */ 251 0 : FD_EXEC_CU_UPDATE( ctx, DEFAULT_COMPUTE_UNITS ); 252 : 253 0 : int ret = _process_config_instr( ctx ); 254 0 : return ret; 255 0 : }