Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions src/choreo/tower/fd_tower_serdes.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@
} while(0)

static ulong
de_short_u16( ushort * dst, uchar const * src ) {
de_short_u16( ushort * dst,
uchar const * src,
ulong src_sz ) {
if( FD_UNLIKELY( src_sz<1UL ) ) return 0;
if ( FD_LIKELY( !(0x80U & src[0]) ) ) { *dst = (ushort)src[0]; return 1; }
if( FD_UNLIKELY( src_sz<2UL ) ) return 0;
else if( FD_LIKELY( !(0x80U & src[1]) ) ) { *dst = (ushort)((ulong)(src[0]&0x7FUL) + (((ulong)src[1])<<7)); return 2; }
if( FD_UNLIKELY( src_sz<3UL ) ) return 0;
else { *dst = (ushort)((ulong)(src[0]&0x7FUL) + (((ulong)(src[1]&0x7FUL))<<7) + (((ulong)src[2])<<14)); return 3; }
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the 3-byte decode path, src[2] is incorporated without masking/validating and the result is cast to ushort. Inputs where src[2] has bits outside the valid ShortU16 range (e.g. src[2]>3 or src[2]&0x80) will wrap/truncate into an unrelated lockouts_cnt value and can bypass the later max check. Consider enforcing canonical ShortU16 constraints (final byte must terminate and the overall value must fit in ushort).

Suggested change
else { *dst = (ushort)((ulong)(src[0]&0x7FUL) + (((ulong)(src[1]&0x7FUL))<<7) + (((ulong)src[2])<<14)); return 3; }
else {
/* Third byte must terminate (MSB clear) and only use low 2 bits
to keep the value within the 16-bit range and enforce a
canonical ShortU16 encoding. */
if( FD_UNLIKELY( src[2] & 0xFCU ) ) return 0;
ulong val = (ulong)(src[0] & 0x7FUL)
+ (((ulong)(src[1] & 0x7FUL)) << 7)
+ (((ulong)src[2]) << 14);
if( FD_UNLIKELY( val>65535UL ) ) return 0;
*dst = (ushort)val;
return 3;
}

Copilot uses AI. Check for mistakes.
}

Expand Down Expand Up @@ -81,12 +86,15 @@ fd_compact_tower_sync_de( fd_compact_tower_sync_serde_t * serde,
ulong buf_sz ) {
ulong off = 0;
DE( ulong, root );
off += de_short_u16( &serde->lockouts_cnt, buf+off );
ulong n;
n = de_short_u16( &serde->lockouts_cnt, buf+off, buf_sz-off );
if( FD_UNLIKELY( !n ) ) return -1;
off += n;
if( FD_UNLIKELY( serde->lockouts_cnt > FD_TOWER_VOTE_MAX ) ) return -1;
for( ulong i = 0; i < serde->lockouts_cnt; i++ ) {
ulong varint_sz = de_var_int( &serde->lockouts[i].offset, buf+off, buf_sz-off );
if( FD_UNLIKELY( !varint_sz ) ) return -1;
off += varint_sz;
n = de_var_int( &serde->lockouts[i].offset, buf+off, buf_sz-off );
if( FD_UNLIKELY( !n ) ) return -1;
off += n;
DE( uchar, lockouts[i].confirmation_count );
}
DE( fd_hash_t, hash );
Expand Down
5 changes: 2 additions & 3 deletions src/choreo/tower/fuzz_tower_serdes.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@
int
LLVMFuzzerInitialize( int * argc,
char *** argv ) {
/* Set up shell without signal handlers */
putenv( "FD_LOG_BACKTRACE=0" );
fd_boot( argc, argv );
atexit( fd_halt );
fd_log_level_core_set(3); /* crash on warning log */
fd_log_level_core_set(3);
return 0;
}

Expand All @@ -36,7 +35,7 @@ LLVMFuzzerTestOneInput( uchar const * data,

FD_FUZZ_MUST_BE_COVERED;

uchar buf[1024];
uchar buf[4096];
ulong out_sz = 0;

int ser_err = fd_compact_tower_sync_ser( serde, buf, sizeof(buf), &out_sz );
Expand Down
Loading