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 5543 : ulong * payload_sz_opt ) { 12 5543 : 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 5543 : ulong bytes_consumed = 0UL; 49 : 50 : /* Increment counters and return immediately if cond is false. */ 51 215092 : #define CHECK( cond ) do { \ 52 215092 : if( FD_UNLIKELY( !(cond) ) ) { \ 53 0 : if( FD_LIKELY( counters_opt ) ) { \ 54 0 : counters_opt->failure_ring[ ( counters_opt->failure_cnt++ )%FD_TXN_PARSE_COUNTERS_RING_SZ ] = __LINE__; \ 55 0 : } \ 56 0 : return 0UL; \ 57 0 : } \ 58 215092 : } 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 87895 : #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 5543 : #define READ_CHECKED_COMPACT_U16( out_sz, var_name, where ) \ 68 38443 : do { \ 69 38443 : ulong _where = (where); \ 70 38443 : ulong _out_sz = fd_cu16_dec_sz( payload+_where, payload_sz-_where ); \ 71 38443 : CHECK( _out_sz ); \ 72 38443 : (var_name) = fd_cu16_dec_fixed( payload+_where, _out_sz ); \ 73 38443 : (out_sz) = _out_sz; \ 74 38443 : } 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 5543 : #define MIN_INSTR_SZ (3UL) 79 5543 : 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 5543 : CHECK_LEFT( 1UL ); uchar signature_cnt = payload[ i ]; i++; 87 : /* Must have at least one signer for the fee payer */ 88 5543 : CHECK( (1UL<=signature_cnt) & (signature_cnt<=FD_TXN_SIG_MAX) ); 89 5543 : 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 5543 : CHECK_LEFT( 1UL ); uchar header_b0 = payload[ i ]; i++; 93 : 94 5543 : uchar transaction_version; 95 5543 : if( FD_LIKELY( (ulong)header_b0 & 0x80UL ) ) { 96 : /* This is a versioned transaction */ 97 5029 : transaction_version = header_b0 & 0x7F; 98 5029 : CHECK( transaction_version==FD_TXN_V0 ); /* Only recognized one so far */ 99 : 100 5029 : CHECK_LEFT( 1UL ); CHECK( signature_cnt==payload[ i ] ); i++; 101 5029 : } else { 102 514 : transaction_version = FD_TXN_VLEGACY; 103 514 : CHECK( signature_cnt==header_b0 ); 104 514 : } 105 5543 : CHECK_LEFT( 1UL ); uchar ro_signed_cnt = payload[ i ]; i++; 106 : /* Must have at least one writable signer for the fee payer */ 107 5543 : CHECK( ro_signed_cnt<signature_cnt ); 108 : 109 5543 : CHECK_LEFT( 1UL ); uchar ro_unsigned_cnt= payload[ i ]; i++; 110 : 111 5543 : ushort acct_addr_cnt = (ushort)0; 112 5543 : READ_CHECKED_COMPACT_U16( bytes_consumed, acct_addr_cnt, i ); i+=bytes_consumed; 113 5543 : CHECK( (signature_cnt<=acct_addr_cnt) & (acct_addr_cnt<=FD_TXN_ACCT_ADDR_MAX) ); 114 5543 : CHECK( (ulong)signature_cnt+(ulong)ro_unsigned_cnt<=(ulong)acct_addr_cnt ); 115 : 116 5543 : CHECK_LEFT( FD_TXN_ACCT_ADDR_SZ*acct_addr_cnt ); ulong acct_addr_off = i ; i+=FD_TXN_ACCT_ADDR_SZ*acct_addr_cnt; 117 5543 : CHECK_LEFT( FD_TXN_BLOCKHASH_SZ ); ulong recent_blockhash_off = i ; i+=FD_TXN_BLOCKHASH_SZ; 118 : 119 5543 : ushort instr_cnt = (ushort)0; 120 5543 : READ_CHECKED_COMPACT_U16( bytes_consumed, instr_cnt, i ); i+=bytes_consumed; 121 : 122 5543 : CHECK( (ulong)instr_cnt<=FD_TXN_INSTR_MAX ); 123 : 124 5543 : CHECK_LEFT( MIN_INSTR_SZ*instr_cnt ); 125 : /* If it has >0 instructions, it must have at least one other account 126 : address (the program id) that can't be the fee payer */ 127 5543 : CHECK( (ulong)acct_addr_cnt>(!!instr_cnt) ); 128 : 129 5543 : fd_txn_t * parsed = (fd_txn_t *)out_buf; 130 : 131 5543 : if( parsed ) { 132 5538 : parsed->transaction_version = transaction_version; 133 5538 : parsed->signature_cnt = signature_cnt; 134 5538 : parsed->signature_off = (ushort)signature_off; 135 5538 : parsed->message_off = (ushort)message_off; 136 5538 : parsed->readonly_signed_cnt = ro_signed_cnt; 137 5538 : parsed->readonly_unsigned_cnt = ro_unsigned_cnt; 138 5538 : parsed->acct_addr_cnt = acct_addr_cnt; 139 5538 : parsed->acct_addr_off = (ushort)acct_addr_off; 140 5538 : parsed->recent_blockhash_off = (ushort)recent_blockhash_off; 141 : /* Need to assign addr_table_lookup_cnt, 142 : addr_table_adtl_writable_cnt, addr_table_adtl_cnt, 143 : _padding_reserved_1 later */ 144 5538 : parsed->instr_cnt = instr_cnt; 145 5538 : } 146 : 147 5543 : uchar max_acct = 0UL; 148 13428 : for( ulong j=0UL; j<instr_cnt; j++ ) { 149 : 150 : /* Parsing instruction */ 151 7885 : ushort acct_cnt = (ushort)0; 152 7885 : ushort data_sz = (ushort)0; 153 7885 : CHECK_LEFT( MIN_INSTR_SZ ); uchar program_id = payload[ i ]; i++; 154 7885 : READ_CHECKED_COMPACT_U16( bytes_consumed, acct_cnt, i ); i+=bytes_consumed; 155 7885 : CHECK_LEFT( acct_cnt ); ulong acct_off = i ; 156 30119 : for( ulong k=0; k<acct_cnt; k++ ) { max_acct=fd_uchar_max( max_acct, payload[ k+i ] ); } i+=acct_cnt; 157 7885 : READ_CHECKED_COMPACT_U16( bytes_consumed, data_sz, i ); i+=bytes_consumed; 158 7885 : CHECK_LEFT( data_sz ); ulong data_off = i ; i+=data_sz; 159 : 160 : /* Account 0 is the fee payer and the program can't be the fee 161 : payer. The fee payer account must be owned by the system 162 : program, but the program must be an executable account and the 163 : system program is not permitted to own any executable account. 164 : As of https://github.com/solana-labs/solana/issues/25034, the 165 : program ID can't come from a table. */ 166 7885 : CHECK( (0UL < (ulong)program_id) & ((ulong)program_id < (ulong)acct_addr_cnt) ); 167 : 168 7887 : if( parsed ){ 169 7887 : parsed->instr[ j ].program_id = program_id; 170 7887 : parsed->instr[ j ]._padding_reserved_1 = (uchar)0; 171 7887 : parsed->instr[ j ].acct_cnt = acct_cnt; 172 7887 : parsed->instr[ j ].data_sz = data_sz; 173 : /* By our invariant, i<size when it was copied into acct_off and 174 : data_off, and size<=USHORT_MAX from above, so this cast is safe */ 175 7887 : parsed->instr[ j ].acct_off = (ushort)acct_off; 176 7887 : parsed->instr[ j ].data_off = (ushort)data_off; 177 7887 : } 178 7885 : } 179 5543 : #undef MIN_INSTR_SIZE 180 : 181 5543 : ushort addr_table_cnt = 0; 182 5543 : ulong addr_table_adtl_writable_cnt = 0; 183 5543 : ulong addr_table_adtl_cnt = 0; 184 : 185 : /* parsed->instr_cnt set above, so calling get_address_tables is safe */ 186 5543 : fd_txn_acct_addr_lut_t * address_tables = (parsed == NULL) ? NULL : fd_txn_get_address_tables( parsed ); 187 5543 : if( FD_LIKELY( transaction_version==FD_TXN_V0 ) ) { 188 5027 : #define MIN_ADDR_LUT_SIZE (34UL) 189 5027 : READ_CHECKED_COMPACT_U16( bytes_consumed, addr_table_cnt, i ); i+=bytes_consumed; 190 5027 : CHECK( addr_table_cnt <= FD_TXN_ADDR_TABLE_LOOKUP_MAX ); 191 5027 : CHECK_LEFT( MIN_ADDR_LUT_SIZE*addr_table_cnt ); 192 : 193 8307 : for( ulong j=0; j<addr_table_cnt; j++ ) { 194 3280 : CHECK_LEFT( FD_TXN_ACCT_ADDR_SZ ); ulong addr_off = i ; i+=FD_TXN_ACCT_ADDR_SZ; 195 : 196 3280 : ushort writable_cnt = 0; 197 3280 : ushort readonly_cnt = 0; 198 3280 : READ_CHECKED_COMPACT_U16( bytes_consumed, writable_cnt, i ); i+=bytes_consumed; 199 3280 : CHECK_LEFT( writable_cnt ); ulong writable_off = i ; i+=writable_cnt; 200 3280 : READ_CHECKED_COMPACT_U16( bytes_consumed, readonly_cnt, i ); i+=bytes_consumed; 201 3280 : CHECK_LEFT( readonly_cnt ); ulong readonly_off = i ; i+=readonly_cnt; 202 : 203 3280 : CHECK( writable_cnt<=FD_TXN_ACCT_ADDR_MAX-acct_addr_cnt ); /* implies <256 ... */ 204 3280 : CHECK( readonly_cnt<=FD_TXN_ACCT_ADDR_MAX-acct_addr_cnt ); 205 3280 : CHECK( (ushort)1 <=writable_cnt+readonly_cnt ); /* ... so the sum can't overflow */ 206 3280 : if( address_tables ) { 207 3280 : address_tables[ j ].addr_off = (ushort)addr_off; 208 3280 : address_tables[ j ].writable_cnt = (uchar )writable_cnt; 209 3280 : address_tables[ j ].readonly_cnt = (uchar )readonly_cnt; 210 3280 : address_tables[ j ].writable_off = (ushort)writable_off; 211 3280 : address_tables[ j ].readonly_off = (ushort)readonly_off; 212 3280 : } 213 : 214 3280 : addr_table_adtl_writable_cnt += (ulong)writable_cnt; 215 3280 : addr_table_adtl_cnt += (ulong)writable_cnt + (ulong)readonly_cnt; 216 3280 : } 217 5027 : } 218 5543 : #undef MIN_ADDR_LUT_SIZE 219 : /* Check for leftover bytes if out_sz_opt not specified. */ 220 5543 : CHECK( (payload_sz_opt!=NULL) | (i==payload_sz) ); 221 : 222 5543 : CHECK( acct_addr_cnt+addr_table_adtl_cnt<=FD_TXN_ACCT_ADDR_MAX ); /* implies addr_table_adtl_cnt<256 */ 223 : 224 : /* Final validation that all the account address indices are in range */ 225 5543 : CHECK( max_acct < acct_addr_cnt + addr_table_adtl_cnt ); 226 : 227 5543 : if( parsed ) { 228 : /* Assign final variables */ 229 5539 : parsed->addr_table_lookup_cnt = (uchar)addr_table_cnt; 230 5539 : parsed->addr_table_adtl_writable_cnt = (uchar)addr_table_adtl_writable_cnt; 231 5539 : parsed->addr_table_adtl_cnt = (uchar)addr_table_adtl_cnt; 232 5539 : parsed->_padding_reserved_1 = (uchar)0; 233 5539 : } 234 : 235 5543 : if( FD_LIKELY( counters_opt ) ) counters_opt->success_cnt++; 236 5543 : if( FD_LIKELY( payload_sz_opt ) ) *payload_sz_opt = i; 237 : 238 5543 : return fd_txn_footprint( instr_cnt, addr_table_cnt ); 239 : 240 5543 : #undef CHECK 241 5543 : #undef CHECK_LEFT 242 5543 : #undef READ_CHECKED_COMPACT_U16 243 5543 : }