From b2109327297346a86b35c8b93caf302702e76167 Mon Sep 17 00:00:00 2001 From: Christopher Date: Thu, 2 May 2019 13:24:45 +0200 Subject: [PATCH] Adding fuzz directory and modifications to support it. --- CMakeLists.txt | 5 ++ fuzz/CMakeLists.txt | 24 +++++++ fuzz/README | 2 + fuzz/fuzz_real_query.cc | 28 ++++++++ fuzz/fuzz_stmt_execute.cc | 146 ++++++++++++++++++++++++++++++++++++++ fuzz/fuzz_stmt_fetch.cc | 115 ++++++++++++++++++++++++++++++ fuzz/onefile.cc | 50 +++++++++++++ include/mysql.h | 3 +- include/violite.h | 19 ++++- sql-common/client.cc | 6 ++ vio/CMakeLists.txt | 1 + vio/vio.cc | 24 ++++++- vio/viofuzz.cc | 111 +++++++++++++++++++++++++++++ 13 files changed, 531 insertions(+), 3 deletions(-) create mode 100644 fuzz/CMakeLists.txt create mode 100644 fuzz/README create mode 100644 fuzz/fuzz_real_query.cc create mode 100644 fuzz/fuzz_stmt_execute.cc create mode 100644 fuzz/fuzz_stmt_fetch.cc create mode 100644 fuzz/onefile.cc create mode 100644 vio/viofuzz.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 758df9761e8..837a8c6b071 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -461,6 +461,7 @@ ENDIF() OPTION(DISABLE_SHARED "Don't build shared libraries, compile code as position-dependent" OFF) +OPTION(FUZZING "Fuzzing" OFF) IF(DISABLE_SHARED) MESSAGE("Dynamic plugins are disabled.") ENDIF() @@ -1140,6 +1141,10 @@ IF(NOT WITHOUT_SERVER) ADD_SUBDIRECTORY(sql) ENDIF() +IF (FUZZING) + ADD_SUBDIRECTORY(fuzz) +ENDIF() + # scripts/mysql_config depends on client and server targets loaded above. # It is referenced by some of the directories below, so we insert it here. ADD_SUBDIRECTORY(scripts) diff --git a/fuzz/CMakeLists.txt b/fuzz/CMakeLists.txt new file mode 100644 index 00000000000..f652b97a2d4 --- /dev/null +++ b/fuzz/CMakeLists.txt @@ -0,0 +1,24 @@ +find_library(FUZZINGENGINE_LIB FuzzingEngine) + +if(NOT FUZZINGENGINE_LIB) + MYSQL_ADD_EXECUTABLE(fuzz_real_query fuzz_real_query.cc onefile.cc) + TARGET_LINK_LIBRARIES(fuzz_real_query mysqlclient) + + MYSQL_ADD_EXECUTABLE(fuzz_stmt_fetch fuzz_stmt_fetch.cc onefile.cc) + TARGET_LINK_LIBRARIES(fuzz_stmt_fetch mysqlclient) + + MYSQL_ADD_EXECUTABLE(fuzz_stmt_execute fuzz_stmt_execute.cc onefile.cc) + TARGET_LINK_LIBRARIES(fuzz_stmt_execute mysqlclient) +else() + MYSQL_ADD_EXECUTABLE(fuzz_real_query fuzz_real_query.cc) + TARGET_LINK_LIBRARIES(fuzz_real_query mysqlclient) + TARGET_LINK_LIBRARIES(fuzz_real_query FuzzingEngine) + + MYSQL_ADD_EXECUTABLE(fuzz_stmt_fetch fuzz_stmt_fetch.cc) + TARGET_LINK_LIBRARIES(fuzz_stmt_fetch mysqlclient) + TARGET_LINK_LIBRARIES(fuzz_stmt_fetch FuzzingEngine) + + MYSQL_ADD_EXECUTABLE(fuzz_stmt_execute fuzz_stmt_execute.cc) + TARGET_LINK_LIBRARIES(fuzz_stmt_execute mysqlclient) + TARGET_LINK_LIBRARIES(fuzz_stmt_execute FuzzingEngine) +endif() diff --git a/fuzz/README b/fuzz/README new file mode 100644 index 00000000000..755f613a0b6 --- /dev/null +++ b/fuzz/README @@ -0,0 +1,2 @@ +This is the directory used for fuzzing, intended to be used with oss-fuzz. + diff --git a/fuzz/fuzz_real_query.cc b/fuzz/fuzz_real_query.cc new file mode 100644 index 00000000000..c2e9f31eb94 --- /dev/null +++ b/fuzz/fuzz_real_query.cc @@ -0,0 +1,28 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "violite.h" + +using namespace std; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + MYSQL mysql; + + mysql_init(&mysql); + mysql.options.protocol = MYSQL_PROTOCOL_FUZZ; + // The fuzzing takes place on network data received from server + sock_initfuzz(Data,Size); + if (!mysql_real_connect(&mysql,"localhost","root","root","",0,NULL,0)) { + return 0; + } + + mysql_real_query(&mysql, "SELECT * FROM Cars",(ulong)strlen("SELECT * FROM Cars")); + + mysql_close(&mysql); + return 0; +} diff --git a/fuzz/fuzz_stmt_execute.cc b/fuzz/fuzz_stmt_execute.cc new file mode 100644 index 00000000000..4cb6551a0d8 --- /dev/null +++ b/fuzz/fuzz_stmt_execute.cc @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "violite.h" + +using namespace std; + +#define STRING_SIZE 50 + +#define INSERT_SAMPLE "INSERT INTO \ +test_table(col1,col2,col3) \ +VALUES(?,?,?)" + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + MYSQL mysql; + MYSQL_BIND bind[3]; + my_ulonglong affected_rows; + int param_count; + short small_data; + int int_data; + char str_data[STRING_SIZE]; + unsigned long str_length; + bool is_null; + + mysql_init(&mysql); + mysql.options.protocol = MYSQL_PROTOCOL_FUZZ; + // The fuzzing takes place on network data received from server + sock_initfuzz(Data,Size); + if (!mysql_real_connect(&mysql,"localhost","root","root","",0,NULL,0)) + { + return 0; + } + + MYSQL_STMT *stmt = mysql_stmt_init(&mysql); + if (!stmt) + { + mysql_stmt_close(stmt); + mysql_close(&mysql); + return 0; + } + if (mysql_stmt_prepare(stmt, INSERT_SAMPLE, strlen(INSERT_SAMPLE))) + { + mysql_stmt_close(stmt); + mysql_close(&mysql); + return 0; + } + + param_count= mysql_stmt_param_count(stmt); + + if (param_count != 3) /* validate parameter count */ + { + mysql_stmt_close(stmt); + mysql_close(&mysql); + return 0; + } + + memset(bind, 0, sizeof(bind)); + + /* INTEGER PARAM */ + bind[0].buffer_type= MYSQL_TYPE_LONG; + bind[0].buffer= (char *)&int_data; + bind[0].is_null= 0; + bind[0].length= 0; + + /* STRING PARAM */ + bind[1].buffer_type= MYSQL_TYPE_STRING; + bind[1].buffer= (char *)str_data; + bind[1].buffer_length= STRING_SIZE; + bind[1].is_null= 0; + bind[1].length= &str_length; + + /* SMALLINT PARAM */ + bind[2].buffer_type= MYSQL_TYPE_SHORT; + bind[2].buffer= (char *)&small_data; + bind[2].is_null= &is_null; + bind[2].length= 0; + + /* Bind the buffers */ + if (mysql_stmt_bind_param(stmt, bind)) + { + mysql_stmt_close(stmt); + mysql_close(&mysql); + return 0; + } + + /* Specify the data values for the first row */ + int_data= 10; /* integer */ + strncpy(str_data, "MySQL", STRING_SIZE); /* string */ + str_length= strlen(str_data); + + /* INSERT SMALLINT data as NULL */ + is_null= 1; + + /* Execute the INSERT statement - 1*/ + if (mysql_stmt_execute(stmt)) + { + mysql_stmt_close(stmt); + mysql_close(&mysql); + return 0; + } + + /* Get the number of affected rows */ + affected_rows= mysql_stmt_affected_rows(stmt); + + if (affected_rows != 1) /* validate affected rows */ + { + mysql_stmt_close(stmt); + mysql_close(&mysql); + return 0; + } + + /* Specify data values for second row, + then re-execute the statement */ + int_data= 1000; + strncpy(str_data, "The most popular Open Source database", STRING_SIZE); + str_length= strlen(str_data); + small_data= 1000; /* smallint */ + is_null= 0; /* reset */ + + /* Execute the INSERT statement - 2*/ + if (mysql_stmt_execute(stmt)) + { + mysql_stmt_close(stmt); + mysql_close(&mysql); + return 0; + } + + /* Get the total rows affected */ + affected_rows= mysql_stmt_affected_rows(stmt); + + if (affected_rows != 1) /* validate affected rows */ + { + mysql_stmt_close(stmt); + mysql_close(&mysql); + return 0; + } + + mysql_stmt_close(stmt); + mysql_close(&mysql); + return 0; +} diff --git a/fuzz/fuzz_stmt_fetch.cc b/fuzz/fuzz_stmt_fetch.cc new file mode 100644 index 00000000000..fea90a6afb1 --- /dev/null +++ b/fuzz/fuzz_stmt_fetch.cc @@ -0,0 +1,115 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "violite.h" + +using namespace std; + +#define STRING_SIZE 50 + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + MYSQL mysql; + MYSQL_BIND bind[4]; + MYSQL_RES *prepare_meta_result; + MYSQL_TIME ts; + unsigned long length[4]; + int param_count, column_count, row_count; + short small_data; + int int_data; + char str_data[STRING_SIZE]; + bool is_null[4]; + bool error[4]; + + mysql_init(&mysql); + mysql.options.protocol = MYSQL_PROTOCOL_FUZZ; + // The fuzzing takes place on network data received from server + sock_initfuzz(Data,Size); + if (!mysql_real_connect(&mysql,"localhost","root","root","",0,NULL,0)) + { + return 0; + } + + MYSQL_STMT *stmt = mysql_stmt_init(&mysql); + if (!stmt) + { + mysql_stmt_close(stmt); + mysql_close(&mysql); + return 0; + } + if (mysql_stmt_prepare(stmt, "SELECT col1, col2, col3, col4 FROM Cars",(ulong)strlen("SELECT col1, col2, col3, col4 FROM Cars"))) + { + mysql_stmt_close(stmt); + mysql_close(&mysql); + return 0; + } + prepare_meta_result = mysql_stmt_result_metadata(stmt); + if (!prepare_meta_result) + { + mysql_stmt_close(stmt); + mysql_close(&mysql); + return 0; + } + + if (mysql_stmt_execute(stmt)) + { + mysql_stmt_close(stmt); + mysql_close(&mysql); + return 0; + } + column_count= mysql_num_fields(prepare_meta_result); + memset(bind, 0, sizeof(bind)); + /* INTEGER COLUMN */ + bind[0].buffer_type= MYSQL_TYPE_LONG; + bind[0].buffer= (char *)&int_data; + bind[0].is_null= &is_null[0]; + bind[0].length= &length[0]; + bind[0].error= &error[0]; + + /* STRING COLUMN */ + bind[1].buffer_type= MYSQL_TYPE_STRING; + bind[1].buffer= (char *)str_data; + bind[1].buffer_length= STRING_SIZE; + bind[1].is_null= &is_null[1]; + bind[1].length= &length[1]; + bind[1].error= &error[1]; + + /* SMALLINT COLUMN */ + bind[2].buffer_type= MYSQL_TYPE_SHORT; + bind[2].buffer= (char *)&small_data; + bind[2].is_null= &is_null[2]; + bind[2].length= &length[2]; + bind[2].error= &error[2]; + + /* TIMESTAMP COLUMN */ + bind[3].buffer_type= MYSQL_TYPE_TIMESTAMP; + bind[3].buffer= (char *)&ts; + bind[3].is_null= &is_null[3]; + bind[3].length= &length[3]; + bind[3].error= &error[3]; + + if (mysql_stmt_bind_result(stmt, bind)) + { + mysql_free_result(prepare_meta_result); + mysql_stmt_close(stmt); + mysql_close(&mysql); + return 0; + } + if (mysql_stmt_store_result(stmt)) + { + mysql_free_result(prepare_meta_result); + mysql_stmt_close(stmt); + mysql_close(&mysql); + return 0; + } + while (!mysql_stmt_fetch(stmt)) {} + + mysql_free_result(prepare_meta_result); + mysql_stmt_close(stmt); + mysql_close(&mysql); + return 0; +} diff --git a/fuzz/onefile.cc b/fuzz/onefile.cc new file mode 100644 index 00000000000..bfffa709ed2 --- /dev/null +++ b/fuzz/onefile.cc @@ -0,0 +1,50 @@ +#include +#include +#include + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size); + +int main(int argc, char** argv) +{ + FILE * fp; + uint8_t *Data; + size_t Size; + + if (argc != 2) { + return 1; + } + //opens the file, get its size, and reads it into a buffer + fp = fopen(argv[1], "rb"); + if (fp == NULL) { + return 2; + } + if (fseek(fp, 0L, SEEK_END) != 0) { + fclose(fp); + return 2; + } + Size = ftell(fp); + if (Size == (size_t) -1) { + fclose(fp); + return 2; + } + if (fseek(fp, 0L, SEEK_SET) != 0) { + fclose(fp); + return 2; + } + Data = (uint8_t*)malloc(Size*sizeof(uint8_t)); + if (Data == NULL) { + fclose(fp); + return 2; + } + if (fread(Data, Size, 1, fp) != 1) { + fclose(fp); + free(Data); + return 2; + } + + //lauch fuzzer + LLVMFuzzerTestOneInput(Data, Size); + free(Data); + fclose(fp); + return 0; +} diff --git a/include/mysql.h b/include/mysql.h index 561960cd925..a11ccb42299 100644 --- a/include/mysql.h +++ b/include/mysql.h @@ -260,7 +260,8 @@ enum mysql_protocol_type { MYSQL_PROTOCOL_TCP, MYSQL_PROTOCOL_SOCKET, MYSQL_PROTOCOL_PIPE, - MYSQL_PROTOCOL_MEMORY + MYSQL_PROTOCOL_MEMORY, + MYSQL_PROTOCOL_FUZZ }; enum mysql_ssl_mode { diff --git a/include/violite.h b/include/violite.h index b0c5c6958ba..04371de3493 100644 --- a/include/violite.h +++ b/include/violite.h @@ -106,12 +106,14 @@ enum enum_vio_type : int { */ VIO_TYPE_PLUGIN = 7, + VIO_TYPE_FUZZ = 8, + FIRST_VIO_TYPE = VIO_TYPE_TCPIP, /* If a new type is added, please update LAST_VIO_TYPE. In addition, please change get_vio_type_name() in vio/vio.c to return correct name for it. */ - LAST_VIO_TYPE = VIO_TYPE_PLUGIN + LAST_VIO_TYPE = VIO_TYPE_FUZZ }; /** @@ -457,4 +459,19 @@ struct Vio { #define SSL_handle void * #endif + +//Vio fuzzing +bool vio_connect_fuzz(MYSQL_VIO vio, struct sockaddr *addr, socklen_t len, + int timeout); +int vio_socket_timeout_fuzz(Vio *vio, uint which, bool b); +void sock_initfuzz(const uint8_t *Data, size_t Size); +size_t vio_read_buff_fuzz(Vio *vio, uchar *buf, size_t size); +size_t vio_write_buff_fuzz(Vio *vio, const uchar *buf, size_t size); +bool vio_is_connected_fuzz(Vio *vio); +bool vio_was_timeout_fuzz(Vio *vio); +int vio_shutdown_fuzz(Vio *vio); +int vio_keepalive_fuzz(Vio *vio, bool set_keep_alive); +int vio_io_wait_fuzz(Vio *vio, enum enum_vio_io_event event, int timeout); +int vio_fastsend_fuzz(Vio *vio); + #endif /* vio_violite_h_ */ diff --git a/sql-common/client.cc b/sql-common/client.cc index d6d977970b7..ca6bbfb5773 100644 --- a/sql-common/client.cc +++ b/sql-common/client.cc @@ -5692,6 +5692,12 @@ static mysql_state_machine_status csm_begin_connect(mysql_async_connect *ctx) { } } #endif /* _WIN32 */ +if (!net->vio && + (mysql->options.protocol == MYSQL_PROTOCOL_FUZZ)) { + net->vio = + vio_new(0, VIO_TYPE_FUZZ, 0); + ctx->host_info = (char *)ER_CLIENT(CR_LOCALHOST_CONNECTION); +} #if defined(HAVE_SYS_UN_H) if (!net->vio && (!mysql->options.protocol || diff --git a/vio/CMakeLists.txt b/vio/CMakeLists.txt index f46858dd4bf..09c0a62ef8c 100644 --- a/vio/CMakeLists.txt +++ b/vio/CMakeLists.txt @@ -25,6 +25,7 @@ SET(VIO_SOURCES viosocket.cc viossl.cc viosslfactories.cc + viofuzz.cc ) IF(WIN32) diff --git a/vio/vio.cc b/vio/vio.cc index de46703eb82..14eef9dafc5 100644 --- a/vio/vio.cc +++ b/vio/vio.cc @@ -300,6 +300,27 @@ static bool vio_init(Vio *vio, enum enum_vio_type type, my_socket sd, return false; } #endif /* HAVE_OPENSSL */ + if (type == VIO_TYPE_FUZZ) { + vio->viodelete = vio_delete; + vio->vioerrno = vio_errno; + vio->read = vio_read_buff_fuzz; + vio->write = vio_write_buff_fuzz; + vio->fastsend = vio_fastsend_fuzz; + vio->viokeepalive = vio_keepalive_fuzz; + vio->should_retry = vio_should_retry; + vio->was_timeout = vio_was_timeout_fuzz; + vio->vioshutdown = vio_shutdown_fuzz; + vio->peer_addr = vio_peer_addr; + vio->timeout = vio_socket_timeout_fuzz; + vio->io_wait = vio_io_wait_fuzz; + vio->is_connected = vio_is_connected_fuzz; + vio->has_data = vio->read_buffer ? vio_buff_has_data : has_no_data; + vio->is_blocking = vio_is_blocking; + vio->set_blocking = vio_set_blocking; + vio->set_blocking_flag = vio_set_blocking_flag; + vio->is_blocking_flag = false; + return false; + } vio->viodelete = vio_delete; vio->vioerrno = vio_errno; vio->read = vio->read_buffer ? vio_read_buff : vio_read; @@ -577,7 +598,8 @@ static const vio_string vio_type_names[] = {{"", 0}, {STRING_WITH_LEN("SSL/TLS")}, {STRING_WITH_LEN("Shared Memory")}, {STRING_WITH_LEN("Internal")}, - {STRING_WITH_LEN("Plugin")}}; + {STRING_WITH_LEN("Plugin")}, + {STRING_WITH_LEN("Fuzz")}}; void get_vio_type_name(enum enum_vio_type vio_type, const char **str, int *len) { diff --git a/vio/viofuzz.cc b/vio/viofuzz.cc new file mode 100644 index 00000000000..65574bf1d79 --- /dev/null +++ b/vio/viofuzz.cc @@ -0,0 +1,111 @@ + +#include "my_config.h" + +#include +#include +#include +#include +#ifndef _WIN32 +#include +#endif +#include +#include + +#include "my_compiler.h" +#include "my_dbug.h" +#include "my_inttypes.h" +#include "my_io.h" +#include "my_macros.h" +#include "vio/vio_priv.h" + +#ifdef FIONREAD_IN_SYS_FILIO +#include +#endif +#ifndef _WIN32 +#include +#endif +#ifdef HAVE_POLL_H +#include +#endif +#ifdef HAVE_SYS_IOCTL_H +#include +#endif + +static const uint8_t *fuzzBuffer; +static size_t fuzzSize; +static size_t fuzzPos; + + +void sock_initfuzz(const uint8_t *Data, size_t Size) { + fuzzPos = 0; + fuzzSize = Size; + fuzzBuffer = Data; +} + +bool vio_connect_fuzz(Vio *vio, struct sockaddr *addr, socklen_t len, + int timeout) { + int ret, wait; + int retry_count = 0; + DBUG_ENTER("vio_socket_connect"); + + /* Only for socket-based transport types. */ + DBUG_ASSERT(vio->type == VIO_TYPE_SOCKET || vio->type == VIO_TYPE_TCPIP); + + /* Initiate the connection. */ + ret=0; + + DBUG_RETURN(MY_TEST(ret)); +} + + +int vio_socket_timeout_fuzz(Vio *vio, uint which, bool b) { + DBUG_ENTER("vio_socket_timeout_fuzz\n"); + return 1; +} + + +size_t vio_read_buff_fuzz(Vio *vio, uchar *bufp, size_t size) { + DBUG_ENTER("vio_read_buff_fuzz.\n"); + if (size > fuzzSize - fuzzPos) { + size = fuzzSize - fuzzPos; + } + if (fuzzPos < fuzzSize) { + memcpy(bufp, fuzzBuffer + fuzzPos, size); + } + fuzzPos += size; + return size; +} + +size_t vio_write_buff_fuzz(Vio *vio, const uchar *bufp, size_t size) { + DBUG_ENTER("vio_write_buff_fuzz\n"); + return size; +} + +bool vio_is_connected_fuzz(Vio *vio) { + DBUG_ENTER("vio_is_connected_fuzz\n"); + return true; +} + +bool vio_was_timeout_fuzz(Vio *vio) { + DBUG_ENTER("vio_was_timeout_fuzz\n"); + return false; +} + +int vio_shutdown_fuzz(Vio *vio) { + DBUG_ENTER("vio_shutdown_fuzz"); + return 0; +} + +int vio_keepalive_fuzz(Vio *vio, bool set_keep_alive) { + DBUG_ENTER("vio_keepalive_fuzz\n"); + return 0; +} +int vio_io_wait_fuzz(Vio *vio, enum enum_vio_io_event event, int timeout) { + DBUG_ENTER("vio_io_wait_fuzz"); + return 1; +} + +int vio_fastsend_fuzz(Vio *vio) { + DBUG_ENTER("vio_fastsend_fuzz\n"); + return 0; +}