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