LCOV - code coverage report
Current view: top level - waltz/h2 - fuzz_h2.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 7 94 7.4 %
Date: 2025-07-01 05:00:49 Functions: 1 12 8.3 %

          Line data    Source code
       1             : /* fuzz_h2.c covers the fd_h2 connection-level APIs.  It attempts to
       2             :    find crashes, spinloops, and other bugs.  */
       3             : 
       4             : #if !FD_HAS_HOSTED
       5             : #error "This target requires FD_HAS_HOSTED"
       6             : #endif
       7             : 
       8             : #include <assert.h>
       9             : #include <stdlib.h>
      10             : 
      11             : #include "fd_h2.h"
      12             : #include "../../util/fd_util.h"
      13             : 
      14             : struct fuzz_h2_ctx {
      15             :   fd_h2_rbuf_t   rbuf_tx[1];
      16             :   fd_h2_conn_t   conn[1];
      17             :   fd_h2_stream_t stream[1];
      18             :   fd_h2_tx_op_t  tx_op[1];
      19             : };
      20             : 
      21             : typedef struct fuzz_h2_ctx fuzz_h2_ctx_t;
      22             : 
      23             : static FD_TL fuzz_h2_ctx_t g_ctx;
      24             : 
      25             : static FD_TL fd_rng_t g_rng[1];
      26             : 
      27             : /* Stream leak detector (detects unbalanced callbacks) */
      28             : static FD_TL long g_stream_cnt;
      29             : 
      30             : /* Double callback detector */
      31             : static FD_TL long g_conn_final_cnt;
      32             : 
      33             : static void
      34           0 : test_response_continue( void ) {
      35           0 :   if( !g_ctx.stream->stream_id ) return;
      36           0 :   fd_h2_tx_op_copy( g_ctx.conn, g_ctx.stream, g_ctx.rbuf_tx, g_ctx.tx_op );
      37           0 :   if( g_ctx.stream->state==FD_H2_STREAM_STATE_CLOSED ) {
      38           0 :     g_stream_cnt--;
      39           0 :     memset( g_ctx.tx_op,  0, sizeof(g_ctx.tx_op ) );
      40           0 :     memset( g_ctx.stream, 0, sizeof(g_ctx.stream) );
      41           0 :   }
      42           0 : }
      43             : 
      44             : static void
      45             : test_response_init( fd_h2_conn_t *   conn,
      46           0 :                     fd_h2_stream_t * stream ) {
      47           0 :   (void)conn;
      48           0 :   uint stream_id = stream->stream_id;
      49             : 
      50           0 :   fd_h2_rbuf_t * rbuf_tx = g_ctx.rbuf_tx;
      51           0 :   uchar hpack[] = { 0x88 /* :status: 200 */ };
      52           0 :   fd_h2_tx( rbuf_tx, hpack, sizeof(hpack), FD_H2_FRAME_TYPE_HEADERS, FD_H2_FLAG_END_HEADERS, stream_id );
      53             : 
      54           0 :   fd_h2_tx_op_t * tx_op = g_ctx.tx_op;
      55           0 :   fd_h2_tx_op_init( tx_op, "Ok", 2UL, FD_H2_FLAG_END_STREAM );
      56           0 :   test_response_continue();
      57           0 : }
      58             : 
      59             : static fd_h2_stream_t *
      60             : cb_stream_create( fd_h2_conn_t * conn,
      61           0 :                   uint           stream_id ) {
      62           0 :   (void)conn; (void)stream_id;
      63           0 :   if( g_ctx.stream->stream_id ) {
      64           0 :     return NULL;
      65           0 :   }
      66           0 :   fd_h2_stream_init( g_ctx.stream );
      67           0 :   g_stream_cnt++;
      68           0 :   return g_ctx.stream;
      69           0 : }
      70             : 
      71             : static fd_h2_stream_t *
      72             : cb_stream_query( fd_h2_conn_t * conn,
      73           0 :                  uint           stream_id ) {
      74           0 :   assert( conn == g_ctx.conn );
      75           0 :   if( g_ctx.stream->stream_id!=stream_id ) return NULL;
      76           0 :   return g_ctx.stream;
      77           0 : }
      78             : 
      79             : static void
      80           0 : cb_conn_established( fd_h2_conn_t * conn ) {
      81           0 :   assert( conn == g_ctx.conn );
      82           0 :   return;
      83           0 : }
      84             : 
      85             : static void
      86             : cb_conn_final( fd_h2_conn_t * conn,
      87             :                uint           h2_err,
      88           0 :                int            closed_by ) {
      89           0 :   assert( conn == g_ctx.conn );
      90           0 :   assert( closed_by==0 || closed_by==1 );
      91           0 :   (void)h2_err;
      92           0 :   g_stream_cnt = 0L;
      93           0 :   g_conn_final_cnt++;
      94           0 :   return;
      95           0 : }
      96             : 
      97             : static void
      98             : cb_headers( fd_h2_conn_t *   conn,
      99             :             fd_h2_stream_t * stream,
     100             :             void const *     data,
     101             :             ulong            data_sz,
     102           0 :             ulong            flags ) {
     103           0 :   fd_hpack_rd_t hpack_rd[1];
     104           0 :   fd_hpack_rd_init( hpack_rd, data, data_sz );
     105           0 :   while( !fd_hpack_rd_done( hpack_rd ) )  {
     106           0 :     static FD_TL uchar scratch_buf[ 4096 ];
     107           0 :     uchar * scratch = scratch_buf;
     108           0 :     fd_h2_hdr_t hdr[1];
     109           0 :     uint err = fd_hpack_rd_next( hpack_rd, hdr, &scratch, scratch_buf+sizeof(scratch_buf) );
     110           0 :     if( FD_UNLIKELY( err ) ) {
     111           0 :       fd_h2_conn_error( conn, err );
     112           0 :       return;
     113           0 :     }
     114           0 :   }
     115           0 :   if( flags & FD_H2_FLAG_END_STREAM ) {
     116           0 :     test_response_init( conn, stream );
     117           0 :   }
     118           0 :   return;
     119           0 : }
     120             : 
     121             : static void
     122             : cb_data( fd_h2_conn_t *   conn,
     123             :          fd_h2_stream_t * stream,
     124             :          void const *     data,
     125             :          ulong            data_sz,
     126           0 :          ulong            flags ) {
     127           0 :   assert( conn == g_ctx.conn );
     128           0 :   (void)stream; (void)data; (void)data_sz; (void)flags;
     129           0 :   if( flags & FD_H2_FLAG_END_STREAM ) {
     130           0 :     test_response_init( conn, stream );
     131           0 :   }
     132           0 :   return;
     133           0 : }
     134             : 
     135             : static void
     136             : cb_rst_stream( fd_h2_conn_t *   conn,
     137             :                fd_h2_stream_t * stream,
     138             :                uint             error_code,
     139           0 :                int              closed_by ) {
     140           0 :   (void)stream; (void)error_code;
     141           0 :   assert( conn == g_ctx.conn );
     142           0 :   assert( closed_by==0 || closed_by==1 );
     143           0 :   memset( &g_ctx.stream, 0, sizeof(fd_h2_stream_t) );
     144           0 :   g_stream_cnt--;
     145           0 :   return;
     146           0 : }
     147             : 
     148             : static void
     149             : cb_window_update( fd_h2_conn_t * conn,
     150           0 :                   uint           increment ) {
     151           0 :   (void)conn; (void)increment;
     152           0 :   return;
     153           0 : }
     154             : 
     155             : static void
     156             : cb_stream_window_update( fd_h2_conn_t *   conn,
     157             :                          fd_h2_stream_t * stream,
     158           0 :                          uint             increment ) {
     159           0 :   (void)conn; (void)stream; (void)increment;
     160           0 :   return;
     161           0 : }
     162             : 
     163             : static fd_h2_callbacks_t fuzz_h2_cb = {
     164             :   .stream_create        = cb_stream_create,
     165             :   .stream_query         = cb_stream_query,
     166             :   .conn_established     = cb_conn_established,
     167             :   .conn_final           = cb_conn_final,
     168             :   .headers              = cb_headers,
     169             :   .data                 = cb_data,
     170             :   .rst_stream           = cb_rst_stream,
     171             :   .window_update        = cb_window_update,
     172             :   .stream_window_update = cb_stream_window_update
     173             : };
     174             : 
     175             : int
     176             : LLVMFuzzerInitialize( int  *   argc,
     177          15 :                       char *** argv ) {
     178             :   /* Set up shell without signal handlers */
     179          15 :   putenv( "FD_LOG_BACKTRACE=0" );
     180          15 :   fd_boot( argc, argv );
     181          15 :   (void)atexit( fd_halt );
     182          15 :   fd_log_level_core_set(1); /* crash on info log */
     183          15 :   return 0;
     184          15 : }
     185             : 
     186             : int
     187             : LLVMFuzzerTestOneInput( uchar const * data,
     188             :                         ulong         size ) {
     189             :   memset( &g_ctx, 0, sizeof(fuzz_h2_ctx_t) );
     190             : 
     191             :   if( size<4 ) return -1;
     192             :   uint seed = FD_LOAD( uint, data );
     193             :   data += 4; size -= 4;
     194             : 
     195             :   fd_rng_t * rng = fd_rng_join( fd_rng_new( g_rng, seed, 0UL ) );
     196             : 
     197             :   uchar buf_rx [ 256 ];
     198             :   uchar buf_tx [ 256 ];
     199             :   uchar scratch[ 256 ];
     200             :   fd_h2_rbuf_t rbuf_rx[1];
     201             :   fd_h2_rbuf_t * rbuf_tx = g_ctx.rbuf_tx;
     202             :   fd_h2_rbuf_init( rbuf_rx, buf_rx, sizeof(buf_rx) );
     203             :   fd_h2_rbuf_init( rbuf_tx, buf_tx, sizeof(buf_tx) );
     204             : 
     205             :   if( seed&1 ) {
     206             :     fd_h2_conn_init_client( g_ctx.conn );
     207             :   } else {
     208             :     fd_h2_conn_init_server( g_ctx.conn );
     209             :   }
     210             :   g_ctx.conn->self_settings.max_frame_size = 256;
     211             : 
     212             :   g_stream_cnt     = 0L;
     213             :   g_conn_final_cnt = 0L;
     214             : 
     215             :   while( size ) {
     216             :     fd_h2_tx_control( g_ctx.conn, rbuf_tx, &fuzz_h2_cb );
     217             :     rbuf_tx->lo_off = rbuf_tx->hi_off;
     218             :     rbuf_tx->lo     = rbuf_tx->hi;
     219             : 
     220             :     if( FD_UNLIKELY( g_ctx.conn->flags & FD_H2_CONN_FLAGS_DEAD ) ) {
     221             :       assert( g_conn_final_cnt==1 );
     222             :       assert( g_stream_cnt==0     );
     223             :       break;
     224             :     }
     225             : 
     226             :     ulong chunk = fd_ulong_min( size, (fd_rng_uint( rng )&15)+1 );
     227             :     if( fd_h2_rbuf_used_sz( rbuf_rx )+chunk > rbuf_rx->bufsz ) break;
     228             :     fd_h2_rbuf_push( rbuf_rx, data, chunk );
     229             :     data += chunk; size -= chunk;
     230             : 
     231             :     fd_h2_rx( g_ctx.conn, rbuf_rx, rbuf_tx, scratch, sizeof(scratch), &fuzz_h2_cb );
     232             :   }
     233             :   fd_h2_tx_control( g_ctx.conn, rbuf_tx, &fuzz_h2_cb );
     234             : 
     235             :   assert( g_stream_cnt>=0 );
     236             :   assert( g_conn_final_cnt==0 || g_conn_final_cnt==1 );
     237             :   if( g_stream_cnt==1 ) {
     238             :     assert( g_ctx.stream->state==FD_H2_STREAM_STATE_OPEN ||
     239             :             g_ctx.stream->state==FD_H2_STREAM_STATE_CLOSING_RX ||
     240             :             g_ctx.stream->state==FD_H2_STREAM_STATE_CLOSING_TX );
     241             :   }
     242             : 
     243             :   fd_rng_delete( fd_rng_leave( rng ) );
     244             :   return 0;
     245             : }

Generated by: LCOV version 1.14