1 // Copyright 2020 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 //
15 ////////////////////////////////////////////////////////////////////////////////
16 extern "C" {
17 #include <ngx_config.h>
18 #include <ngx_core.h>
19 #include <ngx_event.h>
20 #include <ngx_http.h>
21 }
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <sys/stat.h>
26 #include <time.h>
27 #include <unistd.h>
28
29 #include "http_request_proto.pb.h"
30 #include "libprotobuf-mutator/src/libfuzzer/libfuzzer_macro.h"
31
32 static char configuration[] =
33 "error_log stderr emerg;\n"
34 "events {\n"
35 " use epoll;\n"
36 " worker_connections 2;\n"
37 " multi_accept off;\n"
38 " accept_mutex off;\n"
39 "}\n"
40 "http {\n"
41 " server_tokens off;\n"
42 " default_type application/octet-stream;\n"
43 " map $http_upgrade $connection_upgrade {\n"
44 " default upgrade;\n"
45 " '' close;\n"
46 " }\n"
47 " error_log stderr emerg;\n"
48 " access_log off;\n"
49 " map $subdomain $nss {\n"
50 " default local_upstream;\n"
51 " }\n"
52 " upstream local_upstream {\n"
53 " server 127.0.0.1:1010 max_fails=0;\n"
54 " server 127.0.0.1:1011 max_fails=0;\n"
55 " server 127.0.0.1:1012 max_fails=0;\n"
56 " server 127.0.0.1:1013 max_fails=0;\n"
57 " server 127.0.0.1:1014 max_fails=0;\n"
58 " server 127.0.0.1:1015 max_fails=0;\n"
59 " server 127.0.0.1:1016 max_fails=0;\n"
60 " server 127.0.0.1:1017 max_fails=0;\n"
61 " server 127.0.0.1:1018 max_fails=0;\n"
62 " server 127.0.0.1:1019 max_fails=0;\n"
63 " }\n"
64 " client_max_body_size 256M;\n"
65 " client_body_temp_path /tmp/;\n"
66 " proxy_temp_path /tmp/;\n"
67 " proxy_buffer_size 24K;\n"
68 " proxy_max_temp_file_size 0;\n"
69 " proxy_buffers 8 4K;\n"
70 " proxy_busy_buffers_size 28K;\n"
71 " proxy_buffering off;\n"
72 " server {\n"
73 " listen unix:nginx.sock;\n"
74 " server_name ~^(?<subdomain>.+)\\.url.com$;\n"
75 " proxy_next_upstream off;\n"
76 " proxy_read_timeout 5m;\n"
77 " proxy_http_version 1.1;\n"
78 " proxy_set_header Host $http_host;\n"
79 " proxy_set_header X-Real-IP $remote_addr;\n"
80 " proxy_set_header X-Real-Port $remote_port;\n"
81 " location / {\n"
82 " proxy_pass http://$nss;\n"
83 " proxy_set_header Host $http_host;\n"
84 " proxy_set_header X-Real-IP $remote_addr;\n"
85 " proxy_set_header X-Real-Port $remote_port;\n"
86 " proxy_set_header Connection '';\n"
87 " chunked_transfer_encoding off;\n"
88 " proxy_buffering off;\n"
89 " proxy_cache off;\n"
90 " }\n"
91 " }\n"
92 "}\n"
93 "\n";
94
95
96 static ngx_cycle_t *cycle;
97 static ngx_log_t ngx_log;
98 static ngx_open_file_t ngx_log_file;
99 static char *my_argv[2];
100 static char arg1[] = {0, 0xA, 0};
101
102 extern char **environ;
103
104 static const char *config_file = "/tmp/http_config.conf";
105
106 struct fuzzing_data {
107 const uint8_t *data;
108 size_t data_len;
109 };
110
111 static struct fuzzing_data request;
112 static struct fuzzing_data reply;
113
114 static ngx_http_upstream_t *upstream;
115 static ngx_http_request_t *req_reply;
116 static ngx_http_cleanup_t cln_new = {};
117 static int cln_added;
118
119 // Called when finalizing the request to upstream
120 // Do not need to clean the request pool
cleanup_reply(void * data)121 static void cleanup_reply(void *data) { req_reply = NULL; }
122
123 // Called by the http parser to read the buffer
request_recv_handler(ngx_connection_t * c,u_char * buf,size_t size)124 static ssize_t request_recv_handler(ngx_connection_t *c, u_char *buf,
125 size_t size) {
126 if (request.data_len < size)
127 size = request.data_len;
128 memcpy(buf, request.data, size);
129 request.data += size;
130 request.data_len -= size;
131 return size;
132 }
133
134 // Feed fuzzing input for the reply from upstream
reply_recv_handler(ngx_connection_t * c,u_char * buf,size_t size)135 static ssize_t reply_recv_handler(ngx_connection_t *c, u_char *buf,
136 size_t size) {
137 req_reply = (ngx_http_request_t *)(c->data);
138 if (!cln_added) { // add cleanup so that we know whether everything is cleanup
139 // correctly
140 cln_added = 1;
141 cln_new.handler = cleanup_reply;
142 cln_new.next = req_reply->cleanup;
143 cln_new.data = NULL;
144 req_reply->cleanup = &cln_new;
145 }
146 upstream = req_reply->upstream;
147
148 if (reply.data_len < size)
149 size = reply.data_len;
150 memcpy(buf, reply.data, size);
151 reply.data += size;
152 reply.data_len -= size;
153 if (size == 0)
154 c->read->ready = 0;
155 return size;
156 }
157
add_event(ngx_event_t * ev,ngx_int_t event,ngx_uint_t flags)158 static ngx_int_t add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags) {
159 return NGX_OK;
160 }
161
init_event(ngx_cycle_t * cycle,ngx_msec_t timer)162 static ngx_int_t init_event(ngx_cycle_t *cycle, ngx_msec_t timer) {
163 return NGX_OK;
164 }
165
166 // Used when sending data, do nothing
send_chain(ngx_connection_t * c,ngx_chain_t * in,off_t limit)167 static ngx_chain_t *send_chain(ngx_connection_t *c, ngx_chain_t *in,
168 off_t limit) {
169 c->read->ready = 1;
170 c->recv = reply_recv_handler;
171 return in->next;
172 }
173
174 // Create a base state for Nginx without starting the server
InitializeNginx(void)175 extern "C" int InitializeNginx(void) {
176 ngx_log_t *log;
177 ngx_cycle_t init_cycle;
178
179 if (access("nginx.sock", F_OK) != -1) {
180 remove("nginx.sock");
181 }
182
183 ngx_debug_init();
184 ngx_strerror_init();
185 ngx_time_init();
186 ngx_regex_init();
187
188 // Just output logs to stderr
189 ngx_log.file = &ngx_log_file;
190 ngx_log.log_level = NGX_LOG_EMERG;
191 ngx_log_file.fd = ngx_stderr;
192 log = &ngx_log;
193
194 ngx_memzero(&init_cycle, sizeof(ngx_cycle_t));
195 init_cycle.log = log;
196 ngx_cycle = &init_cycle;
197
198 init_cycle.pool = ngx_create_pool(1024, log);
199
200 // Set custom argv/argc
201 my_argv[0] = arg1;
202 my_argv[1] = NULL;
203 ngx_argv = ngx_os_argv = my_argv;
204 ngx_argc = 0;
205
206 // Weird trick to free a leaking buffer always caught by ASAN
207 // We basically let ngx overwrite the environment variable, free the leak and
208 // restore the environment as before.
209 char *env_before = environ[0];
210 environ[0] = my_argv[0] + 1;
211 ngx_os_init(log);
212 free(environ[0]);
213 environ[0] = env_before;
214
215 ngx_crc32_table_init();
216 ngx_preinit_modules();
217
218 FILE *fptr = fopen(config_file, "w");
219 fprintf(fptr, "%s", configuration);
220 fclose(fptr);
221 init_cycle.conf_file.len = strlen(config_file);
222 init_cycle.conf_file.data = (unsigned char *) config_file;
223
224 cycle = ngx_init_cycle(&init_cycle);
225
226 ngx_os_status(cycle->log);
227 ngx_cycle = cycle;
228
229 ngx_event_actions.add = add_event;
230 ngx_event_actions.init = init_event;
231 ngx_io.send_chain = send_chain;
232 ngx_event_flags = 1;
233 ngx_queue_init(&ngx_posted_accept_events);
234 ngx_queue_init(&ngx_posted_next_events);
235 ngx_queue_init(&ngx_posted_events);
236 ngx_event_timer_init(cycle->log);
237 return 0;
238 }
239
invalid_call(ngx_connection_s * a,ngx_chain_s * b,long int c)240 extern "C" long int invalid_call(ngx_connection_s *a, ngx_chain_s *b,
241 long int c) {
242 return 0;
243 }
244
DEFINE_PROTO_FUZZER(const HttpProto & input)245 DEFINE_PROTO_FUZZER(const HttpProto &input) {
246 static int init = InitializeNginx();
247 assert(init == 0);
248
249 // have two free connections, one for client, one for upstream
250 ngx_event_t read_event1 = {};
251 ngx_event_t write_event1 = {};
252 ngx_connection_t local1 = {};
253 ngx_event_t read_event2 = {};
254 ngx_event_t write_event2 = {};
255 ngx_connection_t local2 = {};
256 ngx_connection_t *c;
257 ngx_listening_t *ls;
258
259 req_reply = NULL;
260 upstream = NULL;
261 cln_added = 0;
262
263 const char *req_string = input.request().c_str();
264 size_t req_len = strlen(req_string);
265 const char *rep_string = input.reply().c_str();
266 size_t rep_len = strlen(rep_string);
267 request.data = (const uint8_t *)req_string;
268 request.data_len = req_len;
269 reply.data = (const uint8_t *)rep_string;
270 reply.data_len = rep_len;
271
272 // Use listening entry created from configuration
273 ls = (ngx_listening_t *)ngx_cycle->listening.elts;
274
275 // Fake event ready for dispatch on read
276 local1.read = &read_event1;
277 local1.write = &write_event1;
278 local2.read = &read_event2;
279 local2.write = &write_event2;
280 local2.send_chain = send_chain;
281
282 // Create fake free connection to feed the http handler
283 ngx_cycle->free_connections = &local1;
284 local1.data = &local2;
285 ngx_cycle->free_connection_n = 2;
286
287 // Initialize connection
288 c = ngx_get_connection(
289 255, &ngx_log); // 255 - (hopefully unused) socket descriptor
290
291 c->shared = 1;
292 c->destroyed = 0;
293 c->type = SOCK_STREAM;
294 c->pool = ngx_create_pool(256, ngx_cycle->log);
295 c->sockaddr = ls->sockaddr;
296 c->listening = ls;
297 c->recv = request_recv_handler; // Where the input will be read
298 c->send_chain = send_chain;
299 c->send = (ngx_send_pt)invalid_call;
300 c->recv_chain = (ngx_recv_chain_pt)invalid_call;
301 c->log = &ngx_log;
302 c->pool->log = &ngx_log;
303 c->read->log = &ngx_log;
304 c->write->log = &ngx_log;
305 c->socklen = ls->socklen;
306 c->local_sockaddr = ls->sockaddr;
307 c->local_socklen = ls->socklen;
308 c->data = NULL;
309
310 read_event1.ready = 1;
311 write_event1.ready = write_event1.delayed = 1;
312
313 // Will redirect to http parser
314 ngx_http_init_connection(c);
315
316 // We do not provide working timers or events, and thus we have to manually
317 // clean up the requests we created. We do this here.
318 // Cross-referencing: https://trac.nginx.org/nginx/ticket/2080#no1).I
319 // This is a fix that should be bettered in the future, by creating proper
320 // timers and events.
321 if (c->destroyed != 1) {
322 if (c->read->data != NULL) {
323 ngx_connection_t *c2 = (ngx_connection_t*)c->read->data;
324 ngx_http_free_request((ngx_http_request_t*)c2->data, 0);
325 }
326 ngx_http_close_connection(c);
327 }
328 }
329