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 61347423 : ulong instr_max ) { 13 61347423 : 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 61347423 : ulong bytes_consumed = 0UL; 50 : 51 : /* Increment counters and return immediately if cond is false. */ 52 3831790119 : #define CHECK( cond ) do { \ 53 3831790119 : 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 3831790119 : } 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 1656287700 : #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 61347423 : #define READ_CHECKED_COMPACT_U16( out_sz, var_name, where ) \ 69 889585353 : do { \ 70 889585353 : ulong _where = (where); \ 71 889585353 : ulong _out_sz = fd_cu16_dec_sz( payload+_where, payload_sz-_where ); \ 72 889585353 : CHECK( _out_sz ); \ 73 889585353 : (var_name) = fd_cu16_dec_fixed( payload+_where, _out_sz ); \ 74 889574952 : (out_sz) = _out_sz; \ 75 889574952 : } 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 61347423 : #define MIN_INSTR_SZ (3UL) 80 61347423 : 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 61347423 : CHECK_LEFT( 1UL ); uchar signature_cnt = payload[ i ]; i++; 88 : /* Must have at least one signer for the fee payer */ 89 61347417 : CHECK( (1UL<=signature_cnt) & (signature_cnt<=FD_TXN_SIG_MAX) ); 90 61346643 : 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 61344996 : CHECK_LEFT( 1UL ); uchar header_b0 = payload[ i ]; i++; 94 : 95 61344990 : uchar transaction_version; 96 61344990 : if( FD_LIKELY( (ulong)header_b0 & 0x80UL ) ) { 97 : /* This is a versioned transaction */ 98 30388482 : transaction_version = header_b0 & 0x7F; 99 30388482 : CHECK( transaction_version==FD_TXN_V0 ); /* Only recognized one so far */ 100 : 101 30387690 : CHECK_LEFT( 1UL ); CHECK( signature_cnt==payload[ i ] ); i++; 102 30956508 : } else { 103 30956508 : transaction_version = FD_TXN_VLEGACY; 104 30956508 : CHECK( signature_cnt==header_b0 ); 105 30956508 : } 106 61342626 : CHECK_LEFT( 1UL ); uchar ro_signed_cnt = payload[ i ]; i++; 107 : /* Must have at least one writable signer for the fee payer */ 108 61342620 : CHECK( ro_signed_cnt<signature_cnt ); 109 : 110 61341096 : CHECK_LEFT( 1UL ); uchar ro_unsigned_cnt= payload[ i ]; i++; 111 : 112 61341090 : ushort acct_addr_cnt = (ushort)0; 113 61341090 : READ_CHECKED_COMPACT_U16( bytes_consumed, acct_addr_cnt, i ); i+=bytes_consumed; 114 61340316 : CHECK( (signature_cnt<=acct_addr_cnt) & (acct_addr_cnt<=FD_TXN_ACCT_ADDR_MAX) ); 115 61340301 : CHECK( (ulong)signature_cnt+(ulong)ro_unsigned_cnt<=(ulong)acct_addr_cnt ); 116 : 117 : 118 : 119 61338804 : CHECK_LEFT( FD_TXN_ACCT_ADDR_SZ*acct_addr_cnt ); ulong acct_addr_off = i ; i+=FD_TXN_ACCT_ADDR_SZ*acct_addr_cnt; 120 61335384 : CHECK_LEFT( FD_TXN_BLOCKHASH_SZ ); ulong recent_blockhash_off = i ; i+=FD_TXN_BLOCKHASH_SZ; 121 : 122 61335186 : ushort instr_cnt = (ushort)0; 123 61335186 : READ_CHECKED_COMPACT_U16( bytes_consumed, instr_cnt, i ); i+=bytes_consumed; 124 : 125 : /* FIXME: compile-time max after static_instruction_limit. */ 126 61335150 : CHECK( (ulong)instr_cnt<=instr_max ); 127 : 128 61333995 : CHECK_LEFT( MIN_INSTR_SZ*instr_cnt ); 129 : /* If it has >0 instructions, it must have at least one other account 130 : address (the program id) that can't be the fee payer */ 131 61333887 : CHECK( (ulong)acct_addr_cnt>(!!instr_cnt) ); 132 : 133 61333884 : fd_txn_t * parsed = (fd_txn_t *)out_buf; 134 : 135 61333884 : if( parsed ) { 136 61333881 : parsed->transaction_version = transaction_version; 137 61333881 : parsed->signature_cnt = signature_cnt; 138 61333881 : parsed->signature_off = (ushort)signature_off; 139 61333881 : parsed->message_off = (ushort)message_off; 140 61333881 : parsed->readonly_signed_cnt = ro_signed_cnt; 141 61333881 : parsed->readonly_unsigned_cnt = ro_unsigned_cnt; 142 61333881 : parsed->acct_addr_cnt = acct_addr_cnt; 143 61333881 : parsed->acct_addr_off = (ushort)acct_addr_off; 144 61333881 : parsed->recent_blockhash_off = (ushort)recent_blockhash_off; 145 : /* Need to assign addr_table_lookup_cnt, 146 : addr_table_adtl_writable_cnt, addr_table_adtl_cnt, 147 : _padding_reserved_1 later */ 148 61333881 : parsed->instr_cnt = instr_cnt; 149 61333881 : } 150 : 151 61333884 : uchar max_acct = 0UL; 152 338455512 : for( ulong j=0UL; j<instr_cnt; j++ ) { 153 : 154 : /* Parsing instruction */ 155 277142166 : ushort acct_cnt = (ushort)0; 156 277142166 : ushort data_sz = (ushort)0; 157 277142166 : CHECK_LEFT( MIN_INSTR_SZ ); uchar program_id = payload[ i ]; i++; 158 277141842 : READ_CHECKED_COMPACT_U16( bytes_consumed, acct_cnt, i ); i+=bytes_consumed; 159 277139739 : CHECK_LEFT( acct_cnt ); ulong acct_off = i ; 160 2358388626 : for( ulong k=0; k<acct_cnt; k++ ) { max_acct=fd_uchar_max( max_acct, payload[ k+i ] ); } i+=acct_cnt; 161 277135206 : READ_CHECKED_COMPACT_U16( bytes_consumed, data_sz, i ); i+=bytes_consumed; 162 277129428 : CHECK_LEFT( data_sz ); ulong data_off = i ; i+=data_sz; 163 : 164 : /* Account 0 is the fee payer and the program can't be the fee 165 : payer. The fee payer account must be owned by the system 166 : program, but the program must be an executable account and the 167 : system program is not permitted to own any executable account. 168 : As of https://github.com/solana-labs/solana/issues/25034, the 169 : program ID can't come from a table. */ 170 277128189 : CHECK( (0UL < (ulong)program_id) & ((ulong)program_id < (ulong)acct_addr_cnt) ); 171 : 172 277121628 : if( parsed ){ 173 277121625 : parsed->instr[ j ].program_id = program_id; 174 277121625 : parsed->instr[ j ]._padding_reserved_1 = (uchar)0; 175 277121625 : parsed->instr[ j ].acct_cnt = acct_cnt; 176 277121625 : parsed->instr[ j ].data_sz = data_sz; 177 : /* By our invariant, i<size when it was copied into acct_off and 178 : data_off, and size<=USHORT_MAX from above, so this cast is safe */ 179 277121625 : parsed->instr[ j ].acct_off = (ushort)acct_off; 180 277121625 : parsed->instr[ j ].data_off = (ushort)data_off; 181 277121625 : } 182 277121628 : } 183 61313346 : #undef MIN_INSTR_SIZE 184 : 185 61313346 : ushort addr_table_cnt = 0; 186 61313346 : ulong addr_table_adtl_writable_cnt = 0; 187 61313346 : ulong addr_table_adtl_cnt = 0; 188 : 189 : /* parsed->instr_cnt set above, so calling get_address_tables is safe */ 190 61313346 : fd_txn_acct_addr_lut_t * address_tables = (parsed == NULL) ? NULL : fd_txn_get_address_tables( parsed ); 191 61313346 : if( FD_LIKELY( transaction_version==FD_TXN_V0 ) ) { 192 30378954 : #define MIN_ADDR_LUT_SIZE (34UL) 193 30378954 : READ_CHECKED_COMPACT_U16( bytes_consumed, addr_table_cnt, i ); i+=bytes_consumed; 194 30378672 : CHECK( addr_table_cnt <= FD_TXN_ADDR_TABLE_LOOKUP_MAX ); 195 30378210 : CHECK_LEFT( MIN_ADDR_LUT_SIZE*addr_table_cnt ); 196 : 197 121500495 : for( ulong j=0; j<addr_table_cnt; j++ ) { 198 91127853 : CHECK_LEFT( FD_TXN_ACCT_ADDR_SZ ); ulong addr_off = i ; i+=FD_TXN_ACCT_ADDR_SZ; 199 : 200 91127580 : ushort writable_cnt = 0; 201 91127580 : ushort readonly_cnt = 0; 202 91127580 : READ_CHECKED_COMPACT_U16( bytes_consumed, writable_cnt, i ); i+=bytes_consumed; 203 91126725 : CHECK_LEFT( writable_cnt ); ulong writable_off = i ; i+=writable_cnt; 204 91125495 : READ_CHECKED_COMPACT_U16( bytes_consumed, readonly_cnt, i ); i+=bytes_consumed; 205 91124922 : CHECK_LEFT( readonly_cnt ); ulong readonly_off = i ; i+=readonly_cnt; 206 : 207 91123194 : CHECK( writable_cnt<=FD_TXN_ACCT_ADDR_MAX-acct_addr_cnt ); /* implies <256 ... */ 208 91123194 : CHECK( readonly_cnt<=FD_TXN_ACCT_ADDR_MAX-acct_addr_cnt ); 209 91123194 : CHECK( (ushort)1 <=writable_cnt+readonly_cnt ); /* ... so the sum can't overflow */ 210 91123194 : if( address_tables ) { 211 91123194 : address_tables[ j ].addr_off = (ushort)addr_off; 212 91123194 : address_tables[ j ].writable_cnt = (uchar )writable_cnt; 213 91123194 : address_tables[ j ].readonly_cnt = (uchar )readonly_cnt; 214 91123194 : address_tables[ j ].writable_off = (ushort)writable_off; 215 91123194 : address_tables[ j ].readonly_off = (ushort)readonly_off; 216 91123194 : } 217 : 218 91123194 : addr_table_adtl_writable_cnt += (ulong)writable_cnt; 219 91123194 : addr_table_adtl_cnt += (ulong)writable_cnt + (ulong)readonly_cnt; 220 91123194 : } 221 30377301 : } 222 61307034 : #undef MIN_ADDR_LUT_SIZE 223 : /* Check for leftover bytes if out_sz_opt not specified. */ 224 61307034 : CHECK( (payload_sz_opt!=NULL) | (i==payload_sz) ); 225 : 226 61306899 : CHECK( acct_addr_cnt+addr_table_adtl_cnt<=FD_TXN_ACCT_ADDR_MAX ); /* implies addr_table_adtl_cnt<256 */ 227 : 228 : /* Final validation that all the account address indices are in range */ 229 61306899 : CHECK( max_acct < acct_addr_cnt + addr_table_adtl_cnt ); 230 : 231 61259835 : if( parsed ) { 232 : /* Assign final variables */ 233 61259832 : parsed->addr_table_lookup_cnt = (uchar)addr_table_cnt; 234 61259832 : parsed->addr_table_adtl_writable_cnt = (uchar)addr_table_adtl_writable_cnt; 235 61259832 : parsed->addr_table_adtl_cnt = (uchar)addr_table_adtl_cnt; 236 61259832 : parsed->_padding_reserved_1 = (uchar)0; 237 61259832 : } 238 : 239 61259835 : if( FD_LIKELY( counters_opt ) ) counters_opt->success_cnt++; 240 61259835 : if( FD_LIKELY( payload_sz_opt ) ) *payload_sz_opt = i; 241 : 242 61259835 : ulong footprint = fd_txn_footprint( instr_cnt, addr_table_cnt ); 243 : /* FIXME remove this check when static_instruction_limit is activated on all networks. */ 244 61259835 : if( FD_UNLIKELY( instr_cnt>FD_TXN_INSTR_MAX && footprint>FD_TXN_MAX_SZ ) ) { 245 0 : uchar const * sig = payload+parsed->signature_off; 246 0 : FD_LOG_HEXDUMP_WARNING(( "txnsig", sig, FD_TXN_SIGNATURE_SZ )); 247 0 : FD_LOG_CRIT(( "instr_cnt %u footprint %lu", instr_cnt, footprint )); 248 0 : } 249 61259835 : return footprint; 250 : 251 61259835 : #undef CHECK 252 61259835 : #undef CHECK_LEFT 253 61259835 : #undef READ_CHECKED_COMPACT_U16 254 61259835 : }