Coverage Report

Created: 2025-06-22 06:18

/src/h2o/fuzz/driver.cc
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2016 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 <string.h>
30
#include <errno.h>
31
#include <limits.h>
32
#include <netinet/in.h>
33
#include <netinet/tcp.h>
34
#include <signal.h>
35
#include <stdio.h>
36
#include <stdlib.h>
37
#include <sys/socket.h>
38
#include <sys/stat.h>
39
#include <sys/select.h>
40
#include <sys/wait.h>
41
#include <unistd.h>
42
#include <fcntl.h>
43
44
#include "h2o.h"
45
#include "h2o/http1.h"
46
#include "h2o/http2.h"
47
#include "h2o/url.h"
48
#include "h2o/memcached.h"
49
50
#include "driver_common.h"
51
52
#if !defined(HTTP1) && !defined(HTTP2)
53
#error "Please defined one of HTTP1 or HTTP2"
54
#endif
55
56
#if defined(HTTP1) && defined(HTTP2)
57
#error "Please defined one of HTTP1 or HTTP2, but not both"
58
#endif
59
60
static h2o_globalconf_t config;
61
static h2o_context_t ctx;
62
static h2o_accept_ctx_t accept_ctx;
63
static int client_timeout_ms;
64
static char unix_listener[PATH_MAX];
65
66
/*
67
 * Request handler used for testing. Returns a basic "200 OK" response.
68
 */
69
static int chunked_test(h2o_handler_t *self, h2o_req_t *req)
70
31
{
71
31
    static h2o_generator_t generator = {NULL, NULL};
72
73
31
    if (!h2o_memis(req->method.base, req->method.len, H2O_STRLIT("GET")))
74
4
        return -1;
75
76
27
    h2o_iovec_t body = h2o_strdup(&req->pool, "hello world\n", SIZE_MAX);
77
27
    req->res.status = 200;
78
27
    req->res.reason = "OK";
79
27
    h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, NULL, H2O_STRLIT("text/plain"));
80
27
    h2o_start_response(req, &generator);
81
27
    h2o_send(req, &body, 1, H2O_SEND_STATE_FINAL);
82
83
27
    return 0;
84
31
}
85
86
/* copy from src to dst, return true if src has EOF */
87
static int drain(int fd)
88
17.8k
{
89
17.8k
    char buf[4096];
90
17.8k
    ssize_t n;
91
92
17.8k
    n = read(fd, buf, sizeof(buf));
93
17.8k
    if (n <= 0) {
94
7.97k
        return 1;
95
7.97k
    }
96
9.86k
    return 0;
97
17.8k
}
98
99
/* A request sent from client thread to h2o server */
100
struct writer_thread_arg {
101
    char *buf;
102
    size_t len;
103
    int fd;
104
    h2o_barrier_t barrier;
105
};
106
107
/*
108
 * Reads writer_thread_arg from fd and stores to buf
109
 */
110
static void read_fully(int fd, char *buf, size_t len)
111
8.45k
{
112
8.45k
    int done = 0;
113
16.9k
    while (len) {
114
8.45k
        int ret;
115
8.45k
        while ((ret = read(fd, buf + done, len)) == -1 && errno == EINTR)
116
0
            ;
117
8.45k
        if (ret <= 0) {
118
0
            abort();
119
0
        }
120
8.45k
        done += ret;
121
8.45k
        len -= ret;
122
8.45k
    }
123
8.45k
}
124
125
/*
126
 * Thread: Loops writing fuzzed req to socket and then reading results back.
127
 * Acts as a client to h2o. *arg points to file descripter to read
128
 * writer_thread_args from.
129
 */
