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 61357791 : ulong * payload_sz_opt ) { 12 61357791 : ulong i = 0UL; 13 : /* This code does non-trivial parsing of untrusted user input, which 14 : is a potentially dangerous thing. The main invariants we need to 15 : ensure are 16 : A) i<=payload_sz at all times 17 : B) i< payload_sz prior to reading 18 : As long as these invariants hold, it's safe to read payload[ i ]. 19 : To ensure this, we force the following discipline for all parsing 20 : steps: 21 : Step 1. Assert there are enough bytes to read the field 22 : Step 2. Read the field 23 : Step 3. Advance i 24 : Step 4. Validate the field (if there's anything to do) 25 : This code is structured highly horizontally to make it very clear 26 : that it is correct. 27 : 28 : The first 3 steps are in three columns. The variable `i` only 29 : appears in very specific locations on the line (try searching for 30 : \<i\> in VIM to see this). 31 : 32 : The CHECK_LEFT( x ) call in the first column and the i+=x in the 33 : third column always have the same argument, which ensures invariant 34 : A holds. "Prior to reading" from invariant B corresponds to the 35 : middle column, which is the only place `i` is read. Because x is 36 : positive, the CHECK_LEFT( x ) in the first column ensures invariant 37 : B holds. 38 : 39 : Unfortunately for variable length integers, we have to combine the 40 : first two columns into a call to READ_CHECKED_COMPACT_U16 that also 41 : promises not to use any out-of-bounds data. 42 : 43 : The assignments are done in chunks in as close to the same order as 44 : possible as the variables are declared in the struct, making it 45 : very clear every variable has been initialized. */ 46 : 47 : /* A temporary for storing the return value of fd_cu16_dec_sz */ 48 61357791 : ulong bytes_consumed = 0UL; 49 : 50 : /* Increment counters and return immediately if cond is false. */ 51 3832218303 : #define CHECK( cond ) do { \ 52 3832218303 : if( FD_UNLIKELY( !(cond) ) ) { \ 53 87588 : if( FD_LIKELY( counters_opt ) ) { \ 54 87585 : counters_opt->failure_ring[ ( counters_opt->failure_cnt++ )%FD_TXN_PARSE_COUNTERS_RING_SZ ] = __LINE__; \ 55 87585 : } \ 56 87588 : return 0UL; \ 57 87588 : } \ 58 3832218303 : } while( 0 ) 59 : /* CHECK that it is safe to read at least n more bytes assuming i is 60 : the current location. n is untrusted and could trigger overflow, so 61 : don't do i+n<=payload_sz */ 62 1656462636 : #define CHECK_LEFT( n ) CHECK( (n)<=(payload_sz-i) ) 63 : /* READ_CHECKED_COMPACT_U16 safely reads a compact-u16 from the 64 : indicated location in the payload. It stores the resulting value 65 : in the ushort variable called var_name. It stores the size in 66 : out_sz. */ 67 61357791 : #define READ_CHECKED_COMPACT_U16( out_sz, var_name, where ) \ 68 889663791 : do { \ 69 889663791 : ulong _where = (where); \ 70 889663791 : ulong _out_sz = fd_cu16_dec_sz( payload+_where, payload_sz-_where ); \ 71 889663791 : CHECK( _out_sz ); \ 72 889663791 : (var_name) = fd_cu16_dec_fixed( payload+_where, _out_sz ); \ 73 889653390 : (out_sz) = _out_sz; \ 74 889653390 : } while( 0 ) 75 : 76 : /* Minimal instr has 1B for program id, 1B for an acct_addr list 77 : containing no accounts, 1B for length-0 instruction data */ 78 61357791 : #define MIN_INSTR_SZ (3UL) 79 61357791 : CHECK( payload_sz<=FD_TXN_MTU ); 80 : 81 : /* The documentation sometimes calls signature_cnt a compact-u16 and 82 : sometimes a u8. Because of transaction size limits, even allowing 83 : for a 3k transaction caps the signatures at 48, so we're 84 : comfortably in the range where a compact-u16 and a u8 are 85 : represented the same way. */ 86 61357791 : CHECK_LEFT( 1UL ); uchar signature_cnt = payload[ i ]; i++; 87 : /* Must have at least one signer for the fee payer */ 88 61357785 : CHECK( (1UL<=signature_cnt) & (signature_cnt<=FD_TXN_SIG_MAX) ); 89 61357011 : CHECK_LEFT( FD_TXN_SIGNATURE_SZ*signature_cnt ); ulong signature_off = i ; i+=FD_TXN_SIGNATURE_SZ*signature_cnt; 90 : 91 : /* Not actually parsing anything, just store. */ ulong message_off = i ; 92 61355364 : CHECK_LEFT( 1UL ); uchar header_b0 = payload[ i ]; i++; 93 : 94 61355358 : uchar transaction_version; 95 61355358 : if( FD_LIKELY( (ulong)header_b0 & 0x80UL ) ) { 96 : /* This is a versioned transaction */ 97 30399360 : transaction_version = header_b0 & 0x7F; 98 30399360 : CHECK( transaction_version==FD_TXN_V0 ); /* Only recognized one so far */ 99 : 100 30398568 : CHECK_LEFT( 1UL ); CHECK( signature_cnt==payload[ i ] ); i++; 101 30955998 : } else { 102 30955998 : transaction_version = FD_TXN_VLEGACY; 103 30955998 : CHECK( signature_cnt==header_b0 ); 104 30955998 : } 105 61352994 : CHECK_LEFT( 1UL ); uchar ro_signed_cnt = payload[ i ]; i++; 106 : /* Must have at least one writable signer for the fee payer */ 107 61352988 : CHECK( ro_signed_cnt<signature_cnt ); 108 : 109 61351464 : CHECK_LEFT( 1UL ); uchar ro_unsigned_cnt= payload[ i ]; i++; 110 : 111 61351458 : ushort acct_addr_cnt = (ushort)0; 112 61351458 : READ_CHECKED_COMPACT_U16( bytes_consumed, acct_addr_cnt, i ); i+=bytes_consumed; 113 61350684 : CHECK( (signature_cnt<=acct_addr_cnt) & (acct_addr_cnt<=FD_TXN_ACCT_ADDR_MAX) ); 114 61350669 : CHECK( (ulong)signature_cnt+(ulong)ro_unsigned_cnt<=(ulong)acct_addr_cnt ); 115 : 116 : 117 : 118 61349172 : CHECK_LEFT( FD_TXN_ACCT_ADDR_SZ*acct_addr_cnt ); ulong acct_addr_off = i ; i+=FD_TXN_ACCT_ADDR_SZ*acct_addr_cnt; 119 61345752 : CHECK_LEFT( FD_TXN_BLOCKHASH_SZ ); ulong recent_blockhash_off = i ; i+=FD_TXN_BLOCKHASH_SZ; 120 : 121 61345554 : ushort instr_cnt = (ushort)0; 122 61345554 : READ_CHECKED_COMPACT_U16( bytes_consumed, instr_cnt, i ); i+=bytes_consumed; 123 : 124 : #ifdef FD_OFFLINE_REPLAY 125 : /* For offline replay, we allow up to 128 instructions per 126 : transaction. Note that this is simply a bump in the limit that is 127 : completely local to this check. We are not concomitantly bumping 128 : the size of fd_txn_t. So we risk potential buffer overflow in 129 : fd_txn_t, but again only in offline replay. */ 130 : CHECK( (ulong)instr_cnt<=128UL ); 131 : #else 132 61345518 : CHECK( (ulong)instr_cnt<=FD_TXN_INSTR_MAX ); 133 61344363 : #endif 134 61344363 : CHECK_LEFT( MIN_INSTR_SZ*instr_cnt ); 135 : /* If it has >0 instructions, it must have at least one other account 136 : address (the program id) that can't be the fee payer */ 137 61344255 : CHECK( (ulong)acct_addr_cnt>(!!instr_cnt) ); 138 : 139 61344252 : fd_txn_t * parsed = (fd_txn_t *)out_buf; 140 : 141 61344252 : if( parsed ) { 142 61344252 : parsed->transaction_version = transaction_version; 143 61344252 : parsed->signature_cnt = signature_cnt; 144 61344252 : parsed->signature_off = (ushort)signature_off; 145 61344252 : parsed->message_off = (ushort)message_off; 146 61344252 : parsed->readonly_signed_cnt = ro_signed_cnt; 147 61344252 : parsed->readonly_unsigned_cnt = ro_unsigned_cnt; 148 61344252 : parsed->acct_addr_cnt = acct_addr_cnt; 149 61344252 : parsed->acct_addr_off = (ushort)acct_addr_off; 150 61344252 : parsed->recent_blockhash_off = (ushort)recent_blockhash_off; 151 : /* Need to assign addr_table_lookup_cnt, 152 : addr_table_adtl_writable_cnt, addr_table_adtl_cnt, 153 : _padding_reserved_1 later */ 154 61344252 : parsed->instr_cnt = instr_cnt; 155 61344252 : } 156 : 157 61344252 : uchar max_acct = 0UL; 158 338481495 : for( ulong j=0UL; j<instr_cnt; j++ ) { 159 : 160 : /* Parsing instruction */ 161 277157781 : ushort acct_cnt = (ushort)0; 162 277157781 : ushort data_sz = (ushort)0; 163 277157781 : CHECK_LEFT( MIN_INSTR_SZ ); uchar program_id = payload[ i ]; i++; 164 277157457 : READ_CHECKED_COMPACT_U16( bytes_consumed, acct_cnt, i ); i+=bytes_consumed; 165 277155354 : CHECK_LEFT( acct_cnt ); ulong acct_off = i ; 166 2358445821 : for( ulong k=0; k<acct_cnt; k++ ) { max_acct=fd_uchar_max( max_acct, payload[ k+i ] ); } i+=acct_cnt; 167 277150821 : READ_CHECKED_COMPACT_U16( bytes_consumed, data_sz, i ); i+=bytes_consumed; 168 277145043 : CHECK_LEFT( data_sz ); ulong data_off = i ; i+=data_sz; 169 : 170 : /* Account 0 is the fee payer and the program can't be the fee 171 : payer. The fee payer account must be owned by the system 172 : program, but the program must be an executable account and the 173 : system program is not permitted to own any executable account. 174 : As of https://github.com/solana-labs/solana/issues/25034, the 175 : program ID can't come from a table. */ 176 277143804 : CHECK( (0UL < (ulong)program_id) & ((ulong)program_id < (ulong)acct_addr_cnt) ); 177 : 178 277137243 : if( parsed ){ 179 277137243 : parsed->instr[ j ].program_id = program_id; 180 277137243 : parsed->instr[ j ]._padding_reserved_1 = (uchar)0; 181 277137243 : parsed->instr[ j ].acct_cnt = acct_cnt; 182 277137243 : parsed->instr[ j ].data_sz = data_sz; 183 : /* By our invariant, i<size when it was copied into acct_off and 184 : data_off, and size<=USHORT_MAX from above, so this cast is safe */ 185 277137243 : parsed->instr[ j ].acct_off = (ushort)acct_off; 186 277137243 : parsed->instr[ j ].data_off = (ushort)data_off; 187 277137243 : } 188 277137243 : } 189 61323714 : #undef MIN_INSTR_SIZE 190 : 191 61323714 : ushort addr_table_cnt = 0; 192 61323714 : ulong addr_table_adtl_writable_cnt = 0; 193 61323714 : ulong addr_table_adtl_cnt = 0; 194 : 195 : /* parsed->instr_cnt set above, so calling get_address_tables is safe */ 196 61323714 : fd_txn_acct_addr_lut_t * address_tables = (parsed == NULL) ? NULL : fd_txn_get_address_tables( parsed ); 197 61323714 : if( FD_LIKELY( transaction_version==FD_TXN_V0 ) ) { 198 30389832 : #define MIN_ADDR_LUT_SIZE (34UL) 199 30389832 : READ_CHECKED_COMPACT_U16( bytes_consumed, addr_table_cnt, i ); i+=bytes_consumed; 200 30389550 : CHECK( addr_table_cnt <= FD_TXN_ADDR_TABLE_LOOKUP_MAX ); 201 30389088 : CHECK_LEFT( MIN_ADDR_LUT_SIZE*addr_table_cnt ); 202 : 203 121519170 : for( ulong j=0; j<addr_table_cnt; j++ ) { 204 91135650 : CHECK_LEFT( FD_TXN_ACCT_ADDR_SZ ); ulong addr_off = i ; i+=FD_TXN_ACCT_ADDR_SZ; 205 : 206 91135377 : ushort writable_cnt = 0; 207 91135377 : ushort readonly_cnt = 0; 208 91135377 : READ_CHECKED_COMPACT_U16( bytes_consumed, writable_cnt, i ); i+=bytes_consumed; 209 91134522 : CHECK_LEFT( writable_cnt ); ulong writable_off = i ; i+=writable_cnt; 210 91133292 : READ_CHECKED_COMPACT_U16( bytes_consumed, readonly_cnt, i ); i+=bytes_consumed; 211 91132719 : CHECK_LEFT( readonly_cnt ); ulong readonly_off = i ; i+=readonly_cnt; 212 : 213 91130991 : CHECK( writable_cnt<=FD_TXN_ACCT_ADDR_MAX-acct_addr_cnt ); /* implies <256 ... */ 214 91130991 : CHECK( readonly_cnt<=FD_TXN_ACCT_ADDR_MAX-acct_addr_cnt ); 215 91130991 : CHECK( (ushort)1 <=writable_cnt+readonly_cnt ); /* ... so the sum can't overflow */ 216 91130991 : if( address_tables ) { 217 91130991 : address_tables[ j ].addr_off = (ushort)addr_off; 218 91130991 : address_tables[ j ].writable_cnt = (uchar )writable_cnt; 219 91130991 : address_tables[ j ].readonly_cnt = (uchar )readonly_cnt; 220 91130991 : address_tables[ j ].writable_off = (ushort)writable_off; 221 91130991 : address_tables[ j ].readonly_off = (ushort)readonly_off; 222 91130991 : } 223 : 224 91130991 : addr_table_adtl_writable_cnt += (ulong)writable_cnt; 225 91130991 : addr_table_adtl_cnt += (ulong)writable_cnt + (ulong)readonly_cnt; 226 91130991 : } 227 30388179 : } 228 61317402 : #undef MIN_ADDR_LUT_SIZE 229 : /* Check for leftover bytes if out_sz_opt not specified. */ 230 61317402 : CHECK( (payload_sz_opt!=NULL) | (i==payload_sz) ); 231 : 232 61317267 : CHECK( acct_addr_cnt+addr_table_adtl_cnt<=FD_TXN_ACCT_ADDR_MAX ); /* implies addr_table_adtl_cnt<256 */ 233 : 234 : /* Final validation that all the account address indices are in range */ 235 61317267 : CHECK( max_acct < acct_addr_cnt + addr_table_adtl_cnt ); 236 : 237 61270203 : if( parsed ) { 238 : /* Assign final variables */ 239 61270203 : parsed->addr_table_lookup_cnt = (uchar)addr_table_cnt; 240 61270203 : parsed->addr_table_adtl_writable_cnt = (uchar)addr_table_adtl_writable_cnt; 241 61270203 : parsed->addr_table_adtl_cnt = (uchar)addr_table_adtl_cnt; 242 61270203 : parsed->_padding_reserved_1 = (uchar)0; 243 61270203 : } 244 : 245 61270203 : if( FD_LIKELY( counters_opt ) ) counters_opt->success_cnt++; 246 61270203 : if( FD_LIKELY( payload_sz_opt ) ) *payload_sz_opt = i; 247 61270203 : return fd_txn_footprint( instr_cnt, addr_table_cnt ); 248 : 249 61317267 : #undef CHECK 250 61317267 : #undef CHECK_LEFT 251 61317267 : #undef READ_CHECKED_COMPACT_U16 252 61317267 : }