From f8c9bb0d4471555d669be9e15821f400c5fa27af Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Sun, 25 May 2025 21:04:47 +0200 Subject: [PATCH] fuzz: parse_sql target --- sql/CMakeLists.txt | 5 ++ sql/fuzz/CMakeLists.txt | 8 ++ sql/fuzz/fuzz_sql_parse.cc | 147 +++++++++++++++++++++++++++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 sql/fuzz/CMakeLists.txt create mode 100644 sql/fuzz/fuzz_sql_parse.cc diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index b6559a2bbf71..ec0e7766e411 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -1468,3 +1468,8 @@ ADD_CUSTOM_TARGET(distclean ADD_CUSTOM_TARGET(show-dist-name COMMAND ${CMAKE_COMMAND} -E echo "${CPACK_PACKAGE_FILE_NAME}" ) + +INCLUDE(../router/cmake/fuzzer.cmake) +IF(LIBFUZZER_COMPILE_FLAGS) + ADD_SUBDIRECTORY(fuzz) +ENDIF() diff --git a/sql/fuzz/CMakeLists.txt b/sql/fuzz/CMakeLists.txt new file mode 100644 index 000000000000..2ba9f1805e47 --- /dev/null +++ b/sql/fuzz/CMakeLists.txt @@ -0,0 +1,8 @@ +IF(LIBFUZZER_COMPILE_FLAGS) + MYSQL_ADD_EXECUTABLE(fuzz_sql_parse + fuzz_sql_parse.cc + LINK_LIBRARIES server_unittest_library + SKIP_INSTALL + ) + LIBFUZZER_ADD_TEST(fuzz_sql_parse) +ENDIF() diff --git a/sql/fuzz/fuzz_sql_parse.cc b/sql/fuzz/fuzz_sql_parse.cc new file mode 100644 index 000000000000..43cf967c52e6 --- /dev/null +++ b/sql/fuzz/fuzz_sql_parse.cc @@ -0,0 +1,147 @@ +#include +#include +#include +#include +#include + +#include "sql/sql_class.h" +#include "sql/sql_lex.h" +#include "sql/sql_parse.h" + +#include "my_inttypes.h" +#include "my_rnd.h" +#include "mysql/strings/m_ctype.h" +#include "mysql_com.h" +#include "sql-common/my_decimal.h" + +#include "sql/binlog.h" +#include "sql/client_settings.h" +#include "sql/conn_handler/connection_handler_manager.h" +#include "sql/dd/dd.h" +#include "sql/dd/impl/dictionary_impl.h" // dd::Dictionary_impl +#include "sql/dd/impl/tables/column_type_elements.h" +#include "sql/dd/impl/tables/schemata.h" +#include "sql/dd/impl/tables/tables.h" +#include "sql/derror.h" +#include "sql/item_func.h" +#include "sql/keycaches.h" +#include "sql/log.h" // query_logger +#include "sql/mysqld.h" // set_remaining_args +#include "sql/mysqld_thd_manager.h" +#include "sql/opt_costconstantcache.h" // optimizer cost constant cache +#include "sql/range_optimizer/range_optimizer.h" +#include "sql/rpl_filter.h" +#include "sql/rpl_handler.h" // delegates_init() +#include "sql/set_var.h" +#include "sql/sql_class.h" +#include "sql/sql_lex.h" +#include "sql/sql_locale.h" +#include "sql/sql_plugin.h" +#include "sql/xa.h" +#include "sql/xa/transaction_cache.h" // xa::Transaction_cache + +using namespace std; + +static int initialized = 0; + +namespace my_testing { + +class DD_initializer { + public: + static void SetUp(); + static void TearDown(); +}; + +void DD_initializer::SetUp() { + /* + With WL#6599, Query_block::add_table_to_list() will invoke + dd::Dictionary::is_system_view_name() method. E.g., the unit + test InsertDelayed would invoke above API. This requires us + to have a instance of dictionary_impl. We do not really need + to initialize dd::System_views for this test. Also, there can + be future test cases that need the same. + */ + dd::Dictionary_impl::s_instance = new (std::nothrow) dd::Dictionary_impl(); + assert(dd::Dictionary_impl::s_instance != nullptr); +} + +void DD_initializer::TearDown() { + assert(dd::Dictionary_impl::s_instance != nullptr); + delete dd::Dictionary_impl::s_instance; +} + +} // namespace my_testing + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + if (initialized == 0) { + std::string my_name("fuzz_sql_parse"); + MY_INIT("fuzz_sql_parse"); + char *argv[] = {const_cast(my_name.c_str()), + const_cast("--secure-file-priv=NULL"), + const_cast("--log_syslog=0"), + const_cast("--explicit_defaults_for_timestamp"), + const_cast("--datadir=/tmp"), + const_cast("--lc-messages-dir=/tmp"), + nullptr}; + set_remaining_args(6, argv); + system_charset_info = &my_charset_utf8mb3_general_ci; + + mysql_mutex_init(PSI_NOT_INSTRUMENTED, &LOCK_plugin, MY_MUTEX_INIT_FAST); + sys_var_init(); + init_common_variables(); + test_flags |= TEST_SIGINT; + test_flags |= TEST_NO_TEMP_TABLES; + test_flags &= ~TEST_CORE_ON_SIGNAL; + my_init_signals(); + // Install server's abort handler to better represent server environment. + // set_my_abort(my_server_abort); + randominit(&sql_rand, 0, 0); + xa::Transaction_cache::initialize(); + delegates_init(); + gtid_server_init(); + // error_handler_hook = test_error_handler_hook; + // Initialize Query_logger last, to avoid spurious warnings to stderr. + query_logger.init(); + init_optimizer_cost_module(false); + my_testing::DD_initializer::SetUp(); + + initialized = 1; + } + + THD *thd = new THD(false); + THD *stack_thd = thd; + + thd->set_new_thread_id(); + thd->thread_stack = (char *)&stack_thd; + thd->store_globals(); + lex_start(thd); + char *db = static_cast(my_malloc(PSI_NOT_INSTRUMENTED, 3, MYF(0))); + sprintf(db, "db"); + LEX_CSTRING db_lex_cstr = {db, strlen(db)}; + thd->reset_db(db_lex_cstr); + + Parser_state state; + char *mutable_query = (char *)malloc(Size+1); + if (!mutable_query) { + return 0; + } + mutable_query[Size] = 0; + memcpy(mutable_query, Data, Size); + + state.init(thd, mutable_query, Size); + /* + This tricks the server to parse the query and then stop, + without executing. + */ + thd->security_context()->set_password_expired(true); + + lex_start(thd); + mysql_reset_thd_for_next_command(thd); + parse_sql(thd, &state, nullptr); + + free(mutable_query); + thd->cleanup_after_query(); + delete thd; + + return 0; +}