Coverage Report

Created: 2025-08-26 06:35

/src/h2o/fuzz/driver_h3.cc
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2021 Fastly, Inc.
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining a copy
5
 * of this software and associated documentation files (the "Software"), to
6
 * deal in the Software without restriction, including without limitation the
7
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8
 * sell copies of the Software, and to permit persons to whom the Software is
9
 * furnished to do so, subject to the following conditions:
10
 *
11
 * The above copyright notice and this permission notice shall be included in
12
 * all copies or substantial portions of the Software.
13
 *
14
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20
 * IN THE SOFTWARE.
21
 */
22
23
/*
24
 * This file implements a test harness for using h2o with LibFuzzer.
25
 * See http://llvm.org/docs/LibFuzzer.html for more info.
26
 */
27
28
#define H2O_USE_EPOLL 1
29
#include <signal.h>
30
31
#include "driver_common.h"
32
33
#include "picotls.h"
34
#include "quicly.h"
35
#include "quicly_mock.h"
36
#include "picotls/openssl.h"
37
#include "h2o.h"
38
#include "h2o/http3_server.h"
39
#include "h2o/http3_internal.h"
40
#include "h2o/qpack.h"
41
42
static h2o_globalconf_t config;
43
static h2o_context_t ctx;
44
static h2o_accept_ctx_t accept_ctx;
45
static quicly_cid_plaintext_t next_cid;
46
static h2o_http3_server_ctx_t server_ctx;
47
static quicly_context_t qctx = quicly_spec_context;
48
static ptls_context_t ptls_ctx = {
49
    .random_bytes = ptls_openssl_random_bytes,
50
    .cipher_suites = ptls_openssl_cipher_suites,
51
    .get_time = &ptls_get_time,
52
};
53
static h2o_http3_conn_callbacks_t conn_callbacks = H2O_HTTP3_CONN_CALLBACKS;
54
static char unix_listener[PATH_MAX];
55
static int client_timeout_ms;
56
57
static quicly_address_t src_addr;
58
static quicly_address_t dst_addr;
59
60
static void quic_init_context(h2o_quic_ctx_t *ctx, h2o_evloop_t *loop)
61
1
{
62
1
    ctx->loop = loop;
63
1
    ctx->quic = &qctx;
64
1
    ctx->quic->tls = &ptls_ctx;
65
1
    ctx->conns_by_id = kh_init_h2o_quic_idmap();
66
1
    ctx->conns_accepting = kh_init_h2o_quic_acceptmap();
67
1
}
68
69
static bool init_done;
70
static size_t num_connections = 0;
71
72
static void on_destroy_connection(h2o_quic_conn_t *conn)
73
6.68k
{
74
6.68k
    --num_connections;
75
6.68k
    H2O_HTTP3_CONN_CALLBACKS.super.destroy_connection(conn);
76
6.68k
}
77
78
/* record egress activities on stream 0 */
79
/**
80
 * sent a full response?
81
 */
82
static bool sent_response = false;
83
static uint64_t last_sent_off;
84
static size_t last_sent_len;
85
86
static void on_stream_send_cb(mquicly_on_stream_send_t *self, quicly_conn_t *conn, quicly_stream_t *stream, const void *buff,
87
                              uint64_t off, size_t len, int is_fin)
