From 1bdaabc0c6822c68d39c1e0ea6fc1cda41693d3a Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Wed, 1 Oct 2025 12:12:02 +0200 Subject: [PATCH] MDEV-35622 SEGV, ASAN use-after-poison when reading system table with less than expected number of columns Relaxed check, only number of columns and the PK. Enough to avoid crashes, but doesn't break upgrades and migration from MySQL as in MDEV-37777. Added checks everywhere. (flush/create/alter/drop server) Check mysql.plugin table too. --- mysql-test/main/servers.result | 58 ++++++++++++++++++++++++++++-- mysql-test/main/servers.test | 65 ++++++++++++++++++++++++++++++++-- sql/sql_parse.cc | 5 +-- sql/sql_plugin.cc | 34 +++++++++++------- sql/sql_servers.cc | 31 ++++++++++++---- 5 files changed, 167 insertions(+), 26 deletions(-) diff --git a/mysql-test/main/servers.result b/mysql-test/main/servers.result index a02c3d6ac74..1460eefaa30 100644 --- a/mysql-test/main/servers.result +++ b/mysql-test/main/servers.result @@ -37,11 +37,14 @@ alter server s1 options(host 'server.example.org'); rename table mysql.servers to mysql.servers_save; create table mysql.servers (x int); alter server s1 options(host 'server.example.org'); -ERROR HY000: The foreign server name you are trying to reference does not exist. Data source error: s1 +ERROR HY000: Cannot load from mysql.servers. The table is probably corrupted +drop server s1; +ERROR HY000: Cannot load from mysql.servers. The table is probably corrupted create server s2 foreign data wrapper foo options(user 'a'); -ERROR HY000: Can't read record in system table +ERROR HY000: Cannot load from mysql.servers. The table is probably corrupted drop table mysql.servers; rename table mysql.servers_save to mysql.servers; +flush privileges; drop server s1; # # MDEV-35641 foreign server "disappears" after ALTERing the servers system table to use innodb and FLUSH PRIVILEGES @@ -50,3 +53,54 @@ CREATE SERVER s1 FOREIGN DATA WRAPPER mysql OPTIONS (HOST '127.0.0.1'); ALTER TABLE mysql.servers ENGINE=innodb; FLUSH PRIVILEGES; drop server s1; +# +# MDEV-35622 SEGV, ASAN use-after-poison when reading system table with less than expected number of columns +# MDEV-37777 upgrade from MySQL 5.7 regression, mysql.servers invalid structure +# +# no crash: +rename table mysql.servers to mysql.servers_save; +create table mysql.servers like mysql.servers_save; +alter table mysql.servers drop column owner; +insert into mysql.servers values(0,0,0,0,0,0,0,0); +flush privileges; +ERROR HY000: Cannot load from mysql.servers. The table is probably corrupted +drop table mysql.servers; +# w/o PK +create table mysql.servers like mysql.servers_save; +alter table mysql.servers drop primary key; +insert into mysql.servers values(0,0,0,0,0,0,0,0,0); +flush privileges; +ERROR HY000: Cannot load from mysql.servers. The table is probably corrupted +drop table mysql.servers; +# upgrade is ok +create table mysql.servers like mysql.servers_save; +alter table mysql.servers add column Options text; +create server s1 foreign data wrapper mysql options (host '127.0.0.1'); +flush privileges; +drop server s1; +drop table mysql.servers; +# MySQL 5.7 (MDEV-37777) +create table mysql.servers like mysql.servers_save; +alter table mysql.servers modify Host char(64) not null, modify Owner char(64) not null; +create server s1 foreign data wrapper mysql options (host '127.0.0.1'); +flush privileges; +drop server s1; +drop table mysql.servers; +rename table mysql.servers_save to mysql.servers; +# plugin +create table mysql.plugin_save like mysql.plugin; +alter table mysql.plugin drop column dl; +install soname "ha_example"; +ERROR HY000: Cannot load from mysql.plugin. The table is probably corrupted +uninstall soname "ha_example"; +ERROR HY000: Cannot load from mysql.plugin. The table is probably corrupted +drop table mysql.plugin; +create table mysql.plugin like mysql.plugin_save; +alter table mysql.plugin drop primary key; +install soname "ha_example"; +ERROR HY000: Cannot load from mysql.plugin. The table is probably corrupted +uninstall soname "ha_example"; +ERROR HY000: Cannot load from mysql.plugin. The table is probably corrupted +drop table mysql.plugin; +rename table mysql.plugin_save to mysql.plugin; +# End of 10.11 tests diff --git a/mysql-test/main/servers.test b/mysql-test/main/servers.test index f46041bb21c..5ef82173e88 100644 --- a/mysql-test/main/servers.test +++ b/mysql-test/main/servers.test @@ -34,12 +34,15 @@ create server s1 foreign data wrapper foo options(user 'a'); alter server s1 options(host 'server.example.org'); rename table mysql.servers to mysql.servers_save; create table mysql.servers (x int); ---error ER_FOREIGN_SERVER_DOESNT_EXIST +--error ER_CANNOT_LOAD_FROM_TABLE_V2 alter server s1 options(host 'server.example.org'); ---error ER_CANT_FIND_SYSTEM_REC +--error ER_CANNOT_LOAD_FROM_TABLE_V2 +drop server s1; +--error ER_CANNOT_LOAD_FROM_TABLE_V2 create server s2 foreign data wrapper foo options(user 'a'); drop table mysql.servers; rename table mysql.servers_save to mysql.servers; +flush privileges; drop server s1; --echo # @@ -50,3 +53,61 @@ CREATE SERVER s1 FOREIGN DATA WRAPPER mysql OPTIONS (HOST '127.0.0.1'); ALTER TABLE mysql.servers ENGINE=innodb; FLUSH PRIVILEGES; drop server s1; + +--echo # +--echo # MDEV-35622 SEGV, ASAN use-after-poison when reading system table with less than expected number of columns +--echo # MDEV-37777 upgrade from MySQL 5.7 regression, mysql.servers invalid structure +--echo # + +--echo # no crash: +rename table mysql.servers to mysql.servers_save; +create table mysql.servers like mysql.servers_save; +alter table mysql.servers drop column owner; +insert into mysql.servers values(0,0,0,0,0,0,0,0); +--error ER_CANNOT_LOAD_FROM_TABLE_V2 +flush privileges; +drop table mysql.servers; + +--echo # w/o PK +create table mysql.servers like mysql.servers_save; +alter table mysql.servers drop primary key; +insert into mysql.servers values(0,0,0,0,0,0,0,0,0); +--error ER_CANNOT_LOAD_FROM_TABLE_V2 +flush privileges; +drop table mysql.servers; + +--echo # upgrade is ok +create table mysql.servers like mysql.servers_save; +alter table mysql.servers add column Options text; +create server s1 foreign data wrapper mysql options (host '127.0.0.1'); +flush privileges; +drop server s1; +drop table mysql.servers; + +--echo # MySQL 5.7 (MDEV-37777) +create table mysql.servers like mysql.servers_save; +alter table mysql.servers modify Host char(64) not null, modify Owner char(64) not null; +create server s1 foreign data wrapper mysql options (host '127.0.0.1'); +flush privileges; +drop server s1; +drop table mysql.servers; +rename table mysql.servers_save to mysql.servers; + +--echo # plugin +create table mysql.plugin_save like mysql.plugin; +alter table mysql.plugin drop column dl; +--error ER_CANNOT_LOAD_FROM_TABLE_V2 +install soname "ha_example"; +--error ER_CANNOT_LOAD_FROM_TABLE_V2 +uninstall soname "ha_example"; +drop table mysql.plugin; +create table mysql.plugin like mysql.plugin_save; +alter table mysql.plugin drop primary key; +--error ER_CANNOT_LOAD_FROM_TABLE_V2 +install soname "ha_example"; +--error ER_CANNOT_LOAD_FROM_TABLE_V2 +uninstall soname "ha_example"; +drop table mysql.plugin; +rename table mysql.plugin_save to mysql.plugin; + +--echo # End of 10.11 tests diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 007af961396..a66e1ec83c1 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -6129,11 +6129,12 @@ mysql_execute_command(THD *thd, bool is_called_from_prepared_stmt) if ((err_code= drop_server(thd, &lex->server_options))) { - if (! lex->if_exists() && err_code == ER_FOREIGN_SERVER_DOESNT_EXIST) + if (! lex->if_exists() || err_code != ER_FOREIGN_SERVER_DOESNT_EXIST) { DBUG_PRINT("info", ("problem dropping server %s", lex->server_options.server_name.str)); - my_error(err_code, MYF(0), lex->server_options.server_name.str); + if (!thd->is_error()) + my_error(err_code, MYF(0), lex->server_options.server_name.str); } else { diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc index 35319c4c2eb..1239d165fc7 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -1875,6 +1875,19 @@ static bool register_builtin(struct st_maria_plugin *plugin, } +static bool plugin_table_is_valid(TABLE *table) +{ + if (table->s->fields < PLUGIN_FIELDS_COUNT || + table->s->primary_key == MAX_KEY) + { + my_error(ER_CANNOT_LOAD_FROM_TABLE_V2, MYF(0), + table->s->db.str, table->s->table_name.str); + return false; + } + return true; +} + + /* called only by plugin_init() */ @@ -1917,6 +1930,9 @@ static void plugin_load(MEM_ROOT *tmp_root) goto end; } + if (!plugin_table_is_valid(table)) + goto end2; + if (init_read_record(&read_record_info, new_thd, table, NULL, NULL, 1, 0, FALSE)) { @@ -1978,6 +1994,7 @@ static void plugin_load(MEM_ROOT *tmp_root) sql_print_error(ER_THD(new_thd, ER_GET_ERRNO), my_errno, table->file->table_type()); end_read_record(&read_record_info); +end2: table->mark_table_for_reopen(); close_mysql_tables(new_thd); end: @@ -2280,8 +2297,8 @@ bool mysql_install_plugin(THD *thd, const LEX_CSTRING *name, WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL); /* need to open before acquiring LOCK_plugin or it will deadlock */ - if (! (table = open_ltable(thd, &tables, TL_WRITE, - MYSQL_LOCK_IGNORE_TIMEOUT))) + table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT); + if (!table || !plugin_table_is_valid(table)) DBUG_RETURN(TRUE); if (my_load_defaults(MYSQL_CONFIG_NAME, load_default_groups, &argc, &argv, NULL)) @@ -2438,19 +2455,10 @@ bool mysql_uninstall_plugin(THD *thd, const LEX_CSTRING *name, WSREP_TO_ISOLATION_BEGIN(WSREP_MYSQL_DB, NULL, NULL); /* need to open before acquiring LOCK_plugin or it will deadlock */ - if (! (table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT))) + table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT); + if (!table || !plugin_table_is_valid(table)) DBUG_RETURN(TRUE); - if (!table->key_info) - { - my_printf_error(ER_UNKNOWN_ERROR, - "The table %s.%s has no primary key. " - "Please check the table definition and " - "create the primary key accordingly.", MYF(0), - table->s->db.str, table->s->table_name.str); - DBUG_RETURN(TRUE); - } - /* Pre-acquire audit plugins for events that may potentially occur during [UN]INSTALL PLUGIN. diff --git a/sql/sql_servers.cc b/sql/sql_servers.cc index 3fa319eb571..c6e06ca3d48 100644 --- a/sql/sql_servers.cc +++ b/sql/sql_servers.cc @@ -315,6 +315,20 @@ end: } +static bool servers_table_is_valid(TABLE *table) +{ + if (table->s->fields < SERVERS_FIELDS_COUNT || + table->s->primary_key == MAX_KEY) + { + my_errno= 1; + my_error(ER_CANNOT_LOAD_FROM_TABLE_V2, MYF(0), + table->s->db.str, table->s->table_name.str); + return false; + } + return true; +} + + /* Forget current servers cache and read new servers from the conneciton table. @@ -358,6 +372,9 @@ bool servers_reload(THD *thd) goto end; } + if (!servers_table_is_valid(tables.table)) + goto end; + if ((return_val= servers_load(thd, &tables))) { // Error. Revert to old list /* blast, for now, we have no servers, discuss later way to preserve */ @@ -479,8 +496,10 @@ insert_server(THD *thd, FOREIGN_SERVER *server) tables.init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_SERVERS_NAME, 0, TL_WRITE); /* need to open before acquiring THR_LOCK_plugin or it will deadlock */ - if (! (table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT))) + table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT); + if (!table || !servers_table_is_valid(table)) goto end; + table->file->row_logging= 0; // Don't log to binary log /* insert the server into the table */ @@ -559,9 +578,6 @@ store_server_fields(TABLE *table, FOREIGN_SERVER *server) table->use_all_columns(); - if (table->s->fields < SERVERS_FIELDS_COUNT) - return ER_CANT_FIND_SYSTEM_REC; - /* "server" has already been prepped by prepare_server_struct_for_<> so, all we need to do is check if the value is set (> -1 for port) @@ -711,8 +727,8 @@ static int drop_server_internal(THD *thd, LEX_SERVER_OPTIONS *server_options) if (unlikely((error= delete_server_record_in_cache(server_options)))) goto end; - if (unlikely(!(table= open_ltable(thd, &tables, TL_WRITE, - MYSQL_LOCK_IGNORE_TIMEOUT)))) + table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT); + if (!table || !servers_table_is_valid(table)) { error= my_errno; goto end; @@ -839,7 +855,8 @@ int update_server(THD *thd, FOREIGN_SERVER *existing, FOREIGN_SERVER *altered) tables.init_one_table(&MYSQL_SCHEMA_NAME, &MYSQL_SERVERS_NAME, 0, TL_WRITE); - if (!(table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT))) + table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT); + if (!table || !servers_table_is_valid(table)) { error= my_errno; goto end;