From 4936f39676997d95e5d15772d3904e5942fa9864 Mon Sep 17 00:00:00 2001 From: Philip Taffet Date: Mon, 19 Aug 2024 22:06:05 +0000 Subject: [PATCH] shred: add extra shred validation --- src/app/fdctl/run/tiles/fd_shred.c | 4 +- src/app/fdctl/run/topos/fd_firedancer.c | 11 +- src/app/fdctl/run/topos/fd_frankendancer.c | 11 +- src/ballet/shred/fd_shred.c | 15 ++ src/ballet/shred/fd_shred.h | 23 +-- src/ballet/shred/test_shred.c | 2 + src/disco/shred/fd_fec_resolver.c | 23 ++- src/disco/shred/fd_fec_resolver.h | 11 +- src/disco/shred/test_fec_resolver.c | 154 ++++++++++++++++++++- src/disco/topo/fd_topo.h | 1 + 10 files changed, 222 insertions(+), 33 deletions(-) diff --git a/src/app/fdctl/run/tiles/fd_shred.c b/src/app/fdctl/run/tiles/fd_shred.c index 789c48a0d8..9557d2cf5a 100644 --- a/src/app/fdctl/run/tiles/fd_shred.c +++ b/src/app/fdctl/run/tiles/fd_shred.c @@ -791,6 +791,7 @@ unprivileged_init( fd_topo_t * topo, sign_in->mcache, sign_in->dcache ) ) ); + ulong shred_limit = fd_ulong_if( tile->shred.larger_shred_limits_per_block, 32UL*32UL*1024UL, 32UL*1024UL ); fd_fec_set_t * resolver_sets = fec_sets + (shred_store_mcache_depth+1UL)/2UL + 1UL; ctx->shredder = NONNULL( fd_shredder_join ( fd_shredder_new ( _shredder, fd_shred_signer, ctx->keyguard_client, (ushort)expected_shred_version ) ) ); ctx->resolver = NONNULL( fd_fec_resolver_join ( fd_fec_resolver_new ( _resolver, @@ -798,7 +799,8 @@ unprivileged_init( fd_topo_t * topo, tile->shred.fec_resolver_depth, 1UL, (shred_store_mcache_depth+3UL)/2UL, 128UL * tile->shred.fec_resolver_depth, resolver_sets, - (ushort)expected_shred_version ) ) ); + (ushort)expected_shred_version, + shred_limit ) ) ); ctx->shred34 = shred34; ctx->fec_sets = fec_sets; diff --git a/src/app/fdctl/run/topos/fd_firedancer.c b/src/app/fdctl/run/topos/fd_firedancer.c index f3fa61c7a9..00193376ab 100644 --- a/src/app/fdctl/run/topos/fd_firedancer.c +++ b/src/app/fdctl/run/topos/fd_firedancer.c @@ -425,11 +425,12 @@ fd_topo_firedancer( config_t * _config ) { fd_memcpy( tile->shred.src_mac_addr, config->tiles.net.mac_addr, 6 ); strncpy( tile->shred.identity_key_path, config->consensus.identity_path, sizeof(tile->shred.identity_key_path) ); - tile->shred.depth = topo->links[ tile->out_link_id_primary ].depth; - tile->shred.ip_addr = config->tiles.net.ip_addr; - tile->shred.fec_resolver_depth = config->tiles.shred.max_pending_shred_sets; - tile->shred.expected_shred_version = config->consensus.expected_shred_version; - tile->shred.shred_listen_port = config->tiles.shred.shred_listen_port; + tile->shred.depth = topo->links[ tile->out_link_id_primary ].depth; + tile->shred.ip_addr = config->tiles.net.ip_addr; + tile->shred.fec_resolver_depth = config->tiles.shred.max_pending_shred_sets; + tile->shred.expected_shred_version = config->consensus.expected_shred_version; + tile->shred.shred_listen_port = config->tiles.shred.shred_listen_port; + tile->shred.larger_shred_limits_per_block = config->development.bench.larger_shred_limits_per_block; } else if( FD_UNLIKELY( !strcmp( tile->name, "storei" ) ) ) { strncpy( tile->store_int.blockstore_restore, config->tiles.store_int.blockstore_restore, sizeof(tile->store_int.blockstore_restore) ); diff --git a/src/app/fdctl/run/topos/fd_frankendancer.c b/src/app/fdctl/run/topos/fd_frankendancer.c index 27718937aa..71aff6bfc8 100644 --- a/src/app/fdctl/run/topos/fd_frankendancer.c +++ b/src/app/fdctl/run/topos/fd_frankendancer.c @@ -283,11 +283,12 @@ fd_topo_frankendancer( config_t * config ) { fd_memcpy( tile->shred.src_mac_addr, config->tiles.net.mac_addr, 6 ); strncpy( tile->shred.identity_key_path, config->consensus.identity_path, sizeof(tile->shred.identity_key_path) ); - tile->shred.depth = topo->links[ tile->out_link_id_primary ].depth; - tile->shred.ip_addr = config->tiles.net.ip_addr; - tile->shred.fec_resolver_depth = config->tiles.shred.max_pending_shred_sets; - tile->shred.expected_shred_version = config->consensus.expected_shred_version; - tile->shred.shred_listen_port = config->tiles.shred.shred_listen_port; + tile->shred.depth = topo->links[ tile->out_link_id_primary ].depth; + tile->shred.ip_addr = config->tiles.net.ip_addr; + tile->shred.fec_resolver_depth = config->tiles.shred.max_pending_shred_sets; + tile->shred.expected_shred_version = config->consensus.expected_shred_version; + tile->shred.shred_listen_port = config->tiles.shred.shred_listen_port; + tile->shred.larger_shred_limits_per_block = config->development.bench.larger_shred_limits_per_block; } else if( FD_UNLIKELY( !strcmp( tile->name, "store" ) ) ) { tile->store.disable_blockstore_from_slot = config->development.bench.disable_blockstore_from_slot; diff --git a/src/ballet/shred/fd_shred.c b/src/ballet/shred/fd_shred.c index 4ed4f4ad01..2c599246e6 100644 --- a/src/ballet/shred/fd_shred.c +++ b/src/ballet/shred/fd_shred.c @@ -68,5 +68,20 @@ fd_shred_parse( uchar const * const buf, if( FD_UNLIKELY( sz < header_sz + payload_sz + zero_padding_sz + merkle_proof_sz + previous_merkle_root_sz ) ) return NULL; + /* At this point we know all the fields exist, but we need to sanity + check a few fields that would make a shred illegal. */ + if( FD_LIKELY( type & FD_SHRED_TYPEMASK_DATA ) ) { + if( FD_UNLIKELY( (shred->data.flags&0xC0)==0x80 ) ) return NULL; + if( FD_UNLIKELY( (ulong)(shred->data.parent_off)>shred->slot ) ) return NULL; + if( FD_UNLIKELY( (shred->data.parent_off==0) & (shred->slot!=0UL) ) ) return NULL; + if( FD_UNLIKELY( shred->idxfec_set_idx ) ) return NULL; + } else { + if( FD_UNLIKELY( shred->code.idx>=shred->code.code_cnt ) ) return NULL; + if( FD_UNLIKELY( shred->code.idx> shred->idx ) ) return NULL; + if( FD_UNLIKELY( (shred->code.data_cnt==0)|(shred->code.code_cnt==0) ) ) return NULL; + if( FD_UNLIKELY( shred->code.code_cnt>256 ) ) return NULL; + 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 */ + } + return shred; } diff --git a/src/ballet/shred/fd_shred.h b/src/ballet/shred/fd_shred.h index d9f0d9923f..1e5e2ee52c 100644 --- a/src/ballet/shred/fd_shred.h +++ b/src/ballet/shred/fd_shred.h @@ -190,14 +190,16 @@ struct __attribute__((packed)) fd_shred { /* Hash of the genesis version and historical hard forks of the current chain */ /* 0x4d */ ushort version; - /* Index into the vector of FEC sets for this slot */ + /* Index into the vector of FEC sets for this slot. For data shreds, fec_set_idx<=idx. */ /* 0x4f */ uint fec_set_idx; union { /* Common data shred header */ struct __attribute__((packed)) { /* Slot number difference between this block and the parent block. - Always greater than zero. */ + parent_off <= slot. + Always greater than zero, except for slot 0, in which case the + previous invariant forces this to be 0. */ /* 0x53 */ ushort parent_off; /* Bit field (MSB first) @@ -214,13 +216,14 @@ struct __attribute__((packed)) fd_shred { /* Common coding shred header */ struct __attribute__((packed)) { - /* Total number of data shreds in slot */ + /* Total number of data shreds in slot. Must be positive. */ /* 0x53 */ ushort data_cnt; - /* Total number of coding shreds in slot */ + /* Total number of coding shreds in slot. Must be positive. */ /* 0x55 */ ushort code_cnt; - /* Index within the vector of coding shreds in slot */ + /* Index within the vector of coding shreds in slot. In [0, + code_cnt). Also, shred.code.idx <= shred.idx. */ /* 0x57 */ ushort idx; } code; }; @@ -229,11 +232,13 @@ typedef struct fd_shred fd_shred_t; FD_PROTOTYPES_BEGIN -/* fd_shred_parse: Parses and validates an untrusted shred header. - The provided buffer must be at least FD_SHRED_MIN_SZ bytes long. +/* fd_shred_parse: Parses and validates an untrusted shred stored in + bytes buf[i] for i in [0, sz). sz must be at least FD_SHRED_MIN_SZ + bytes. Allows trailing data. - The returned pointer either equals the input pointer - or is NULL if the given shred is malformed. */ + The returned pointer either equals the input pointer or is NULL if + the given shred is malformed or violates any invariants described + above. */ FD_FN_PURE fd_shred_t const * fd_shred_parse( uchar const * buf, ulong sz ); diff --git a/src/ballet/shred/test_shred.c b/src/ballet/shred/test_shred.c index 4c547132df..adb29a9f37 100644 --- a/src/ballet/shred/test_shred.c +++ b/src/ballet/shred/test_shred.c @@ -99,6 +99,8 @@ main( int argc, /* Test type-specific bounds checks */ if( FD_LIKELY( is_valid )) { + buf[0x53] = buf[0x55] = (uchar)is_code; /* Set data_cnt, code_cnt to 1 */ + /* Test merkle node count */ uint merkle_cnt = fd_shred_merkle_cnt( (uchar)i ); FD_TEST( (is_merkle || merkle_cnt==0) && merkle_cnt<=16 ); diff --git a/src/disco/shred/fd_fec_resolver.c b/src/disco/shred/fd_fec_resolver.c index 207e325dd8..6c43640027 100644 --- a/src/disco/shred/fd_fec_resolver.c +++ b/src/disco/shred/fd_fec_resolver.c @@ -133,6 +133,13 @@ struct __attribute__((aligned(FD_FEC_RESOLVER_ALIGN))) fd_fec_resolver { fd_fec_resolver_sign_fn * signer; void * sign_ctx; + /* max_shred_idx is the exclusive upper bound for shred indices. We + need to reject any shred with an index >= max_shred_idx, but we + also want to reject anything that is part of an FEC set where the + highest index of a shred in the FEC set will be >= max_shred_idx. + */ + ulong max_shred_idx; + /* sha512 and reedsol are used for calculations while adding a shred. Their state outside a call to add_shred is indeterminate. */ fd_sha512_t sha512[1]; @@ -187,7 +194,8 @@ fd_fec_resolver_new( void * shmem, ulong complete_depth, ulong done_depth, fd_fec_set_t * sets, - ushort expected_shred_version ) { + ushort expected_shred_version, + ulong max_shred_idx ) { if( FD_UNLIKELY( (depth==0UL) | (partial_depth==0UL) | (complete_depth==0UL) | (done_depth==0UL) ) ) return NULL; if( FD_UNLIKELY( (depth>=(1UL<<62)-1UL) | (done_depth>=(1UL<<62)-1UL ) ) ) return NULL; @@ -241,6 +249,7 @@ fd_fec_resolver_new( void * shmem, resolver->expected_shred_version = expected_shred_version; resolver->signer = signer; resolver->sign_ctx = sign_ctx; + resolver->max_shred_idx = max_shred_idx; return shmem; } @@ -352,15 +361,23 @@ int fd_fec_resolver_add_shred( fd_fec_resolver_t * resolver, } if( FD_UNLIKELY( shred->version!=resolver->expected_shred_version ) ) return FD_FEC_RESOLVER_SHRED_REJECTED; + if( FD_UNLIKELY( shred_szidx>=resolver->max_shred_idx ) ) return FD_FEC_RESOLVER_SHRED_REJECTED; int is_data_shred = fd_shred_is_data( shred_type ); if( !is_data_shred ) { /* Roughly 50/50 branch */ if( FD_UNLIKELY( (shred->code.data_cnt>FD_REEDSOL_DATA_SHREDS_MAX) | (shred->code.code_cnt>FD_REEDSOL_PARITY_SHREDS_MAX) ) ) return FD_FEC_RESOLVER_SHRED_REJECTED; - if( FD_UNLIKELY( (shred->code.data_cnt==0UL) | (shred->code.code_cnt==0UL) ) ) return FD_FEC_RESOLVER_SHRED_REJECTED; + if( FD_UNLIKELY( (shred->code.data_cnt==0UL) | (shred->code.code_cnt==0UL) ) ) + return FD_FEC_RESOLVER_SHRED_REJECTED; + if( FD_UNLIKELY( (ulong)shred->fec_set_idx+(ulong)shred->code.data_cnt>=resolver->max_shred_idx ) ) + return FD_FEC_RESOLVER_SHRED_REJECTED; + if( FD_UNLIKELY( (ulong)shred->idx + (ulong)shred->code.code_cnt - (ulong)shred->code.idx>=resolver->max_shred_idx ) ) + return FD_FEC_RESOLVER_SHRED_REJECTED; } + /* For the purposes of the shred header, tree_depth means the number of nodes, counting the leaf but excluding the root. For bmtree, depth means the number of layers, which counts both. */ @@ -496,7 +513,7 @@ int fd_fec_resolver_add_shred( fd_fec_resolver_t * resolver, /* Copy the shred to memory the FEC resolver owns */ uchar * dst = fd_ptr_if( is_data_shred, ctx->set->data_shreds[ in_type_idx ], ctx->set->parity_shreds[ in_type_idx ] ); - fd_memcpy( dst, shred, shred_sz ); + fd_memcpy( dst, shred, fd_shred_sz( shred ) ); /* If the shred needs a retransmitter signature, set it */ if( FD_UNLIKELY( fd_shred_is_resigned( shred_type ) ) ) { diff --git a/src/disco/shred/fd_fec_resolver.h b/src/disco/shred/fd_fec_resolver.h index 57b372188e..9b4d9a246b 100644 --- a/src/disco/shred/fd_fec_resolver.h +++ b/src/disco/shred/fd_fec_resolver.h @@ -123,8 +123,12 @@ ulong fd_fec_resolver_align ( void ); output parameters of _add_shred. The FEC resolver will reject any shreds with a shred version that does not match the value provided for expected_shred_version. Shred versions are always non-zero, so - expected_shred_version must be non-zero. Returns shmem on success - and NULL on failure (logs details). */ + expected_shred_version must be non-zero. The FEC resolver will also + reject any shred that seems to be part of a block containing more + than max_shred_idx data or parity shreds. Since shred_idx is a uint, + it doesn't really make sense to have max_shred_idx > UINT_MAX, and + max_shred_idx==0 rejects all shreds. Returns shmem on success and + NULL on failure (logs details). */ void * fd_fec_resolver_new( void * shmem, fd_fec_resolver_sign_fn * signer, @@ -134,7 +138,8 @@ fd_fec_resolver_new( void * shmem, ulong complete_depth, ulong done_depth, fd_fec_set_t * sets, - ushort expected_shred_version ); + ushort expected_shred_version, + ulong max_shred_idx ); fd_fec_resolver_t * fd_fec_resolver_join( void * shmem ); diff --git a/src/disco/shred/test_fec_resolver.c b/src/disco/shred/test_fec_resolver.c index 45f0511139..51e7ec4d60 100644 --- a/src/disco/shred/test_fec_resolver.c +++ b/src/disco/shred/test_fec_resolver.c @@ -31,6 +31,7 @@ uchar metrics_scratch[ FD_METRICS_FOOTPRINT( 0, 0 ) ] __attribute__((aligned(FD_ fd_shredder_t _shredder[ 1 ]; #define SHRED_VER (ushort)6051 /* An arbitary value */ +#define MAX (32UL*1024UL) struct signer_ctx { fd_sha512_t sha512[ 1 ]; @@ -114,9 +115,9 @@ test_one_batch( void ) { ulong foot = fd_fec_resolver_footprint( 4UL, 1UL, 1UL, 1UL ); fd_fec_resolver_t *r1, *r2, *r3; - r1 = fd_fec_resolver_join( fd_fec_resolver_new( res_mem+0UL*foot, NULL, NULL, 2UL, 1UL, 1UL, 1UL, out_sets, SHRED_VER ) ); - r2 = fd_fec_resolver_join( fd_fec_resolver_new( res_mem+1UL*foot, NULL, NULL, 2UL, 1UL, 1UL, 1UL, out_sets+4UL, SHRED_VER ) ); - r3 = fd_fec_resolver_join( fd_fec_resolver_new( res_mem+2UL*foot, NULL, NULL, 2UL, 1UL, 1UL, 1UL, out_sets+8UL, SHRED_VER ) ); + r1 = fd_fec_resolver_join( fd_fec_resolver_new( res_mem+0UL*foot, NULL, NULL, 2UL, 1UL, 1UL, 1UL, out_sets, SHRED_VER, MAX ) ); + r2 = fd_fec_resolver_join( fd_fec_resolver_new( res_mem+1UL*foot, NULL, NULL, 2UL, 1UL, 1UL, 1UL, out_sets+4UL, SHRED_VER, MAX ) ); + r3 = fd_fec_resolver_join( fd_fec_resolver_new( res_mem+2UL*foot, NULL, NULL, 2UL, 1UL, 1UL, 1UL, out_sets+8UL, SHRED_VER, MAX ) ); fd_fec_set_t const * out_fec[1]; fd_shred_t const * out_shred[1]; @@ -186,7 +187,7 @@ test_interleaved( void ) { fd_fec_set_t const * out_fec[1]; fd_shred_t const * out_shred[1]; - fd_fec_resolver_t * resolver = fd_fec_resolver_join( fd_fec_resolver_new( res_mem, NULL, NULL, 2UL, 1UL, 1UL, 1UL, out_sets, SHRED_VER ) ); + fd_fec_resolver_t * resolver = fd_fec_resolver_join( fd_fec_resolver_new( res_mem, NULL, NULL, 2UL, 1UL, 1UL, 1UL, out_sets, SHRED_VER, MAX ) ); for( ulong j=0UL; jdata_shred_cnt; j++ ) { ADD_SHRED( resolver, set0->data_shreds[ j ], OKAY ); ADD_SHRED( resolver, set1->data_shreds[ j ], OKAY ); @@ -232,7 +233,7 @@ test_rolloff( void ) { FD_TEST( fd_shredder_fini_batch( shredder ) ); fd_fec_resolver_t * resolver; - resolver = fd_fec_resolver_join( fd_fec_resolver_new( res_mem, NULL, NULL, 2UL, 1UL, 1UL, 8UL, out_sets, SHRED_VER ) ); + resolver = fd_fec_resolver_join( fd_fec_resolver_new( res_mem, NULL, NULL, 2UL, 1UL, 1UL, 8UL, out_sets, SHRED_VER, MAX ) ); for( ulong j=0UL; jdata_shred_cnt; j++ ) { ADD_SHRED( resolver, set0->data_shreds[ j ], OKAY ); } for( ulong j=0UL; jdata_shred_cnt; j++ ) { ADD_SHRED( resolver, set1->data_shreds[ j ], OKAY ); } @@ -296,7 +297,7 @@ test_new_formats( void ) { for( ulong i=0UL; i<4UL; i++ ) ptr = allocate_fec_set( out_sets+i, ptr ); fd_fec_resolver_t * resolver; - resolver = fd_fec_resolver_join( fd_fec_resolver_new( res_mem, test_signer, signer_ctx, 2UL, 1UL, 1UL, 8UL, out_sets, 1 ) ); + resolver = fd_fec_resolver_join( fd_fec_resolver_new( res_mem, test_signer, signer_ctx, 2UL, 1UL, 1UL, 8UL, out_sets, 1, MAX ) ); uchar pubkey[32]; fd_base58_decode_32( "5XDmMEZpXM2GBXNjhgRCti4qLGeFQvx4RnzWeRgfupYk", pubkey ); @@ -385,7 +386,7 @@ test_shred_version( void ) { for( ulong i=0UL; i<4UL; i++ ) ptr = allocate_fec_set( out_sets+i, ptr ); - fd_fec_resolver_t * r = fd_fec_resolver_join( fd_fec_resolver_new( res_mem, NULL, NULL, 2UL, 1UL, 1UL, 1UL, out_sets, SHRED_VER ) ); + fd_fec_resolver_t * r = fd_fec_resolver_join( fd_fec_resolver_new( res_mem, NULL, NULL, 2UL, 1UL, 1UL, 1UL, out_sets, SHRED_VER, MAX ) ); fd_fec_set_t const * out_fec[1]; fd_shred_t const * out_shred[1]; @@ -398,6 +399,144 @@ test_shred_version( void ) { fd_fec_resolver_delete( fd_fec_resolver_leave( r ) ); } +static void +fake_resign( fd_shred_t * shred, + signer_ctx_t * sign_ctx ) { + + uchar variant = shred->variant; + uchar shred_type = fd_shred_type( variant ); + int is_data_shred = fd_shred_is_data( shred_type ); + ulong in_type_idx = fd_ulong_if( is_data_shred, shred->idx - shred->fec_set_idx, shred->code.idx ); + ulong shred_idx = fd_ulong_if( is_data_shred, in_type_idx, in_type_idx + shred->code.data_cnt ); + + ulong tree_depth = fd_shred_merkle_cnt( variant ); /* In [0, 15] */ + ulong reedsol_protected_sz = 1115UL + FD_SHRED_DATA_HEADER_SZ - FD_SHRED_SIGNATURE_SZ - FD_SHRED_MERKLE_NODE_SZ*tree_depth + - FD_SHRED_MERKLE_ROOT_SZ*fd_shred_is_chained ( shred_type ) + - FD_SHRED_SIGNATURE_SZ *fd_shred_is_resigned( shred_type); /* In [743, 1139] conservatively*/ + ulong data_merkle_protected_sz = reedsol_protected_sz + FD_SHRED_MERKLE_ROOT_SZ*fd_shred_is_chained ( shred_type ); + ulong parity_merkle_protected_sz = reedsol_protected_sz + FD_SHRED_MERKLE_ROOT_SZ*fd_shred_is_chained ( shred_type )+0x59UL-0x40UL; + ulong merkle_protected_sz = fd_ulong_if( is_data_shred, data_merkle_protected_sz, parity_merkle_protected_sz ); + + uchar bmtree_mem[ fd_bmtree_commit_footprint( 10UL ) ] __attribute__((aligned(FD_BMTREE_COMMIT_ALIGN))); + fd_bmtree_node_t root[1]; + fd_bmtree_node_t leaf[1]; + fd_bmtree_hash_leaf( leaf, (uchar const *)shred + sizeof(fd_ed25519_sig_t), merkle_protected_sz, FD_BMTREE_LONG_PREFIX_SZ ); + fd_shred_merkle_t const * proof = fd_shred_merkle_nodes( shred ); + fd_bmtree_commit_t * tree; + tree = fd_bmtree_commit_init( bmtree_mem, FD_SHRED_MERKLE_NODE_SZ, FD_BMTREE_LONG_PREFIX_SZ, 10UL ); + int rv = fd_bmtree_commitp_insert_with_proof( tree, shred_idx, leaf, (uchar const *)proof, tree_depth, root ); + FD_TEST( rv ); + + test_signer( sign_ctx, shred->signature, root->hash ); +} + +static void +test_shred_reject( void ) { + signer_ctx_t signer_ctx[ 1 ]; + signer_ctx_init( signer_ctx, test_private_key ); + + FD_TEST( _shredder==fd_shredder_new( _shredder, test_signer, signer_ctx, (ushort)SHRED_VER ) ); + fd_shredder_t * shredder = fd_shredder_join( _shredder ); FD_TEST( shredder ); + + uchar const * pubkey = test_private_key+32UL; + + fd_entry_batch_meta_t meta[1]; + fd_memset( meta, 0, sizeof(fd_entry_batch_meta_t) ); + meta->parent_offset = 1UL; + meta->block_complete = 1; + + FD_TEST( fd_shredder_init_batch( shredder, test_bin, test_bin_sz, 2UL, meta ) ); + + fd_fec_set_t _set[ 1 ]; + fd_fec_set_t out_sets[ 4UL ]; + uchar * ptr = fec_set_memory; + ptr = allocate_fec_set( _set, ptr ); + + for( ulong i=0UL; i<4UL; i++ ) ptr = allocate_fec_set( out_sets+i, ptr ); + + fd_fec_resolver_t * r = fd_fec_resolver_join( fd_fec_resolver_new( res_mem, NULL, NULL, 2UL, 1UL, 1UL, 1UL, out_sets, SHRED_VER, MAX ) ); + + fd_fec_set_t const * out_fec[1]; + fd_shred_t const * out_shred[1]; + +#define SIGN_ACCEPT( shred ) \ + fake_resign( shred, signer_ctx ); \ + FD_TEST( fd_shred_parse( (uchar const *)shred, 2048UL ) ); \ + FD_TEST( FD_FEC_RESOLVER_SHRED_OKAY==fd_fec_resolver_add_shred( r, shred, 2048UL, pubkey, out_fec, out_shred ) ); + +#define SIGN_REJECT( shred ) \ + fake_resign( shred, signer_ctx ); \ + FD_TEST( NULL==fd_shred_parse( (uchar const *)shred, 2048UL ) || \ + FD_FEC_RESOLVER_SHRED_REJECTED==fd_fec_resolver_add_shred( r, shred, 2048UL, pubkey, out_fec, out_shred ) ); + + fd_fec_set_t * set = fd_shredder_next_fec_set( shredder, _set ); + fd_shred_t * shred; + shred = (fd_shred_t *)fd_shred_parse( set->data_shreds[ 0 ], 2048UL ); FD_TEST( shred ); + /* Test basic setup is working. */ + FD_TEST( FD_FEC_RESOLVER_SHRED_OKAY==fd_fec_resolver_add_shred( r, shred, 2048UL, pubkey, out_fec, out_shred ) ); + + shred = (fd_shred_t *)fd_shred_parse( set->data_shreds[ 1 ], 2048UL ); FD_TEST( shred ); + (*(uchar *)fd_shred_data_payload( shred ))++; + /* Data modified but signature not updated */ + FD_TEST( FD_FEC_RESOLVER_SHRED_REJECTED==fd_fec_resolver_add_shred( r, shred, 2048UL, pubkey, out_fec, out_shred ) ); + + /* fake_resign fixed up the signature. */ + SIGN_ACCEPT( shred ); + + shred = (fd_shred_t *)fd_shred_parse( set->data_shreds[ 2 ], 2048UL ); FD_TEST( shred ); + shred->idx = MAX-1UL; shred->fec_set_idx = MAX-20UL; SIGN_ACCEPT( shred ); + shred->idx = MAX; shred->fec_set_idx = MAX-20UL; SIGN_REJECT( shred ); + shred->idx = MAX+1UL; shred->fec_set_idx = MAX-20UL; SIGN_REJECT( shred ); + + shred = (fd_shred_t *)fd_shred_parse( set->data_shreds[ 3 ], 2048UL ); FD_TEST( shred ); + shred->data.flags = 0x80; SIGN_REJECT( shred );/* block complete but not batch complete */ + + shred = (fd_shred_t *)fd_shred_parse( set->data_shreds[ 4 ], 2048UL ); FD_TEST( shred ); + shred->data.parent_off = 2; SIGN_ACCEPT( shred ); /* Slot == 2 */ + shred->data.parent_off = 0; SIGN_REJECT( shred ); + shred->data.parent_off = 3; SIGN_REJECT( shred ); + shred->data.parent_off = 0; shred->slot = 0UL; SIGN_ACCEPT( shred ); + shred->data.parent_off = 1; SIGN_REJECT( shred ); + + shred = (fd_shred_t *)fd_shred_parse( set->data_shreds[ 5 ], 2048UL ); FD_TEST( shred ); + shred->idx = 1U; shred->fec_set_idx = 1U; SIGN_ACCEPT( shred ); + shred->idx = 8U; shred->fec_set_idx = 1U; SIGN_ACCEPT( shred ); + /* The following two are so malformed that fake_resign chokes on them. + No matter, because shred_parse rejects them. */ + shred->idx = 1U; shred->fec_set_idx = 8U; FD_TEST( NULL==fd_shred_parse( (uchar const *)shred, 2048UL ) ); + shred->idx = 7U; shred->fec_set_idx = 8U; FD_TEST( NULL==fd_shred_parse( (uchar const *)shred, 2048UL ) ); + + /* Now parity shred tests */ + shred = (fd_shred_t *)fd_shred_parse( set->parity_shreds[ 0 ], 2048UL ); FD_TEST( shred ); + shred->idx = MAX-2UL; shred->code.code_cnt = 1UL; SIGN_ACCEPT( shred ); + shred->idx = MAX; shred->code.code_cnt = 1UL; SIGN_REJECT( shred ); + shred->idx = MAX+1UL; shred->code.code_cnt = 1UL; SIGN_REJECT( shred ); + + shred = (fd_shred_t *)fd_shred_parse( set->parity_shreds[ 1 ], 2048UL ); FD_TEST( shred ); + shred->code.data_cnt = 68UL; shred->code.code_cnt = 68UL; SIGN_REJECT( shred ); + shred->code.data_cnt = 256UL; shred->code.code_cnt = 256UL; SIGN_REJECT( shred ); + shred->code.data_cnt = 0UL; shred->code.code_cnt = 32UL; SIGN_REJECT( shred ); + shred->code.data_cnt = 32UL; shred->code.code_cnt = 0UL; SIGN_REJECT( shred ); + + shred = (fd_shred_t *)fd_shred_parse( set->parity_shreds[ 2 ], 2048UL ); FD_TEST( shred ); + shred->fec_set_idx = 12U; SIGN_ACCEPT( shred ); + shred->fec_set_idx = MAX-3UL; shred->code.data_cnt = 2UL; SIGN_ACCEPT( shred ); + shred->fec_set_idx = MAX-2UL; shred->code.data_cnt = 3UL; SIGN_REJECT( shred ); + /* This one is also so malformed that fake_resign can't sign it. The + Merkle tree required to sign an FEC set that large wouldn't fit. */ + shred->fec_set_idx = UINT_MAX; shred->code.data_cnt = USHORT_MAX; FD_TEST( NULL==fd_shred_parse( (uchar const *)shred, 2048UL ) ); + + shred = (fd_shred_t *)fd_shred_parse( set->parity_shreds[ 3 ], 2048UL ); FD_TEST( shred ); + shred->idx = shred->code.idx - 1U; SIGN_REJECT( shred ); + shred->idx = MAX-5UL; shred->code.idx = 0; shred->code.code_cnt = 4U; SIGN_ACCEPT( shred ); + shred->idx = MAX-5UL; shred->code.idx = 0; shred->code.code_cnt = 5U; SIGN_REJECT( shred ); + shred->idx = MAX-5UL; shred->code.idx = 1; shred->code.code_cnt = 5U; SIGN_ACCEPT( shred ); + + shred = (fd_shred_t *)fd_shred_parse( set->parity_shreds[ 4 ], 2048UL ); FD_TEST( shred ); + shred->code.idx = 4; shred->code.code_cnt = 5; SIGN_ACCEPT( shred ); + shred->code.idx = 4; shred->code.code_cnt = 4; SIGN_REJECT( shred ); +} + int main( int argc, @@ -412,6 +551,7 @@ main( int argc, test_rolloff(); test_new_formats(); test_shred_version(); + test_shred_reject(); FD_LOG_NOTICE(( "pass" )); diff --git a/src/disco/topo/fd_topo.h b/src/disco/topo/fd_topo.h index c3fda86204..4bbb547a43 100644 --- a/src/disco/topo/fd_topo.h +++ b/src/disco/topo/fd_topo.h @@ -189,6 +189,7 @@ typedef struct { ulong fec_resolver_depth; char identity_key_path[ PATH_MAX ]; ushort shred_listen_port; + int larger_shred_limits_per_block; ulong expected_shred_version; } shred;