Line data Source code
1 : /* https://docs.solana.com/developing/programming-model/transactions#anatomy-of-a-transaction */ 2 : 3 : #include "fd_txn.h" 4 : #include "fd_compact_u16.h" 5 : 6 : ulong 7 : fd_txn_parse_core( uchar const * payload, 8 : ulong payload_sz, 9 : void * out_buf, 10 : fd_txn_parse_counters_t * counters_opt, 11 : ulong * payload_sz_opt, 12 121347492 : ulong instr_max ) { 13 121347492 : ulong i = 0UL; 14 : /* This code does non-trivial parsing of untrusted user input, which 15 : is a potentially dangerous thing. The main invariants we need to 16 : ensure are 17 : A) i<=payload_sz at all times 18 : B) i< payload_sz prior to reading 19 : As long as these invariants hold, it's safe to read payload[ i ]. 20 : To ensure this, we force the following discipline for all parsing 21 : steps: 22 : Step 1. Assert there are enough bytes to read the field 23 : Step 2. Read the field 24 : Step 3. Advance i 25 : Step 4. Validate the field (if there's anything to do) 26 : This code is structured highly horizontally to make it very clear 27 : that it is correct. 28 : 29 : The first 3 steps are in three columns. The variable `i` only 30 : appears in very specific locations on the line (try searching for 31 : \<i\> in VIM to see this). 32 : 33 : The CHECK_LEFT( x ) call in the first column and the i+=x in the 34 : third column always have the same argument, which ensures invariant 35 : A holds. "Prior to reading" from invariant B corresponds to the 36 : middle column, which is the only place `i` is read. Because x is 37 : positive, the CHECK_LEFT( x ) in the first column ensures invariant 38 : B holds. 39 : 40 : Unfortunately for variable length integers, we have to combine the 41 : first two columns into a call to READ_CHECKED_COMPACT_U16 that also 42 : promises not to use any out-of-bounds data. 43 : 44 : The assignments are done in chunks in as close to the same order as 45 : possible as the variables are declared in the struct, making it 46 : very clear every variable has been initialized. */ 47 : 48 : /* A temporary for storing the return value of fd_cu16_dec_sz */ 49 121347492 : ulong bytes_consumed = 0UL; 50 : 51 : /* Increment counters and return immediately if cond is false. */ 52 15141792189 : #define CHECK( cond ) do { \ 53 15141792189 : if( FD_UNLIKELY( !(cond) ) ) { \ 54 87588 : if( FD_LIKELY( counters_opt ) ) { \ 55 87585 : counters_opt->failure_ring[ ( counters_opt->failure_cnt++ )%FD_TXN_PARSE_COUNTERS_RING_SZ ] = __LINE__; \ 56 87585 : } \ 57 87588 : return 0UL; \ 58 87588 : } \ 59 15141792189 : } while( 0 ) 60 : /* CHECK that it is safe to read at least n more bytes assuming i is 61 : the current location. n is untrusted and could trigger overflow, so 62 : don't do i+n<=payload_sz */ 63 6741288540 : #define CHECK_LEFT( n ) CHECK( (n)<=(payload_sz-i) ) 64 : /* READ_CHECKED_COMPACT_U16 safely reads a compact-u16 from the 65 : indicated location in the payload. It stores the resulting value 66 : in the ushort variable called var_name. It stores the size in 67 : out_sz. */ 68 121347492 : #define READ_CHECKED_COMPACT_U16( out_sz, var_name, where ) \ 69 4069585668 : do { \ 70 4069585668 : ulong _where = (where); \ 71 4069585668 : ulong _out_sz = fd_cu16_dec_sz( payload+_where, payload_sz-_where ); \ 72 4069585668 : CHECK( _out_sz ); \ 73 4069585668 : (var_name) = fd_cu16_dec_fixed( payload+_where, _out_sz ); \ 74 4069575267 : (out_sz) = _out_sz; \ 75 4069575267 : } while( 0 ) 76 : 77 : /* Minimal instr has 1B for program id, 1B for an acct_addr list 78 : containing no accounts, 1B for length-0 instruction data */ 79 121347492 : #define MIN_INSTR_SZ (3UL) 80 121347492 : CHECK( payload_sz<=FD_TXN_MTU ); 81 : 82 : /* The documentation sometimes calls signature_cnt a compact-u16 and 83 : sometimes a u8. Because of transaction size limits, even allowing 84 : for a 3k transaction caps the signatures at 48, so we're 85 : comfortably in the range where a compact-u16 and a u8 are 86 : represented the same way. */ 87 121347492 : CHECK_LEFT( 1UL ); uchar signature_cnt = payload[ i ]; i++; 88 : /* Must have at least one signer for the fee payer */ 89 121347486 : CHECK( (1UL<=signature_cnt) & (signature_cnt<=FD_TXN_SIG_MAX) ); 90 121346712 : CHECK_LEFT( FD_TXN_SIGNATURE_SZ*signature_cnt ); ulong signature_off = i ; i+=FD_TXN_SIGNATURE_SZ*signature_cnt; 91 : 92 : /* Not actually parsing anything, just store. */ ulong message_off = i ; 93 121345065 : CHECK_LEFT( 1UL ); uchar header_b0 = payload[ i ]; i++; 94 : 95 121345059 : uchar transaction_version; 96 121345059 : if( FD_LIKELY( (ulong)header_b0 & 0x80UL ) ) { 97 : /* This is a versioned transaction */ 98 60388527 : transaction_version = header_b0 & 0x7F; 99 60388527 : CHECK( transaction_version==FD_TXN_V0 ); /* Only recognized one so far */ 100 : 101 60387735 : CHECK_LEFT( 1UL ); CHECK( signature_cnt==payload[ i ] ); i++; 102 60956532 : } else { 103 60956532 : transaction_version = FD_TXN_VLEGACY; 104 60956532 : CHECK( signature_cnt==header_b0 ); 105 60956532 : } 106 121342695 : CHECK_LEFT( 1UL ); uchar ro_signed_cnt = payload[ i ]; i++; 107 : /* Must have at least one writable signer for the fee payer */ 108 121342689 : CHECK( ro_signed_cnt<signature_cnt ); 109 : 110 121341165 : CHECK_LEFT( 1UL ); uchar ro_unsigned_cnt= payload[ i ]; i++; 111 : 112 121341159 : ushort acct_addr_cnt = (ushort)0; 113 121341159 : READ_CHECKED_COMPACT_U16( bytes_consumed, acct_addr_cnt, i ); i+=bytes_consumed; 114 121340385 : CHECK( (signature_cnt<=acct_addr_cnt) & (acct_addr_cnt<=FD_TXN_ACCT_ADDR_MAX) ); 115 121340370 : CHECK( (ulong)signature_cnt+(ulong)ro_unsigned_cnt<=(ulong)acct_addr_cnt ); 116 : 117 121338873 : CHECK_LEFT( FD_TXN_ACCT_ADDR_SZ*acct_addr_cnt ); ulong acct_addr_off = i ; i+=FD_TXN_ACCT_ADDR_SZ*acct_addr_cnt; 118 121335453 : CHECK_LEFT( FD_TXN_BLOCKHASH_SZ ); ulong recent_blockhash_off = i ; i+=FD_TXN_BLOCKHASH_SZ; 119 : 120 121335255 : ushort instr_cnt = (ushort)0; 121 121335255 : READ_CHECKED_COMPACT_U16( bytes_consumed, instr_cnt, i ); i+=bytes_consumed; 122 : 123 : /* FIXME: compile-time max after static_instruction_limit. */ 124 121335219 : CHECK( (ulong)instr_cnt<=instr_max ); 125 : 126 121334064 : CHECK_LEFT( MIN_INSTR_SZ*instr_cnt ); 127 : /* If it has >0 instructions, it must have at least one other account 128 : address (the program id) that can't be the fee payer */ 129 121333956 : CHECK( (ulong)acct_addr_cnt>(!!instr_cnt) ); 130 : 131 121333953 : fd_txn_t * parsed = (fd_txn_t *)out_buf; 132 : 133 121333953 : if( parsed ) { 134 121333944 : parsed->transaction_version = transaction_version; 135 121333944 : parsed->signature_cnt = signature_cnt; 136 121333944 : parsed->signature_off = (ushort)signature_off; 137 121333944 : parsed->message_off = (ushort)message_off; 138 121333944 : parsed->readonly_signed_cnt = ro_signed_cnt; 139 121333944 : parsed->readonly_unsigned_cnt = ro_unsigned_cnt; 140 121333944 : parsed->acct_addr_cnt = acct_addr_cnt; 141 121333944 : parsed->acct_addr_off = (ushort)acct_addr_off; 142 121333944 : parsed->recent_blockhash_off = (ushort)recent_blockhash_off; 143 : /* Need to assign addr_table_lookup_cnt, 144 : addr_table_adtl_writable_cnt, addr_table_adtl_cnt, 145 : _padding_reserved_1 later */ 146 121333944 : parsed->instr_cnt = instr_cnt; 147 121333944 : } 148 : 149 121333953 : uchar max_acct = 0UL; 150 1508455647 : for( ulong j=0UL; j<instr_cnt; j++ ) { 151 : 152 : /* Parsing instruction */ 153 1387142232 : ushort acct_cnt = (ushort)0; 154 1387142232 : ushort data_sz = (ushort)0; 155 1387142232 : CHECK_LEFT( MIN_INSTR_SZ ); uchar program_id = payload[ i ]; i++; 156 1387141908 : READ_CHECKED_COMPACT_U16( bytes_consumed, acct_cnt, i ); i+=bytes_consumed; 157 1387139805 : CHECK_LEFT( acct_cnt ); ulong acct_off = i ; 158 4548388815 : for( ulong k=0; k<acct_cnt; k++ ) { max_acct=fd_uchar_max( max_acct, payload[ k+i ] ); } i+=acct_cnt; 159 1387135272 : READ_CHECKED_COMPACT_U16( bytes_consumed, data_sz, i ); i+=bytes_consumed; 160 1387129494 : CHECK_LEFT( data_sz ); ulong data_off = i ; i+=data_sz; 161 : 162 : /* Account 0 is the fee payer and the program can't be the fee 163 : payer. The fee payer account must be owned by the system 164 : program, but the program must be an executable account and the 165 : system program is not permitted to own any executable account. 166 : As of https://github.com/solana-labs/solana/issues/25034, the 167 : program ID can't come from a table. */ 168 1387128255 : CHECK( (0UL < (ulong)program_id) & ((ulong)program_id < (ulong)acct_addr_cnt) ); 169 : 170 1387121694 : if( parsed ){ 171 1387121685 : parsed->instr[ j ].program_id = program_id; 172 1387121685 : parsed->instr[ j ]._padding_reserved_1 = (uchar)0; 173 1387121685 : parsed->instr[ j ].acct_cnt = acct_cnt; 174 1387121685 : parsed->instr[ j ].data_sz = data_sz; 175 : /* By our invariant, i<size when it was copied into acct_off and 176 : data_off, and size<=USHORT_MAX from above, so this cast is safe */ 177 1387121685 : parsed->instr[ j ].acct_off = (ushort)acct_off; 178 1387121685 : parsed->instr[ j ].data_off = (ushort)data_off; 179 1387121685 : } 180 1387121694 : } 181 121313415 : #undef MIN_INSTR_SIZE 182 : 183 121313415 : ushort addr_table_cnt = 0; 184 121313415 : ulong addr_table_adtl_writable_cnt = 0; 185 121313415 : ulong addr_table_adtl_cnt = 0; 186 : 187 : /* parsed->instr_cnt set above, so calling get_address_tables is safe */ 188 121313415 : fd_txn_acct_addr_lut_t * address_tables = (parsed == NULL) ? NULL : fd_txn_get_address_tables( parsed ); 189 121313415 : if( FD_LIKELY( transaction_version==FD_TXN_V0 ) ) { 190 60378999 : #define MIN_ADDR_LUT_SIZE (34UL) 191 60378999 : READ_CHECKED_COMPACT_U16( bytes_consumed, addr_table_cnt, i ); i+=bytes_consumed; 192 60378717 : CHECK( addr_table_cnt <= FD_TXN_ADDR_TABLE_LOOKUP_MAX ); 193 60378255 : CHECK_LEFT( MIN_ADDR_LUT_SIZE*addr_table_cnt ); 194 : 195 556500540 : for( ulong j=0; j<addr_table_cnt; j++ ) { 196 496127853 : CHECK_LEFT( FD_TXN_ACCT_ADDR_SZ ); ulong addr_off = i ; i+=FD_TXN_ACCT_ADDR_SZ; 197 : 198 496127580 : ushort writable_cnt = 0; 199 496127580 : ushort readonly_cnt = 0; 200 496127580 : READ_CHECKED_COMPACT_U16( bytes_consumed, writable_cnt, i ); i+=bytes_consumed; 201 496126725 : CHECK_LEFT( writable_cnt ); ulong writable_off = i ; i+=writable_cnt; 202 496125495 : READ_CHECKED_COMPACT_U16( bytes_consumed, readonly_cnt, i ); i+=bytes_consumed; 203 496124922 : CHECK_LEFT( readonly_cnt ); ulong readonly_off = i ; i+=readonly_cnt; 204 : 205 496123194 : CHECK( writable_cnt<=FD_TXN_ACCT_ADDR_MAX-acct_addr_cnt ); /* implies <256 ... */ 206 496123194 : CHECK( readonly_cnt<=FD_TXN_ACCT_ADDR_MAX-acct_addr_cnt ); 207 496123194 : CHECK( (ushort)1 <=writable_cnt+readonly_cnt ); /* ... so the sum can't overflow */ 208 496123194 : if( address_tables ) { 209 496123194 : address_tables[ j ].addr_off = (ushort)addr_off; 210 496123194 : address_tables[ j ].writable_cnt = (uchar )writable_cnt; 211 496123194 : address_tables[ j ].readonly_cnt = (uchar )readonly_cnt; 212 496123194 : address_tables[ j ].writable_off = (ushort)writable_off; 213 496123194 : address_tables[ j ].readonly_off = (ushort)readonly_off; 214 496123194 : } 215 : 216 496123194 : addr_table_adtl_writable_cnt += (ulong)writable_cnt; 217 496123194 : addr_table_adtl_cnt += (ulong)writable_cnt + (ulong)readonly_cnt; 218 496123194 : } 219 60377346 : } 220 121307103 : #undef MIN_ADDR_LUT_SIZE 221 : /* Check for leftover bytes if out_sz_opt not specified. */ 222 121307103 : CHECK( (payload_sz_opt!=NULL) | (i==payload_sz) ); 223 : 224 121306968 : CHECK( acct_addr_cnt+addr_table_adtl_cnt<=FD_TXN_ACCT_ADDR_MAX ); /* implies addr_table_adtl_cnt<256 */ 225 : 226 : /* Final validation that all the account address indices are in range */ 227 121306968 : CHECK( max_acct < acct_addr_cnt + addr_table_adtl_cnt ); 228 : 229 121259904 : if( parsed ) { 230 : /* Assign final variables */ 231 121259895 : parsed->addr_table_lookup_cnt = (uchar)addr_table_cnt; 232 121259895 : parsed->addr_table_adtl_writable_cnt = (uchar)addr_table_adtl_writable_cnt; 233 121259895 : parsed->addr_table_adtl_cnt = (uchar)addr_table_adtl_cnt; 234 121259895 : parsed->_padding_reserved_1 = (uchar)0; 235 121259895 : } 236 : 237 121259904 : if( FD_LIKELY( counters_opt ) ) counters_opt->success_cnt++; 238 121259904 : if( FD_LIKELY( payload_sz_opt ) ) *payload_sz_opt = i; 239 : 240 121259904 : ulong footprint = fd_txn_footprint( instr_cnt, addr_table_cnt ); 241 : /* FIXME remove this check when static_instruction_limit is activated on all networks. */ 242 121259904 : if( FD_UNLIKELY( instr_cnt>FD_TXN_INSTR_MAX && footprint>FD_TXN_MAX_SZ ) ) { 243 0 : uchar const * sig = payload+parsed->signature_off; 244 0 : FD_LOG_HEXDUMP_WARNING(( "txnsig", sig, FD_TXN_SIGNATURE_SZ )); 245 0 : FD_LOG_CRIT(( "instr_cnt %u footprint %lu", instr_cnt, footprint )); 246 0 : } 247 121259904 : return footprint; 248 : 249 121259904 : #undef CHECK 250 121259904 : #undef CHECK_LEFT 251 121259904 : #undef READ_CHECKED_COMPACT_U16 252 121259904 : }