From a09153b84f511f6a5093f92fbb4974422ff916be Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Tue, 3 Jun 2025 08:51:28 +0200 Subject: [PATCH] client: add fuzz target Defines a new vio which simply uses a buffer in memory, so that the fuzz target initializes it with the fuzz input, and it gets used as if it were the data from the network sent by the server. --- client/CMakeLists.txt | 5 ++ client/fuzz/CMakeLists.txt | 8 ++ client/fuzz/fuzz_real_query.cc | 130 +++++++++++++++++++++++++++++++++ include/mysql.h | 3 +- include/mysql.h.pp | 3 +- include/violite.h | 8 +- sql-common/client.cc | 5 ++ vio/vio.cc | 3 +- vio/viosocket.cc | 36 +++++++++ 9 files changed, 197 insertions(+), 4 deletions(-) create mode 100644 client/fuzz/CMakeLists.txt create mode 100644 client/fuzz/fuzz_real_query.cc diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 4933a7131029..5b99dae5ffe5 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -29,6 +29,11 @@ DISABLE_MISSING_PROFILE_WARNING() ## Subdirectory for mysql_migrate_keyring code. ADD_SUBDIRECTORY(migrate_keyring) +INCLUDE(../router/cmake/fuzzer.cmake) +IF(LIBFUZZER_COMPILE_FLAGS) + ADD_SUBDIRECTORY(fuzz) +ENDIF() + MYSQL_ADD_EXECUTABLE(mysql ${CMAKE_SOURCE_DIR}/sql-common/net_ns.cc completion_hash.cc diff --git a/client/fuzz/CMakeLists.txt b/client/fuzz/CMakeLists.txt new file mode 100644 index 000000000000..aa58e8f9d2f0 --- /dev/null +++ b/client/fuzz/CMakeLists.txt @@ -0,0 +1,8 @@ +IF(LIBFUZZER_COMPILE_FLAGS) + MYSQL_ADD_EXECUTABLE(fuzz_real_query + fuzz_real_query.cc + LINK_LIBRARIES mysqlclient + SKIP_INSTALL + ) + LIBFUZZER_ADD_TEST(fuzz_real_query) +ENDIF() diff --git a/client/fuzz/fuzz_real_query.cc b/client/fuzz/fuzz_real_query.cc new file mode 100644 index 000000000000..af6ba562fc1f --- /dev/null +++ b/client/fuzz/fuzz_real_query.cc @@ -0,0 +1,130 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "violite.h" + +using namespace std; +FILE *logfile = NULL; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + MYSQL mysql; + bool opt_cleartext = true; + unsigned int opt_ssl = SSL_MODE_DISABLED; + MYSQL_RES *result; + uint8_t op; + + if (Size < 1) { + return 0; + } + op = Data[0]; + Data++; + Size--; + if (logfile == NULL) { + logfile = fopen("/dev/null", "w"); + } + mysql_init(&mysql); + if (mysql_options(&mysql, MYSQL_ENABLE_CLEARTEXT_PLUGIN, &opt_cleartext) != 0) { + abort(); + } + if (mysql_options(&mysql, MYSQL_OPT_SSL_MODE, &opt_ssl) != 0) { + abort(); + } + unsigned int my_protocol = MYSQL_PROTOCOL_FUZZ; + if (mysql_options(&mysql, MYSQL_OPT_PROTOCOL, &my_protocol) != 0) { + abort(); + } + // The fuzzing takes place on network data received from server + sock_initfuzz(Data,Size); + if (!mysql_real_connect(&mysql, "localhost", "user", "pass", "db", 0, NULL, 0)) { + goto out; + } + + mysql_info(&mysql); + mysql_ping(&mysql); + + switch (op) { + case 0: + mysql_query(&mysql, "CREATE DATABASE fuzz"); + if (mysql_query(&mysql, "SELECT * FROM crashes")) { + goto out; + } + result = mysql_store_result(&mysql); + if (result != NULL) { + mysql_result_metadata(result); + mysql_num_rows(result); + int num_fields = mysql_num_fields(result); + MYSQL_FIELD *field; + while((field = mysql_fetch_field(result))) { + fprintf(logfile, "%s\n", field->name); + } + MYSQL_ROW row = mysql_fetch_row(result); + unsigned long * lengths = mysql_fetch_lengths(result); + while (row ) { + for(int i = 0; i < num_fields; i++) { + fprintf(logfile, "length %lu, %s\n", lengths[i], row[i] ? row[i] : "NULL"); + } + row = mysql_fetch_row(result); + } + mysql_free_result(result); + } + break; + case 1: + result = mysql_list_dbs(&mysql, NULL); + if (result) { + mysql_free_result(result); + } + break; + case 2: + result = mysql_list_tables(&mysql, NULL); + if (result) { + mysql_free_result(result); + } + break; + case 3: + result = mysql_list_fields(&mysql, "table", NULL); + if (result) { + mysql_free_result(result); + } + break; + case 4: + result = mysql_list_processes(&mysql); + if (result) { + mysql_free_result(result); + } + break; + case 5: + if (mysql_query(&mysql, "INSERT INTO Fuzzers(Name) VALUES('target')") == 0) { + fprintf(logfile, "The last inserted row id is: %llu\n", mysql_insert_id(&mysql)); + fprintf(logfile, "%llu affected rows\n", mysql_affected_rows(&mysql)); + } + break; + case 6: + if (mysql_change_user(&mysql, "user", "password", "new_database")) { + goto out; + } + break; + case 7: + if (mysql_select_db(&mysql, "new_database")) { + goto out; + } + break; + } + + mysql_get_host_info(&mysql); + mysql_get_proto_info(&mysql); + mysql_get_server_info(&mysql); + mysql_get_server_version(&mysql); + mysql_dump_debug_info(&mysql); + mysql_sqlstate(&mysql); + mysql_stat(&mysql); + mysql_info(&mysql); + +out: + mysql_close(&mysql); + return 0; +} diff --git a/include/mysql.h b/include/mysql.h index ef1681da644a..384179c2096d 100644 --- a/include/mysql.h +++ b/include/mysql.h @@ -266,7 +266,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/mysql.h.pp b/include/mysql.h.pp index 00cb5969fbc0..cd11bb1322e9 100644 --- a/include/mysql.h.pp +++ b/include/mysql.h.pp @@ -504,7 +504,8 @@ MYSQL_PROTOCOL_TCP, MYSQL_PROTOCOL_SOCKET, MYSQL_PROTOCOL_PIPE, - MYSQL_PROTOCOL_MEMORY + MYSQL_PROTOCOL_MEMORY, + MYSQL_PROTOCOL_FUZZ }; enum mysql_ssl_mode { SSL_MODE_DISABLED = 1, diff --git a/include/violite.h b/include/violite.h index 3a6e6bb6fab5..04eebccd9953 100644 --- a/include/violite.h +++ b/include/violite.h @@ -110,12 +110,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 }; /** @@ -453,4 +455,8 @@ struct Vio { #define SSL_handle SSL * +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION +void sock_initfuzz(const uint8_t *Data, size_t Size); +#endif + #endif /* vio_violite_h_ */ diff --git a/sql-common/client.cc b/sql-common/client.cc index 40a3516e2aa5..f75061421e11 100644 --- a/sql-common/client.cc +++ b/sql-common/client.cc @@ -6479,6 +6479,11 @@ 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 = const_cast(ER_CLIENT(CR_LOCALHOST_CONNECTION)); + } #if defined(HAVE_SYS_UN_H) if (!net->vio && (!mysql->options.protocol || diff --git a/vio/vio.cc b/vio/vio.cc index 0869b6ac524d..8f586bf1a35a 100644 --- a/vio/vio.cc +++ b/vio/vio.cc @@ -577,7 +577,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/viosocket.cc b/vio/viosocket.cc index a499720cb39a..459d61479c47 100644 --- a/vio/viosocket.cc +++ b/vio/viosocket.cc @@ -134,11 +134,41 @@ int vio_socket_io_wait(Vio *vio, enum enum_vio_io_event event) { #define VIO_DONTWAIT 0 #endif +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION +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; +} +#endif + size_t vio_read(Vio *vio, uchar *buf, size_t size) { ssize_t ret; int flags = 0; DBUG_TRACE; +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + if (vio->type == VIO_TYPE_FUZZ) { + if (size > fuzzSize - fuzzPos) { + size = fuzzSize - fuzzPos; + } + if (fuzzPos < fuzzSize) { + memcpy(buf, fuzzBuffer + fuzzPos, size); + } + fuzzPos += size; +#ifdef FUZZ_DEBUG + printf("net cli %zu ", size); + for (size_t i=0; iread_end == vio->read_pos); @@ -216,6 +246,12 @@ size_t vio_write(Vio *vio, const uchar *buf, size_t size) { int flags = 0; DBUG_TRACE; +#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + if (vio->type == VIO_TYPE_FUZZ) { + return size; + } +#endif + /* If timeout is enabled, do not block. */ if (vio->write_timeout >= 0) flags = VIO_DONTWAIT;