diff --git a/projects/mysql-server/Dockerfile b/projects/mysql-server/Dockerfile index 1cd6f8ea9148..6fbe7fbb99ee 100644 --- a/projects/mysql-server/Dockerfile +++ b/projects/mysql-server/Dockerfile @@ -19,7 +19,9 @@ RUN apt-get update && apt-get install -y build-essential libssl-dev libncurses5- RUN git clone --depth 1 https://github.com/mysql/mysql-server WORKDIR $SRC COPY build.sh $SRC/ -COPY fix.diff $SRC/ +COPY patch $SRC/patch +COPY addfuzzdir.patch $SRC/ +#COPY fix.diff $SRC/ #TODO merge custom targets upstream #COPY targets $SRC/mysql-server/fuzz #RUN cd mysql-server && git apply ../fix.diff diff --git a/projects/mysql-server/addfuzzdir.patch b/projects/mysql-server/addfuzzdir.patch new file mode 100644 index 000000000000..af6b6499d24e --- /dev/null +++ b/projects/mysql-server/addfuzzdir.patch @@ -0,0 +1,6 @@ + +INCLUDE(../router/cmake/fuzzer.cmake) +IF(LIBFUZZER_COMPILE_FLAGS) + ADD_SUBDIRECTORY(fuzz) +ENDIF() + diff --git a/projects/mysql-server/build.sh b/projects/mysql-server/build.sh index 8dce0306cb9d..34dec9e3f3e6 100644 --- a/projects/mysql-server/build.sh +++ b/projects/mysql-server/build.sh @@ -16,6 +16,9 @@ # ################################################################################ +cp -r $SRC/patch/* mysql-server/ +cat $SRC/addfuzzdir.patch >> mysql-server/sql/CMakeLists.txt + cd mysql-server mkdir build cd build @@ -33,7 +36,13 @@ cp library_output_directory/libmysql*.so.* $OUT/lib/ cd runtime_output_directory/ ls *fuzz* | while read i; do cp $i $OUT/ - chrpath -r '$ORIGIN/lib' $OUT/$i + chrpath -r '$ORIGIN/lib' $OUT/$i || true +done +) +( +find router/ -type f -executable | grep fuzz | while read i; do + chrpath -r '$ORIGIN/lib' $i || true + cp $i $OUT/ done ) diff --git a/projects/mysql-server/patch/sql/fuzz/CMakeLists.txt b/projects/mysql-server/patch/sql/fuzz/CMakeLists.txt new file mode 100644 index 000000000000..2ba9f1805e47 --- /dev/null +++ b/projects/mysql-server/patch/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/projects/mysql-server/patch/sql/fuzz/fuzz_sql_parse.cc b/projects/mysql-server/patch/sql/fuzz/fuzz_sql_parse.cc new file mode 100644 index 000000000000..24598a0f97a4 --- /dev/null +++ b/projects/mysql-server/patch/sql/fuzz/fuzz_sql_parse.cc @@ -0,0 +1,162 @@ +// Copyright 2025 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. + + +#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; +}