// Copyright 2020 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// extern "C" { #include #include #include #include } #include #include #include #include #include #include #include "http_request_proto.pb.h" #include "libprotobuf-mutator/src/libfuzzer/libfuzzer_macro.h" static char configuration[] = "error_log stderr emerg;\n" "events {\n" " use epoll;\n" " worker_connections 2;\n" " multi_accept off;\n" " accept_mutex off;\n" "}\n" "http {\n" " server_tokens off;\n" " default_type application/octet-stream;\n" " map $http_upgrade $connection_upgrade {\n" " default upgrade;\n" " '' close;\n" " }\n" " error_log stderr emerg;\n" " access_log off;\n" " map $subdomain $nss {\n" " default local_upstream;\n" " }\n" " upstream local_upstream {\n" " server 127.0.0.1:1010 max_fails=0;\n" " server 127.0.0.1:1011 max_fails=0;\n" " server 127.0.0.1:1012 max_fails=0;\n" " server 127.0.0.1:1013 max_fails=0;\n" " server 127.0.0.1:1014 max_fails=0;\n" " server 127.0.0.1:1015 max_fails=0;\n" " server 127.0.0.1:1016 max_fails=0;\n" " server 127.0.0.1:1017 max_fails=0;\n" " server 127.0.0.1:1018 max_fails=0;\n" " server 127.0.0.1:1019 max_fails=0;\n" " }\n" " client_max_body_size 256M;\n" " client_body_temp_path /tmp/;\n" " proxy_temp_path /tmp/;\n" " proxy_buffer_size 24K;\n" " proxy_max_temp_file_size 0;\n" " proxy_buffers 8 4K;\n" " proxy_busy_buffers_size 28K;\n" " proxy_buffering off;\n" " server {\n" " listen unix:nginx.sock;\n" " server_name ~^(?.+)\\.url.com$;\n" " proxy_next_upstream off;\n" " proxy_read_timeout 5m;\n" " proxy_http_version 1.1;\n" " proxy_set_header Host $http_host;\n" " proxy_set_header X-Real-IP $remote_addr;\n" " proxy_set_header X-Real-Port $remote_port;\n" " location / {\n" " proxy_pass http://$nss;\n" " proxy_set_header Host $http_host;\n" " proxy_set_header X-Real-IP $remote_addr;\n" " proxy_set_header X-Real-Port $remote_port;\n" " proxy_set_header Connection '';\n" " chunked_transfer_encoding off;\n" " proxy_buffering off;\n" " proxy_cache off;\n" " }\n" " }\n" "}\n" "\n"; static ngx_cycle_t *cycle; static ngx_log_t ngx_log; static ngx_open_file_t ngx_log_file; static char *my_argv[2]; static char arg1[] = {0, 0xA, 0}; extern char **environ; static const char *config_file = "/tmp/http_config.conf"; struct fuzzing_data { const uint8_t *data; size_t data_len; }; static struct fuzzing_data request; static struct fuzzing_data reply; static ngx_http_upstream_t *upstream; static ngx_http_request_t *req_reply; static ngx_http_cleanup_t cln_new = {}; static int cln_added; // Called when finalizing the request to upstream // Do not need to clean the request pool static void cleanup_reply(void *data) { req_reply = NULL; } // Called by the http parser to read the buffer static ssize_t request_recv_handler(ngx_connection_t *c, u_char *buf, size_t size) { if (request.data_len < size) size = request.data_len; memcpy(buf, request.data, size); request.data += size; request.data_len -= size; return size; } // Feed fuzzing input for the reply from upstream static ssize_t reply_recv_handler(ngx_connection_t *c, u_char *buf, size_t size) { req_reply = (ngx_http_request_t *)(c->data); if (!cln_added) { // add cleanup so that we know whether everything is cleanup // correctly cln_added = 1; cln_new.handler = cleanup_reply; cln_new.next = req_reply->cleanup; cln_new.data = NULL; req_reply->cleanup = &cln_new; } upstream = req_reply->upstream; if (reply.data_len < size) size = reply.data_len; memcpy(buf, reply.data, size); reply.data += size; reply.data_len -= size; if (size == 0) c->read->ready = 0; return size; } static ngx_int_t add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags) { return NGX_OK; } static ngx_int_t init_event(ngx_cycle_t *cycle, ngx_msec_t timer) { return NGX_OK; } // Used when sending data, do nothing static ngx_chain_t *send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) { c->read->ready = 1; c->recv = reply_recv_handler; return in->next; } // Create a base state for Nginx without starting the server extern "C" int InitializeNginx(void) { ngx_log_t *log; ngx_cycle_t init_cycle; if (access("nginx.sock", F_OK) != -1) { remove("nginx.sock"); } ngx_debug_init(); ngx_strerror_init(); ngx_time_init(); ngx_regex_init(); // Just output logs to stderr ngx_log.file = &ngx_log_file; ngx_log.log_level = NGX_LOG_EMERG; ngx_log_file.fd = ngx_stderr; log = &ngx_log; ngx_memzero(&init_cycle, sizeof(ngx_cycle_t)); init_cycle.log = log; ngx_cycle = &init_cycle; init_cycle.pool = ngx_create_pool(1024, log); // Set custom argv/argc my_argv[0] = arg1; my_argv[1] = NULL; ngx_argv = ngx_os_argv = my_argv; ngx_argc = 0; // Weird trick to free a leaking buffer always caught by ASAN // We basically let ngx overwrite the environment variable, free the leak and // restore the environment as before. char *env_before = environ[0]; environ[0] = my_argv[0] + 1; ngx_os_init(log); free(environ[0]); environ[0] = env_before; ngx_crc32_table_init(); ngx_preinit_modules(); FILE *fptr = fopen(config_file, "w"); fprintf(fptr, "%s", configuration); fclose(fptr); init_cycle.conf_file.len = strlen(config_file); init_cycle.conf_file.data = (unsigned char *) config_file; cycle = ngx_init_cycle(&init_cycle); ngx_os_status(cycle->log); ngx_cycle = cycle; ngx_event_actions.add = add_event; ngx_event_actions.init = init_event; ngx_io.send_chain = send_chain; ngx_event_flags = 1; ngx_event_timer_init(cycle->log); return 0; } extern "C" long int invalid_call(ngx_connection_s *a, ngx_chain_s *b, long int c) { return 0; } DEFINE_PROTO_FUZZER(const HttpProto &input) { static int init = InitializeNginx(); assert(init == 0); // have two free connections, one for client, one for upstream ngx_event_t read_event1 = {}; ngx_event_t write_event1 = {}; ngx_connection_t local1 = {}; ngx_event_t read_event2 = {}; ngx_event_t write_event2 = {}; ngx_connection_t local2 = {}; ngx_connection_t *c; ngx_listening_t *ls; req_reply = NULL; upstream = NULL; cln_added = 0; const char *req_string = input.request().c_str(); size_t req_len = strlen(req_string); const char *rep_string = input.reply().c_str(); size_t rep_len = strlen(rep_string); request.data = (const uint8_t *)req_string; request.data_len = req_len; reply.data = (const uint8_t *)rep_string; reply.data_len = rep_len; // Use listening entry created from configuration ls = (ngx_listening_t *)ngx_cycle->listening.elts; // Fake event ready for dispatch on read local1.read = &read_event1; local1.write = &write_event1; local2.read = &read_event2; local2.write = &write_event2; local2.send_chain = send_chain; // Create fake free connection to feed the http handler ngx_cycle->free_connections = &local1; local1.data = &local2; ngx_cycle->free_connection_n = 2; // Initialize connection c = ngx_get_connection( 255, &ngx_log); // 255 - (hopefully unused) socket descriptor c->shared = 1; c->type = SOCK_STREAM; c->pool = ngx_create_pool(256, ngx_cycle->log); c->sockaddr = ls->sockaddr; c->listening = ls; c->recv = request_recv_handler; // Where the input will be read c->send_chain = send_chain; c->send = (ngx_send_pt)invalid_call; c->recv_chain = (ngx_recv_chain_pt)invalid_call; c->log = &ngx_log; c->pool->log = &ngx_log; c->read->log = &ngx_log; c->write->log = &ngx_log; c->socklen = ls->socklen; c->local_sockaddr = ls->sockaddr; c->local_socklen = ls->socklen; read_event1.ready = 1; write_event1.ready = write_event1.delayed = 1; // Will redirect to http parser ngx_http_init_connection(c); }