diff --git a/mysql-test/main/servers.result b/mysql-test/main/servers.result index 585f0f62af0..c466967a0db 100644 --- a/mysql-test/main/servers.result +++ b/mysql-test/main/servers.result @@ -24,3 +24,17 @@ SELECT * FROM mysql.servers; Server_name Host Db Username Password Port Socket Wrapper Owner s1 3306 bar mysql DROP SERVER s1; +# +# MDEV-33783 CREATE SERVER segfaults on wrong mysql.servers +# +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); +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 +create server s2 foreign data wrapper foo options(user 'a'); +ERROR HY000: Can't read record in system table +drop table mysql.servers; +rename table mysql.servers_save to mysql.servers; +drop server s1; diff --git a/mysql-test/main/servers.test b/mysql-test/main/servers.test index 645206c376e..6aaaa356c1d 100644 --- a/mysql-test/main/servers.test +++ b/mysql-test/main/servers.test @@ -22,3 +22,18 @@ DROP SERVER s1; CREATE SERVER s1 FOREIGN DATA WRAPPER mysql OPTIONS(SOCKET 'bar'); SELECT * FROM mysql.servers; DROP SERVER s1; + +--echo # +--echo # MDEV-33783 CREATE SERVER segfaults on wrong mysql.servers +--echo # +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 +alter server s1 options(host 'server.example.org'); +--error ER_CANT_FIND_SYSTEM_REC +create server s2 foreign data wrapper foo options(user 'a'); +drop table mysql.servers; +rename table mysql.servers_save to mysql.servers; +drop server s1; diff --git a/sql/sql_servers.cc b/sql/sql_servers.cc index 8350b9dba9b..59a4679847b 100644 --- a/sql/sql_servers.cc +++ b/sql/sql_servers.cc @@ -462,7 +462,7 @@ get_server_from_table_to_cache(TABLE *table) RETURN VALUES 0 - no error - other - error code + other - ER_ error code */ static int @@ -546,15 +546,19 @@ insert_server_record_into_cache(FOREIGN_SERVER *server) advance of insertion into the mysql.servers table RETURN VALUE - VOID - + 0 - no errors + >0 - ER_ error code */ -static void +static int store_server_fields(TABLE *table, FOREIGN_SERVER *server) { table->use_all_columns(); + + if (table->s->fields < 9) + 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) @@ -563,30 +567,43 @@ store_server_fields(TABLE *table, FOREIGN_SERVER *server) have changed will be set. If an insert, then all will be set, even if with empty strings */ - if (server->host) + if (server->host && table->field[1]->store(server->host, - (uint) strlen(server->host), system_charset_info); - if (server->db) + (uint) strlen(server->host), system_charset_info)) + goto err; + if (server->db && table->field[2]->store(server->db, - (uint) strlen(server->db), system_charset_info); - if (server->username) + (uint) strlen(server->db), system_charset_info)) + goto err; + if (server->username && table->field[3]->store(server->username, - (uint) strlen(server->username), system_charset_info); - if (server->password) + (uint) strlen(server->username), system_charset_info)) + goto err; + if (server->password && table->field[4]->store(server->password, - (uint) strlen(server->password), system_charset_info); - if (server->port > -1) - table->field[5]->store(server->port); - - if (server->socket) + (uint) strlen(server->password), system_charset_info)) + goto err; + if (server->port > -1 && + table->field[5]->store(server->port)) + goto err; + if (server->socket && table->field[6]->store(server->socket, - (uint) strlen(server->socket), system_charset_info); - if (server->scheme) + (uint) strlen(server->socket), system_charset_info)) + goto err; + if (server->scheme && table->field[7]->store(server->scheme, - (uint) strlen(server->scheme), system_charset_info); - if (server->owner) + (uint) strlen(server->scheme), system_charset_info)) + goto err; + if (server->owner && table->field[8]->store(server->owner, - (uint) strlen(server->owner), system_charset_info); + (uint) strlen(server->owner), system_charset_info)) + goto err; + return 0; + +err: + THD *thd= table->in_use; + DBUG_ASSERT(thd->is_error()); + return thd->get_stmt_da()->get_sql_errno(); } /* @@ -608,7 +625,7 @@ store_server_fields(TABLE *table, FOREIGN_SERVER *server) RETURN VALUE 0 - no errors - >0 - error code + >0 - ER_ error code */ @@ -642,7 +659,8 @@ int insert_server_record(TABLE *table, FOREIGN_SERVER *server) error= 1; } /* store each field to be inserted */ - store_server_fields(table, server); + if ((error= store_server_fields(table, server))) + DBUG_RETURN(error); DBUG_PRINT("info",("record for server '%s' not found!", server->server_name)); @@ -972,9 +990,15 @@ update_server_record(TABLE *table, FOREIGN_SERVER *server) table->use_all_columns(); /* set the field that's the PK to the value we're looking for */ - table->field[0]->store(server->server_name, + if (table->field[0]->store(server->server_name, server->server_name_length, - system_charset_info); + system_charset_info)) + { + DBUG_ASSERT(0); /* Protected by servers_cache */ + THD *thd= table->in_use; + DBUG_ASSERT(thd->is_error()); + return thd->get_stmt_da()->get_sql_errno(); + } if (unlikely((error= table->file->ha_index_read_idx_map(table->record[0], 0, @@ -992,7 +1016,8 @@ update_server_record(TABLE *table, FOREIGN_SERVER *server) { /* ok, so we can update since the record exists in the table */ store_record(table,record[1]); - store_server_fields(table, server); + if ((error= store_server_fields(table, server))) + goto end; if (unlikely((error=table->file->ha_update_row(table->record[1], table->record[0])) && error != HA_ERR_RECORD_IS_THE_SAME))