130
void *writer_thread(void *arg)
131
1
{
132
1
    int rfd = (long)arg;
133
8.45k
    while (1) {
134
8.45k
        int pos, sockinp, sockoutp, cnt, len;
135
8.45k
        char *buf;
136
8.45k
        struct writer_thread_arg *wta;
137
138
        /* Get fuzzed request */
139
8.45k
        read_fully(rfd, (char *)&wta, sizeof(wta));
140
141
8.45k
        pos = 0;
142
8.45k
        sockinp = wta->fd;
143
8.45k
        sockoutp = wta->fd;
144
8.45k
        cnt = 0;
145
8.45k
        buf = wta->buf;
146
8.45k
        len = wta->len;
147
148
        /*
149
         * Send fuzzed req and read results until the socket is closed (or
150
         * something spurious happens)
151
         */
152
40.5k
        while (cnt++ < 20 && (pos < len || sockinp >= 0)) {
153
59.7k
#define MARKER "\n--MARK--\n"
154
            /* send 1 packet */
155
32.0k
            if (pos < len) {
156
24.4k
                char *p = (char *)memmem(buf + pos, len - pos, MARKER, sizeof(MARKER) - 1);
157
24.4k
                if (p) {
158
10.8k
                    int l = p - (buf + pos);
159
10.8k
                    write(sockoutp, buf + pos, l);
160
10.8k
                    pos += l;
161
10.8k
                    pos += sizeof(MARKER) - 1;
162
10.8k
                }
163
24.4k
            } else {
164
7.58k
                if (sockinp >= 0) {
165
7.58k
                    shutdown(sockinp, SHUT_WR);
166
7.58k
                }
167
7.58k
            }
168
169
            /* drain socket */
170
32.0k
            if (sockinp >= 0) {
171
26.6k
                struct timeval timeo;
172
26.6k
                fd_set rd;
173
26.6k
                int n;
174
175
26.6k
                FD_ZERO(&rd);
176
26.6k
                FD_SET(sockinp, &rd);
177
26.6k
                timeo.tv_sec = 0;
178
26.6k
                timeo.tv_usec = client_timeout_ms * 1000;
179
26.6k
                n = select(sockinp + 1, &rd, NULL, NULL, &timeo);
180
26.6k
                if (n > 0 && FD_ISSET(sockinp, &rd) && drain(sockinp)) {
181
7.97k
                    sockinp = -1;
182
7.97k
                }
183
26.6k
            }
184
32.0k
        }
185
8.45k
        close(wta->fd);
186
8.45k
        h2o_barrier_wait(&wta->barrier);
187
8.45k
        h2o_barrier_dispose(&wta->barrier);
188
8.45k
        free(wta);
189
8.45k
    }
190
1
}
191
192
/*
193
 * Creates socket pair and passes fuzzed req to a thread (the HTTP[/2] client)
194
 * for writing to the target h2o server. Returns the server socket fd.
195
 */
196
static int feeder(int sfd, char *buf, size_t len, h2o_barrier_t **barrier)
197
8.44k
{
198
8.44k
    int pair[2];
199
8.44k
    struct writer_thread_arg *wta;
200
201
8.44k
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1)
202
0
        return -1;
203
204
8.44k
    wta = (struct writer_thread_arg *)malloc(sizeof(*wta));
205
8.44k
    wta->fd = pair[0];
206
8.44k
    wta->buf = buf;
207
8.44k
    wta->len = len;
208
8.44k
    h2o_barrier_init(&wta->barrier, 2);
209
8.44k
    *barrier = &wta->barrier;
210
211
8.44k
    write_fully(sfd, (char *)&wta, sizeof(wta), 1);
212
8.44k
    return pair[1];
213
8.44k
}
214
215
/*
216
 * Creates/connects socket pair for client/server interaction and passes
217
 * fuzzed request to client for sending.
218
 * Returns server socket fd.
219
 */
220
static int create_accepted(int sfd, char *buf, size_t len, h2o_barrier_t **barrier)
221
8.44k
{
222
8.44k
    int fd;
223
8.44k
    h2o_socket_t *sock;
224
8.44k
    struct timeval connected_at = h2o_gettimeofday(ctx.loop);
225
226
    /* Create an HTTP[/2] client that will send the fuzzed request */
227
8.44k
    fd = feeder(sfd, buf, len, barrier);
228
8.44k
    if (fd < 0) {
229
0
        abort();
230
0
    }
231
232
    /* Pass the server socket to h2o and invoke request processing */
233
8.44k
    sock = h2o_evloop_socket_create(ctx.loop, fd, H2O_SOCKET_FLAG_IS_ACCEPTED_CONNECTION);
234
235
#if defined(HTTP1)
236
    h2o_http1_accept(&accept_ctx, sock, connected_at);
237
#else
238
8.44k
    h2o_http2_accept(&accept_ctx, sock, connected_at);
239
8.44k
#endif
240
241
8.44k
    return fd;
242
8.44k
}
243
244
/*
245
 * Returns true if fd if valid. Used to determine when connection is closed.
246
 */
