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 : }
|