Line data Source code
1 : #include "fd_shred.h" 2 : 3 : fd_shred_t const * 4 : fd_shred_parse( uchar const * const buf, 5 33744 : ulong const sz ) { 6 33744 : ulong total_shred_sz = sz; 7 : /* Initial bounds check */ 8 33744 : if( FD_UNLIKELY( total_shred_sz<fd_ulong_min( FD_SHRED_DATA_HEADER_SZ, FD_SHRED_CODE_HEADER_SZ ) ) ) return NULL; 9 : 10 33744 : fd_shred_t const * shred = (fd_shred_t *)buf; 11 : 12 : /* Validate shred type. 13 : Safe to access because `variant` ends at 0x41, which is <= 0x58 */ 14 33744 : uchar variant = shred->variant; 15 33744 : uchar type = fd_shred_type( variant ); 16 33744 : if( FD_UNLIKELY( (type!=FD_SHRED_TYPE_MERKLE_DATA) & 17 33744 : (type!=FD_SHRED_TYPE_MERKLE_CODE) & 18 33744 : (type!=FD_SHRED_TYPE_MERKLE_DATA_CHAINED) & 19 33744 : (type!=FD_SHRED_TYPE_MERKLE_CODE_CHAINED) & 20 33744 : (type!=FD_SHRED_TYPE_MERKLE_DATA_CHAINED_RESIGNED) & 21 33744 : (type!=FD_SHRED_TYPE_MERKLE_CODE_CHAINED_RESIGNED) & 22 33744 : (variant!=0xa5 /*FD_SHRED_TYPE_LEGACY_DATA*/ ) & 23 33744 : (variant!=0x5a /*FD_SHRED_TYPE_LEGACY_CODE*/ ) ) ) 24 474 : return NULL; 25 : 26 : /* There are six sections of a shred that can contribute to the size: 27 : header, payload, zero-padding, Merkle root of previous erasure 28 : batch, Merkle proof, and retransmitter signature. 29 : Some of these may have 0 size in certain cases. sz is the sum of 30 : all 5, while for data shreds, shred->data.size == header+payload. 31 : We'll call the last three section the trailer. */ 32 33270 : ulong header_sz = fd_shred_header_sz( variant ); /* between 88 and 89 bytes */ 33 33270 : ulong trailer_sz = fd_shred_merkle_sz( shred->variant ) /* between 0 and 300 bytes */ 34 33270 : + fd_ulong_if( fd_shred_is_resigned( type ), FD_SHRED_SIGNATURE_SZ, 0UL ) /* 0 or 64 */ 35 33270 : + fd_ulong_if( fd_shred_is_chained( type ), FD_SHRED_MERKLE_ROOT_SZ, 0UL ); /* 0 or 32 */ 36 33270 : ulong zero_padding_sz; 37 33270 : ulong payload_sz; 38 : 39 33270 : if( FD_LIKELY( type & FD_SHRED_TYPEMASK_DATA ) ) { 40 16221 : if( FD_UNLIKELY( shred->data.size<header_sz ) ) return NULL; 41 16221 : payload_sz = (ulong)shred->data.size - header_sz; /* between 0 and USHORT_MAX */ 42 16221 : if( FD_UNLIKELY( (type!=FD_SHRED_TYPE_LEGACY_DATA) & (sz<FD_SHRED_MIN_SZ) ) ) return NULL; 43 : 44 : /* legacy data shreds might be shorter than the normal 45 : FD_SHRED_MIN_SZ, but they don't have Merkle proofs, so everything 46 : after the payload is zero-padding/ignored. On the other hand, 47 : Merkle data shreds might have some zero-padding, but anything 48 : between [FD_SHRED_MIN_SZ, sz) is extra bytes after the shred 49 : (which we don't care about the contents of but also tolerate). 50 : The Merkle proof is not in bytes [sz-merkle_proof_sz, sz) but in 51 : [FD_SHRED_MIN_SZ-merkle_proof_sz, FD_SHRED_MIN_SZ). From above, 52 : we know sz >= FD_SHRED_MIN_SZ in this case. */ 53 16221 : uchar is_legacy_data_shred = type==FD_SHRED_TYPE_LEGACY_DATA; 54 16221 : ulong effective_sz = fd_ulong_if( is_legacy_data_shred, sz, FD_SHRED_MIN_SZ ); 55 16221 : if( FD_UNLIKELY( effective_sz < header_sz+payload_sz+trailer_sz ) ) return NULL; 56 16221 : zero_padding_sz = effective_sz - header_sz - payload_sz - trailer_sz; 57 16221 : } 58 17049 : else if( FD_LIKELY( type & FD_SHRED_TYPEMASK_CODE ) ) { 59 17049 : zero_padding_sz = 0UL; 60 : /* Payload size is not specified directly, but the whole shred must 61 : be FD_SHRED_MAX_SZ. */ 62 17049 : if( FD_UNLIKELY( header_sz+zero_padding_sz+trailer_sz > FD_SHRED_MAX_SZ ) ) return NULL; 63 17049 : payload_sz = FD_SHRED_MAX_SZ - header_sz - zero_padding_sz - trailer_sz; 64 17049 : } 65 0 : else return NULL; 66 : 67 33270 : if( FD_UNLIKELY( sz < header_sz + payload_sz + zero_padding_sz + trailer_sz ) ) return NULL; 68 : 69 : /* At this point we know all the fields exist, but we need to sanity 70 : check a few fields that would make a shred illegal. */ 71 33270 : if( FD_LIKELY( type & FD_SHRED_TYPEMASK_DATA ) ) { 72 16221 : ulong parent_off = (ulong)shred->data.parent_off; 73 16221 : ulong slot = shred->slot; 74 16221 : if( FD_UNLIKELY( (shred->data.flags&0xC0)==0x80 ) ) return NULL; 75 16218 : if( FD_UNLIKELY( parent_off>slot ) ) return NULL; 76 : 77 : /* There are 3 cases we want to allow: 78 : slot==0, parent_off==0 79 : slot==1, parent_off==1 80 : slot>1, 0<parent_off<slot 81 : We've already ensured parent_off<=slot, so the cases we need to reject are: 82 : slot==1, parent_off==0 83 : slot>1, parent_off==0 84 : slot>1, parent_off==slot 85 : 86 : That gives 87 : (slot==1 & parent_off==0) | (slot>1 & parent_off==0) | (slot>1 & parent_off==slot) 88 : Simplifying a bit, 89 : ((slot==1 | slot>1) & parent_off==0) | (slot>1 & parent_off==slot) 90 : (slot!=0 & parent_off==0) | (slot>1 & parent_off==slot) 91 : 92 : https://github.com/anza-xyz/agave/blob/dda8b79162d9aa1191c7813ca7f024ab5a5b0b9f/ledger/src/blockstore.rs#L5035 93 : */ 94 : 95 16212 : if( FD_UNLIKELY( ((slot!=0UL) & (parent_off==0UL)) | ((slot>1UL) & (parent_off==slot)) ) ) return NULL; 96 16206 : if( FD_UNLIKELY( shred->idx<shred->fec_set_idx ) ) return NULL; 97 17049 : } else { 98 17049 : if( FD_UNLIKELY( shred->code.idx>=shred->code.code_cnt ) ) return NULL; 99 17043 : if( FD_UNLIKELY( shred->code.idx> shred->idx ) ) return NULL; 100 17040 : if( FD_UNLIKELY( (shred->code.data_cnt==0)|(shred->code.code_cnt==0) ) ) return NULL; 101 17037 : if( FD_UNLIKELY( shred->code.code_cnt>256 ) ) return NULL; 102 17037 : if( FD_UNLIKELY( (ulong)shred->code.data_cnt+(ulong)shred->code.code_cnt>256 ) ) return NULL; /* I don't see this check in Agave, but it seems necessary */ 103 17037 : } 104 : 105 33231 : return shred; 106 33270 : } 107 : 108 : FD_FN_PURE int 109 3 : fd_shred_merkle_root( fd_shred_t const * shred, void * bmtree_mem, fd_bmtree_node_t * root_out ) { 110 3 : fd_bmtree_commit_t * tree = fd_bmtree_commit_init( bmtree_mem, 111 3 : FD_SHRED_MERKLE_NODE_SZ, 112 3 : FD_BMTREE_LONG_PREFIX_SZ, 113 3 : FD_SHRED_MERKLE_LAYER_CNT ); 114 : 115 3 : uchar shred_type = fd_shred_type( shred->variant ); 116 3 : int is_data_shred = fd_shred_is_data( shred_type ); 117 3 : ulong in_type_idx = fd_ulong_if( is_data_shred, shred->idx - shred->fec_set_idx, shred->code.idx ); 118 3 : ulong shred_idx = fd_ulong_if( is_data_shred, in_type_idx, in_type_idx + shred->code.data_cnt ); 119 : 120 3 : ulong tree_depth = fd_shred_merkle_cnt( shred->variant ); /* In [0, 15] */ 121 3 : ulong reedsol_protected_sz = 1115UL + FD_SHRED_DATA_HEADER_SZ - FD_SHRED_SIGNATURE_SZ - FD_SHRED_MERKLE_NODE_SZ*tree_depth 122 3 : - FD_SHRED_MERKLE_ROOT_SZ*fd_shred_is_chained ( shred_type ) 123 3 : - FD_SHRED_SIGNATURE_SZ *fd_shred_is_resigned( shred_type); /* In [743, 1139] conservatively*/ 124 3 : ulong data_merkle_protected_sz = reedsol_protected_sz + FD_SHRED_MERKLE_ROOT_SZ*fd_shred_is_chained ( shred_type ); 125 3 : ulong parity_merkle_protected_sz = reedsol_protected_sz + FD_SHRED_MERKLE_ROOT_SZ*fd_shred_is_chained ( shred_type )+FD_SHRED_CODE_HEADER_SZ-FD_ED25519_SIG_SZ; 126 3 : ulong merkle_protected_sz = fd_ulong_if( is_data_shred, data_merkle_protected_sz, parity_merkle_protected_sz ); 127 3 : fd_bmtree_node_t leaf; 128 3 : fd_bmtree_hash_leaf( &leaf, (uchar const *)shred + sizeof(fd_ed25519_sig_t), merkle_protected_sz, FD_BMTREE_LONG_PREFIX_SZ ); 129 : 130 3 : return fd_bmtree_commitp_insert_with_proof( tree, shred_idx, &leaf, (uchar const *)fd_shred_merkle_nodes( shred ), fd_shred_merkle_cnt( shred->variant ), root_out ); 131 3 : }