247
static int is_valid_fd(int fd)
248
32.0k
{
249
32.0k
    return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
250
32.0k
}
251
252
/*
253
 * Entry point for libfuzzer.
254
 * See http://llvm.org/docs/LibFuzzer.html for more info
255
 */
256
static int init_done;
257
static int job_queue[2];
258
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size)
259
8.44k
{
260
8.44k
    int c;
261
8.44k
    h2o_loop_t *loop;
262
8.44k
    h2o_hostconf_t *hostconf;
263
8.44k
    pthread_t twriter;
264
8.44k
    pthread_t tupstream;
265
266
    /*
267
     * Perform one-time initialization
268
     */
269
8.44k
    if (!init_done) {
270
1
        const char *client_timeout_ms_str;
271
1
        static char tmpname[] = "/tmp/h2o-fuzz-XXXXXX";
272
1
        char *dirname;
273
274
1
        h2o_barrier_init(&init_barrier, 2);
275
1
        signal(SIGPIPE, SIG_IGN);
276
277
1
        dirname = mkdtemp(tmpname);
278
1
        snprintf(unix_listener, sizeof(unix_listener), "http://[unix://%s/_.sock]/proxy", dirname);
279
1
        if ((client_timeout_ms_str = getenv("H2O_FUZZER_CLIENT_TIMEOUT")) != NULL)
280
0
            client_timeout_ms = atoi(client_timeout_ms_str);
281
1
        if (!client_timeout_ms)
282
1
            client_timeout_ms = 10;
283
284
        /* Create a single h2o host with multiple request handlers */
285
1
        h2o_config_init(&config);
286
1
        config.http2.idle_timeout = 10 * 1000;
287
1
        config.http1.req_timeout = 10 * 1000;
288
1
        hostconf = h2o_config_register_host(&config, h2o_iovec_init(H2O_STRLIT(unix_listener)), 65535);
289
1
        register_handler(hostconf, "/chunked-test", chunked_test);
290
1
        register_proxy(hostconf, unix_listener, NULL);
291
1
        h2o_file_register(h2o_config_register_path(hostconf, "/", 0), "./examples/doc_root", NULL, NULL, 0);
292
293
1
        loop = h2o_evloop_create();
294
1
        h2o_context_init(&ctx, loop, &config);
295
296
1
        accept_ctx.ctx = &ctx;
297
1
        accept_ctx.hosts = config.hosts;
298
299
        /* Create a thread to act as the HTTP client */
300
1
        if (socketpair(AF_UNIX, SOCK_STREAM, 0, job_queue) != 0) {
301
0
            abort();
302
0
        }
303
1
        if (pthread_create(&twriter, NULL, writer_thread, (void *)(long)job_queue[1]) != 0) {
304
0
            abort();
305
0
        }
306
1
        if (pthread_create(&tupstream, NULL, upstream_thread, dirname) != 0) {
307
0
            abort();
308
0
        }
309
1
        h2o_barrier_wait(&init_barrier);
310
1
        init_done = 1;
311
1
    }
312
313
    /*
314
     * Pass fuzzed request to client thread and get h2o server socket for
315
     * use below
316
     */
317
8.44k
    h2o_barrier_t *end;
318
8.44k
    c = create_accepted(job_queue[0], (char *)Data, (size_t)Size, &end);
319
8.44k
    if (c < 0) {
320
0
        goto Error;
321
0
    }
322
323
    /* Loop until the connection is closed by the client or server */
324
32.0k
    while (is_valid_fd(c)) {
325
23.5k
        h2o_evloop_run(ctx.loop, client_timeout_ms);
326
23.5k
    }
327
328
8.44k
    h2o_barrier_wait(end);
329
8.44k
    return 0;
330
0
Error:
331
0
    return 1;
332
8.44k
}