/********************************************************/ /* */ /* FUZZ TEST for civetweb.c */ /* */ /* Copyright (c) 2015-2020 the CivetWeb developers */ /* */ /* This file contains test code for fuzz tests. */ /* It should not be used in production code. */ /* */ /********************************************************/ #include "civetweb.h" #include #include #include #include #if defined(_WIN32) #error "Currently not supported" #else #include #define test_sleep(x) (sleep(x)) #include #include #include #include #include #include typedef int SOCKET; #define closesocket(a) (close(a)) #endif // not _WIN32 /* Port configuration */ unsigned short PORT_NUM_HTTP = 0; /* set dynamically */ #define TESTabort() \ { \ fprintf(stderr, "!!! Precondition in test environment not met !!!\n"); \ fprintf(stderr, "!!! aborting fuzz test in line %u !!!", __LINE__); \ abort(); \ } /********************************************************/ /* Init CivetWeb server ... test with mock client */ /********************************************************/ #if defined(TEST_FUZZ1) || defined(TEST_FUZZ2) static struct mg_context *ctx = 0; static const char *OPTIONS[] = {"listening_ports", "0", /* port: auto */ "document_root", "fuzztest/docroot", NULL, NULL}; static void civetweb_exit(void) { printf("CivetWeb server exit\n"); mg_stop(ctx); ctx = 0; test_sleep(5); } static void civetweb_init(void) { struct mg_callbacks callbacks; struct mg_server_port ports[8]; memset(&callbacks, 0, sizeof(callbacks)); memset(&ports, 0, sizeof(ports)); ctx = mg_start(&callbacks, 0, OPTIONS); if (!ctx) { fprintf(stderr, "\nCivetWeb test server failed to start\n"); TESTabort(); } int ret = mg_get_server_ports(ctx, 8, ports); if (ret != 1) { fprintf(stderr, "\nCivetWeb test server: cannot determine port number\n"); TESTabort(); } if (ports[0].is_ssl != 0) { fprintf(stderr, "\nCivetWeb fuzz test works on HTTP, not HTTPS.\n" "TLS library should be fuzzed separately.\n"); TESTabort(); } PORT_NUM_HTTP = ports[0].port; printf("CivetWeb server running on port %i\n", (int)PORT_NUM_HTTP); /* Give server 5 seconds to start, before flooding with requests. * Don't know if this is required for fuzz-tests, but it was helpful * when testing starting/stopping the server multiple times in test * container environments. */ test_sleep(5); atexit(civetweb_exit); } int LLVMFuzzerInitialize(int *argc, char ***argv); int LLVMFuzzerInitialize(int *argc, char ***argv) { // Silence unused args warning. (void)(argc); (void)(argv); civetweb_init(); return 0; } #if defined(TEST_FUZZ1) static int test_civetweb_client(const char *server, uint16_t port, int use_ssl, const char *uri) { /* Client var */ struct mg_connection *client; char client_err_buf[256]; char client_data_buf[4096]; const struct mg_response_info *client_ri; int64_t data_read; int r; client = mg_connect_client( server, port, use_ssl, client_err_buf, sizeof(client_err_buf)); if ((client == NULL) || (0 != strcmp(client_err_buf, ""))) { fprintf(stderr, "%s connection to server [%s] port [%u] failed: [%s]\n", use_ssl ? "HTTPS" : "HTTP", server, port, client_err_buf); if (client) { mg_close_connection(client); } /* In heavy fuzz testing, sometimes we run out of available sockets. * Wait for some seconds, and retry. */ test_sleep(5); /* retry once */ client = mg_connect_client( server, port, use_ssl, client_err_buf, sizeof(client_err_buf)); if (!client) { fprintf(stderr, "Retry: error\n"); return 1; } fprintf(stderr, "Retry: success\n"); } mg_printf(client, "GET %s HTTP/1.0\r\n\r\n", uri); r = mg_get_response(client, client_err_buf, sizeof(client_err_buf), 10000); if ((r < 0) || (0 != strcmp(client_err_buf, ""))) { mg_close_connection(client); return 1; } client_ri = mg_get_response_info(client); if (client_ri == NULL) { mg_close_connection(client); return 1; } data_read = 0; while (data_read < client_ri->content_length) { /* store the first sizeof(client_data_buf) bytes * of the HTTP response. */ r = mg_read(client, client_data_buf + data_read, sizeof(client_data_buf) - (size_t)data_read); if (r > 0) { data_read += r; } /* buffer filled? */ if (sizeof(client_data_buf) == (size_t)data_read) { /* ignore the rest */ while (r > 0) { char trash[1024]; r = mg_read(client, trash, sizeof(trash)); } break; } } /* Nothing left to read */ r = mg_read(client, client_data_buf, sizeof(client_data_buf)); if (r != 0) { mg_close_connection(client); return 1; } mg_close_connection(client); return 0; } static int LLVMFuzzerTestOneInput_URI(const uint8_t *data, size_t size) { static char URI[1024 * 64]; /* static, to avoid stack overflow */ if (size + 1 < sizeof(URI)) { memcpy(URI, data, size); URI[size] = 0; } else { return 1; } return test_civetweb_client("127.0.0.1", PORT_NUM_HTTP, 0, URI); } #endif #if defined(TEST_FUZZ2) static int LLVMFuzzerTestOneInput_REQUEST(const uint8_t *data, size_t size) { int r; SOCKET sock = socket(AF_INET, SOCK_STREAM, 6); if (sock == -1) { r = errno; fprintf(stderr, "Error: Cannot create socket [%s]\n", strerror(r)); return 1; } struct sockaddr_in sin; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = inet_addr("127.0.0.1"); sin.sin_port = htons(PORT_NUM_HTTP); r = connect(sock, (struct sockaddr *)&sin, sizeof(sin)); if (r != 0) { r = errno; fprintf(stderr, "Error: Cannot connect [%s]\n", strerror(r)); closesocket(sock); return 1; } char trash[1024]; r = send(sock, data, size, 0); if (r != (int)size) { fprintf(stderr, "Warning: %i bytes sent (TODO: Repeat)\n", r); } int data_read = 0; while ((r = recv(sock, trash, sizeof(trash), 0)) > 0) { data_read += r; }; shutdown(sock, SHUT_RDWR); closesocket(sock); static int max_data_read = 0; if (data_read > max_data_read) { max_data_read = data_read; printf("GOT data: %i\n", data_read); } return 0; } #endif #endif // defined(TEST_FUZZ1) || defined(TEST_FUZZ2) /********************************************************/ /* Init mock server ... test with CivetWeb client */ /********************************************************/ #if defined(TEST_FUZZ3) struct tcp_func_prm { SOCKET sock; }; struct tRESPONSE { char data[4096]; size_t size; } RESPONSE; volatile int mock_server_stop_flag = 0; static void mock_server_exit(void) { printf("MOCK server exit\n"); mock_server_stop_flag = 1; test_sleep(5); } static void * mock_server_thread(void *arg) { char req[1024 * 16]; SOCKET svr = (SOCKET)(-1); /* Get thread parameters and free arg */ { struct tcp_func_prm *ptcp_func_prm = (struct tcp_func_prm *)arg; svr = ptcp_func_prm->sock; free(arg); } mock_server_stop_flag = 0; printf("MOCK server ready, sock %i\n", svr); next_request: while (!mock_server_stop_flag) { struct sockaddr_in cliadr; socklen_t adrlen = sizeof(cliadr); int buf_filled = 0; int req_ready = 0; memset(&cliadr, 0, sizeof(cliadr)); SOCKET cli = accept(svr, (struct sockaddr *)&cliadr, &adrlen); if (cli == -1) { int er = errno; fprintf(stderr, "Error: Accept failed [%s]\n", strerror(er)); test_sleep(1); goto next_request; } /* Read request */ do { int r = recv(cli, req + buf_filled, sizeof(req) - buf_filled - 1, 0); if (r > 0) { buf_filled += r; req[buf_filled] = 0; if (strstr(req, "\r\n\r\n") != NULL) { req_ready = 1; } } else { /* some error */ int er = errno; fprintf(stderr, "Error: Recv failed [%s]\n", strerror(er)); test_sleep(1); goto next_request; } } while (!req_ready); /* Request is complete here. * Now send response */ send(cli, RESPONSE.data, RESPONSE.size, MSG_NOSIGNAL); /* Close connection. */ shutdown(cli, SHUT_RDWR); closesocket(cli); } return 0; } static void mock_server_init(void) { int r; int bind_success = 0; SOCKET sock = socket(AF_INET, SOCK_STREAM, 6); if (sock == -1) { r = errno; fprintf(stderr, "Error: Cannot create socket [%s]\n", strerror(r)); TESTabort(); } for (PORT_NUM_HTTP = 1024; PORT_NUM_HTTP != 0; PORT_NUM_HTTP++) { struct sockaddr_in sin; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = inet_addr("127.0.0.1"); sin.sin_port = htons(PORT_NUM_HTTP); r = bind(sock, (struct sockaddr *)&sin, sizeof(sin)); if (r == 0) { bind_success = 1; break; } r = errno; fprintf(stderr, "Warning: Cannot bind [%s]\n", strerror(r)); } if (!bind_success) { fprintf(stderr, "Error: Cannot bind to any port\n"); closesocket(sock); TESTabort(); } printf("MOCK server running on port %i\n", (int)PORT_NUM_HTTP); r = listen(sock, 128); if (r != 0) { r = errno; fprintf(stderr, "Error: Cannot listen [%s]\n", strerror(r)); closesocket(sock); TESTabort(); } pthread_t thread_id; pthread_attr_t attr; int result; struct tcp_func_prm *thread_prm; thread_prm = (struct tcp_func_prm *)malloc(sizeof(struct tcp_func_prm)); if (!thread_prm) { fprintf(stderr, "Error: Out of memory\n"); closesocket(sock); TESTabort(); } thread_prm->sock = sock; (void)pthread_attr_init(&attr); (void)pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); result = pthread_create(&thread_id, &attr, mock_server_thread, (void *)thread_prm); (void)pthread_attr_destroy(&attr); if (result != 0) { r = errno; fprintf(stderr, "Error: Cannot create thread [%s]\n", strerror(r)); closesocket(sock); TESTabort(); } test_sleep(3); atexit(mock_server_exit); } int LLVMFuzzerInitialize(int *argc, char ***argv); int LLVMFuzzerInitialize(int *argc, char ***argv) { // Silence unused args warning. (void)(argc); (void)(argv); mock_server_init(); return 0; } static int LLVMFuzzerTestOneInput_RESPONSE(const uint8_t *data, size_t size) { if (size > sizeof(RESPONSE.data)) { return 1; } memcpy(RESPONSE.data, data, size); RESPONSE.size = size; char errbuf[256]; struct mg_connection *conn = mg_connect_client( "127.0.0.1", PORT_NUM_HTTP, 0, errbuf, sizeof(errbuf)); if (!conn) { printf("Connect error: %s\n", errbuf); test_sleep(1); return 1; } mg_printf(conn, "GET / HTTP/1.0\r\n\r\n"); int r = mg_get_response(conn, errbuf, sizeof(errbuf), 1000); const struct mg_response_info *ri = mg_get_response_info(conn); (void)r; (void)ri; mg_close_connection(conn); return 0; } #endif // defined(TEST_FUZZ3) /********************************************************/ /* MAIN for fuzztest */ /********************************************************/ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); // warning: no previous prototype for function 'LLVMFuzzerTestOneInput' int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { #if defined(TEST_FUZZ1) /* fuzz target 1: different URI for HTTP/1 server */ LLVMFuzzerTestOneInput_URI(data, size); return 0; #elif defined(TEST_FUZZ2) /* fuzz target 2: different requests for HTTP/1 server */ LLVMFuzzerTestOneInput_REQUEST(data, size); return 0; #elif defined(TEST_FUZZ3) /* fuzz target 3: different responses for HTTP/1 client */ LLVMFuzzerTestOneInput_RESPONSE(data, size); return 0; #elif defined(TEST_FUZZ4) #error "Only useful in HTTP/2 feature branch" /* fuzz target 4: different requests for HTTP/2 server */ LLVMFuzzerTestOneInput_REQUEST_HTTP2(data, size); return 0; #elif defined(TEST_FUZZ5) /* fuzz target 5: calling an internal server test function, * bypassing network sockets */ LLVMFuzzerTestOneInput_process_new_connection(data, size); return 0; #else /* planned targets */ #error "Unknown fuzz target" #endif }