Line data Source code
1 : #include "fd_shred.h" 2 : 3 : fd_shred_t const * 4 : fd_shred_parse( uchar const * const buf, 5 10089 : ulong const sz ) { 6 10089 : ulong total_shred_sz = sz; 7 : /* Initial bounds check */ 8 10089 : if( FD_UNLIKELY( total_shred_sz<fd_ulong_min( FD_SHRED_DATA_HEADER_SZ, FD_SHRED_CODE_HEADER_SZ ) ) ) return NULL; 9 : 10 10089 : 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 10089 : uchar variant = shred->variant; 15 10089 : uchar type = fd_shred_type( variant ); 16 10089 : if( FD_UNLIKELY( (type!=FD_SHRED_TYPE_MERKLE_DATA) & 17 10089 : (type!=FD_SHRED_TYPE_MERKLE_CODE) & 18 10089 : (type!=FD_SHRED_TYPE_MERKLE_DATA_CHAINED) & 19 10089 : (type!=FD_SHRED_TYPE_MERKLE_CODE_CHAINED) & 20 10089 : (type!=FD_SHRED_TYPE_MERKLE_DATA_CHAINED_RESIGNED) & 21 10089 : (type!=FD_SHRED_TYPE_MERKLE_CODE_CHAINED_RESIGNED) & 22 10089 : (variant!=0xa5 /*FD_SHRED_TYPE_LEGACY_DATA*/ ) & 23 10089 : (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 9615 : ulong header_sz = fd_shred_header_sz( variant ); /* between 88 and 89 bytes */ 33 9615 : ulong trailer_sz = fd_shred_merkle_sz( shred->variant ) /* between 0 and 300 bytes */ 34 9615 : + fd_ulong_if( fd_shred_is_resigned( type ), FD_SHRED_SIGNATURE_SZ, 0UL ) /* 0 or 64 */ 35 9615 : + fd_ulong_if( fd_shred_is_chained( type ), FD_SHRED_MERKLE_ROOT_SZ, 0UL ); /* 0 or 32 */ 36 9615 : ulong zero_padding_sz; 37 9615 : ulong payload_sz; 38 : 39 9615 : if( FD_LIKELY( type & FD_SHRED_TYPEMASK_DATA ) ) { 40 4377 : if( FD_UNLIKELY( shred->data.size<header_sz ) ) return NULL; 41 4377 : payload_sz = (ulong)shred->data.size - header_sz; /* between 0 and USHORT_MAX */ 42 4377 : 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 4377 : uchar is_legacy_data_shred = type==FD_SHRED_TYPE_LEGACY_DATA; 54 4377 : ulong effective_sz = fd_ulong_if( is_legacy_data_shred, sz, FD_SHRED_MIN_SZ ); 55 4377 : if( FD_UNLIKELY( effective_sz < header_sz+payload_sz+trailer_sz ) ) return NULL; 56 4377 : zero_padding_sz = effective_sz - header_sz - payload_sz - trailer_sz; 57 4377 : } 58 5238 : else if( FD_LIKELY( type & FD_SHRED_TYPEMASK_CODE ) ) { 59 5238 : zero_padding_sz = 0UL; 60 : /* Payload size is not specified directly, but the whole shred must 61 : be FD_SHRED_MAX_SZ. */ 62 5238 : if( FD_UNLIKELY( header_sz+zero_padding_sz+trailer_sz > FD_SHRED_MAX_SZ ) ) return NULL; 63 5238 : payload_sz = FD_SHRED_MAX_SZ - header_sz - zero_padding_sz - trailer_sz; 64 5238 : } 65 0 : else return NULL; 66 : 67 9615 : 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 9615 : if( FD_LIKELY( type & FD_SHRED_TYPEMASK_DATA ) ) { 72 4377 : ulong parent_off = (ulong)shred->data.parent_off; 73 4377 : ulong slot = shred->slot; 74 4377 : if( FD_UNLIKELY( (shred->data.flags&0xC0)==0x80 ) ) return NULL; 75 4374 : if( FD_UNLIKELY( parent_off>slot ) ) return NULL; 76 : /* The property we want to enforce is 77 : slot==0 <=> parent_off==0 <=> slot==parent_off, 78 : where <=> means if and only if. It's a strange expression 79 : though, because any two of the statements automatically imply the 80 : other one, so it's logically equivalent to: 81 : (slot==0 or parent_off==0) <=> slot==parent_off 82 : We want the complement though, so that we can return NULL, and 83 : the complement of iff is xor. */ 84 4368 : if( FD_UNLIKELY( ((parent_off==0) | (slot==0UL)) ^ (slot==parent_off) ) ) return NULL; 85 4362 : if( FD_UNLIKELY( shred->idx<shred->fec_set_idx ) ) return NULL; 86 5238 : } else { 87 5238 : if( FD_UNLIKELY( shred->code.idx>=shred->code.code_cnt ) ) return NULL; 88 5232 : if( FD_UNLIKELY( shred->code.idx> shred->idx ) ) return NULL; 89 5229 : if( FD_UNLIKELY( (shred->code.data_cnt==0)|(shred->code.code_cnt==0) ) ) return NULL; 90 5226 : if( FD_UNLIKELY( shred->code.code_cnt>256 ) ) return NULL; 91 5226 : 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 */ 92 5226 : } 93 : 94 9576 : return shred; 95 9615 : }