88
21.9k
{
89
    /* at the moment we aren't interested in streams other than 0 */
90
21.9k
    if (stream->stream_id != 0)
91
20.0k
        return;
92
93
1.87k
    last_sent_off = off;
94
1.87k
    last_sent_len = len;
95
96
1.87k
    if (is_fin)
97
1.40k
        sent_response = true;
98
1.87k
}
99
100
static mquicly_on_stream_send_t on_stream_send = {.cb = on_stream_send_cb};
101
102
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size)
103
6.68k
{
104
6.68k
    if (!init_done) {
105
1
        h2o_hostconf_t *hostconf;
106
1
        h2o_access_log_filehandle_t *logfh = NULL;
107
1
        h2o_pathconf_t *pathconf;
108
1
        static char tmpname[] = "/tmp/h2o-fuzz-XXXXXX";
109
1
        char *dirname;
110
1
        pthread_t tupstream;
111
1
        const char *client_timeout_ms_str, *log_access_str;
112
113
1
        h2o_barrier_init(&init_barrier, 2);
114
1
        signal(SIGPIPE, SIG_IGN);
115
116
1
        dirname = mkdtemp(tmpname);
117
1
        snprintf(unix_listener, sizeof(unix_listener), "http://[unix://%s/_.sock]/proxy", dirname);
118
1
        if ((client_timeout_ms_str = getenv("H2O_FUZZER_CLIENT_TIMEOUT")) != NULL)
119
0
            client_timeout_ms = atoi(client_timeout_ms_str);
120
1
        if (!client_timeout_ms)
121
1
            client_timeout_ms = 10;
122
123
1
        if ((log_access_str = getenv("H2O_FUZZER_LOG_ACCESS")) != NULL) {
124
0
            bool log_access = atoi(log_access_str) != 0;
125
0
            if (log_access)
126
0
                logfh = h2o_access_log_open_handle("/dev/stdout", NULL, H2O_LOGCONF_ESCAPE_APACHE);
127
0
        }
128
129
1
        h2o_config_init(&config);
130
1
        hostconf = h2o_config_register_host(&config, h2o_iovec_init(H2O_STRLIT("default")), 65535);
131
132
1
        register_proxy(hostconf, unix_listener, logfh);
133
134
1
        pathconf = h2o_config_register_path(hostconf, "/", 0);
135
1
        h2o_file_register(pathconf, "examples/doc_root", NULL, NULL, 0);
136
1
        if (logfh != NULL)
137
0
            h2o_access_log_register(pathconf, logfh);
138
139
1
        mquicly_context.on_stream_send = &on_stream_send;
140
141
1
        h2o_context_init(&ctx, h2o_evloop_create(), &config);
142
1
        accept_ctx.ctx = &ctx;
143
1
        accept_ctx.hosts = config.hosts;
144
145
1
        server_ctx.super.next_cid = &next_cid;
146
1
        server_ctx.accept_ctx = &accept_ctx;
147
1
        server_ctx.send_retry = 0;
148
1
        server_ctx.qpack.encoder_table_capacity = 4096;
149
150
1
        conn_callbacks.super.destroy_connection = on_destroy_connection;
151
152
1
        ptls_log._generation = 0; /* disable tracing */
153
154
1
        quic_init_context(&server_ctx.super, ctx.loop);
155
1
        h2o_http3_server_amend_quicly_context(&config, server_ctx.super.quic);
156
1
        if (pthread_create(&tupstream, NULL, upstream_thread, dirname) != 0) {
157
0
            abort();
158
0
        }
159
1
        h2o_barrier_wait(&init_barrier);
160
1
        init_done = true;
161
1
    }
162
163
6.68k
    src_addr.sin.sin_family = dst_addr.sin.sin_family = AF_INET;
164
6.68k
    src_addr.sin.sin_addr.s_addr = dst_addr.sin.sin_addr.s_addr = htonl(0x7f000001);
165
6.68k
    src_addr.sin.sin_port = htons(12345);
166
6.68k
    dst_addr.sin.sin_port = htons(8443);
167
168
6.68k
    ++num_connections;
169
6.68k
    sent_response = false;
170
6.68k
    h2o_http3_conn_t *conn = h2o_http3_server_accept(&server_ctx, &dst_addr, &src_addr, NULL /* initial_packet */,
171
6.68k
                                                     NULL /* address_token */, &conn_callbacks);
172
6.68k
    assert(conn != NULL);
173
6.68k
    assert(&conn->super != &h2o_quic_accept_conn_decryption_failed);
174
6.68k
    quicly_stream_t *stream;
175
6.68k
    int ret = mquicly_open_stream(conn->super.quic, &stream, 1 /* remote initiated */, 0 /* bidi */);
176
6.68k
    assert(ret == 0);
177
6.68k
    assert(stream != NULL);
178
179
6.68k
    quicly_recvstate_update(&stream->recvstate, 0, &Size, 1, 63);
180
6.68k
    stream->callbacks->on_receive(stream, 0, Data, Size);
181
182
6.68k
    uint64_t loop_start = h2o_now(ctx.loop);
183
1.88M
    do {
184
1.88M
        if (loop_start + client_timeout_ms < h2o_now(ctx.loop)) {
185
1.08k
            break; /* time up */
186
1.08k
        }
187
1.87M
        uint64_t time_left = loop_start + client_timeout_ms - h2o_now(ctx.loop);
188
1.87M
        h2o_evloop_run(ctx.loop, time_left);
189
1.87M
        if (num_connections == 0) {
190
            /* connection was closed abruptly due to an error in the input */
191
4.20k
            return 0;
192
4.20k
        }
193
194
1.87M
        if (last_sent_len > 0) {
195
            /* simulate ack-receiving event */
196
1.40k
            quicly_sendstate_sent_t sent = {.start = last_sent_off, .end = last_sent_off + last_sent_len};
197
1.40k
            size_t bytes_to_shift;
198
199
1.40k
            last_sent_len = 0;
200
1.40k
            int ret = quicly_sendstate_acked(&stream->sendstate, &sent, &bytes_to_shift);
201
1.40k
            assert(ret == 0);
202
1.40k
            if (bytes_to_shift != 0)
203
926
                stream->callbacks->on_send_shift(stream, bytes_to_shift);
204
1.40k
        }
205
1.87M
    } while (!sent_response);
206
207
2.48k
    mquicly_closed_by_remote(conn->super.quic, 0, 0 /* TODO: frame_type */, ptls_iovec_init(NULL, 0));
208
    /* simulate timer update at the end of process_packets() */
209
2.48k
    h2o_quic_schedule_timer(&conn->super);
210
211
2.48k
    do {
212
2.48k
        h2o_evloop_run(ctx.loop, 1);
213
2.48k
    } while (num_connections != 0);
214
215
2.48k
    return 0;
216
6.68k
}