Commit c70a9fa1 authored by Davi Arnaut's avatar Davi Arnaut

Bug#41726: upgrade from 5.0 to 5.1.30 crashes if you didn't run mysql_upgrade

The problem is that the server could crash when attempting
to access a non-conformant proc system table. One such case
was a crash when invoking stored procedure related statements
on a 5.1 server with a proc system table in the 5.0 format.

The solution is to validate the proc system table format
before attempts to access it are made. If the table is not
in the format that the server expects, a message is written
to the error log and the statement that caused the table to
be accessed fails.

mysql-test/r/sp-destruct.result:
  Add test case result for Bug#41726
mysql-test/t/sp-destruct.test:
  Add test case for Bug#41726
sql/event_db_repository.cc:
  Update code to use new structures.
sql/sp.cc:
  Describe the proc table format and use it to validate when
  opening a instance of the table.
  Add a check to insure that a error message is written to
  the error log only once.
sql/sql_acl.cc:
  Remove unused variable and use new structure.
sql/sql_acl.h:
  Export field definition.
sql/table.cc:
  Accept the field count and definition in a single structure.
sql/table.h:
  Combine the field count and definition in a single structure.
  Transform function into a class in order to support different
  ways of reporting a error.
  Add a pointer cache to TABLE_SHARE.
parent f9af124e
call mtr.add_suppression("Column count of mysql.proc is wrong. Expected 20, found 19. The table is probably corrupted");
use test;
drop procedure if exists bug14233;
drop function if exists bug14233;
......@@ -11,11 +12,13 @@ create table t1 (id int);
create trigger t1_ai after insert on t1 for each row call bug14233();
alter table mysql.proc drop type;
call bug14233();
ERROR HY000: Failed to load routine test.bug14233. The table mysql.proc is missing, corrupt, or contains bad data (internal code -5)
ERROR HY000: Column count of mysql.proc is wrong. Expected 20, found 19. The table is probably corrupted
create view v1 as select bug14233_f();
ERROR HY000: Failed to load routine test.bug14233_f. The table mysql.proc is missing, corrupt, or contains bad data (internal code -5)
ERROR HY000: Column count of mysql.proc is wrong. Expected 20, found 19. The table is probably corrupted
insert into t1 values (0);
ERROR HY000: Failed to load routine test.bug14233. The table mysql.proc is missing, corrupt, or contains bad data (internal code -5)
ERROR HY000: Column count of mysql.proc is wrong. Expected 20, found 19. The table is probably corrupted
show procedure status;
ERROR HY000: Column count of mysql.proc is wrong. Expected 20, found 19. The table is probably corrupted
flush table mysql.proc;
call bug14233();
ERROR HY000: Incorrect information in file: './mysql/proc.frm'
......@@ -88,3 +91,28 @@ show procedure status where db=DATABASE();
Db Name Type Definer Modified Created Security_type Comment character_set_client collation_connection Database Collation
show function status where db=DATABASE();
Db Name Type Definer Modified Created Security_type Comment character_set_client collation_connection Database Collation
DROP TABLE IF EXISTS proc_backup;
DROP PROCEDURE IF EXISTS p1;
# Backup the proc table
RENAME TABLE mysql.proc TO proc_backup;
CREATE TABLE mysql.proc LIKE proc_backup;
FLUSH TABLE mysql.proc;
# Test with a valid table.
CREATE PROCEDURE p1()
SET @foo = 10;
CALL p1();
SHOW PROCEDURE STATUS;
Db Name Type Definer Modified Created Security_type Comment character_set_client collation_connection Database Collation
test p1 PROCEDURE root@localhost 0000-00-00 00:00:00 0000-00-00 00:00:00 DEFINER latin1 latin1_swedish_ci latin1_swedish_ci
# Modify a field of the table.
ALTER TABLE mysql.proc MODIFY comment CHAR (32);
CREATE PROCEDURE p2()
SET @foo = 10;
ERROR HY000: Cannot load from mysql.proc. The table is probably corrupted
# Procedure loaded from the cache
CALL p1();
SHOW PROCEDURE STATUS;
ERROR HY000: Cannot load from mysql.proc. The table is probably corrupted
DROP TABLE mysql.proc;
RENAME TABLE proc_backup TO mysql.proc;
FLUSH TABLE mysql.proc;
......@@ -12,6 +12,9 @@
# mysqltest should be fixed to allow REPLACE_RESULT in error message
-- source include/not_embedded.inc
# Supress warnings written to the log file
call mtr.add_suppression("Column count of mysql.proc is wrong. Expected 20, found 19. The table is probably corrupted");
# Backup proc table
let $MYSQLD_DATADIR= `select @@datadir`;
--copy_file $MYSQLD_DATADIR/mysql/proc.frm $MYSQLTEST_VARDIR/tmp/proc.frm
......@@ -38,15 +41,14 @@ create trigger t1_ai after insert on t1 for each row call bug14233();
# Unsupported tampering with the mysql.proc definition
alter table mysql.proc drop type;
--replace_result $MYSQL_TEST_DIR .
--error ER_SP_PROC_TABLE_CORRUPT
--error ER_COL_COUNT_DOESNT_MATCH_CORRUPTED
call bug14233();
--replace_result $MYSQL_TEST_DIR .
--error ER_SP_PROC_TABLE_CORRUPT
--error ER_COL_COUNT_DOESNT_MATCH_CORRUPTED
create view v1 as select bug14233_f();
--replace_result $MYSQL_TEST_DIR .
--error ER_SP_PROC_TABLE_CORRUPT
--error ER_COL_COUNT_DOESNT_MATCH_CORRUPTED
insert into t1 values (0);
--error ER_COL_COUNT_DOESNT_MATCH_CORRUPTED
show procedure status;
flush table mysql.proc;
......@@ -155,3 +157,43 @@ drop procedure bug14233_3;
# Assert: These should show nothing.
show procedure status where db=DATABASE();
show function status where db=DATABASE();
#
# Bug#41726 upgrade from 5.0 to 5.1.30 crashes if you didn't run mysql_upgrade
#
--disable_warnings
DROP TABLE IF EXISTS proc_backup;
DROP PROCEDURE IF EXISTS p1;
--enable_warnings
--echo # Backup the proc table
RENAME TABLE mysql.proc TO proc_backup;
CREATE TABLE mysql.proc LIKE proc_backup;
FLUSH TABLE mysql.proc;
--echo # Test with a valid table.
CREATE PROCEDURE p1()
SET @foo = 10;
CALL p1();
--replace_column 5 '0000-00-00 00:00:00' 6 '0000-00-00 00:00:00'
SHOW PROCEDURE STATUS;
--echo # Modify a field of the table.
ALTER TABLE mysql.proc MODIFY comment CHAR (32);
--error ER_CANNOT_LOAD_FROM_TABLE
CREATE PROCEDURE p2()
SET @foo = 10;
--echo # Procedure loaded from the cache
CALL p1();
--error ER_CANNOT_LOAD_FROM_TABLE
SHOW PROCEDURE STATUS;
DROP TABLE mysql.proc;
RENAME TABLE proc_backup TO mysql.proc;
FLUSH TABLE mysql.proc;
......@@ -26,7 +26,7 @@
*/
static
const TABLE_FIELD_W_TYPE event_table_fields[ET_FIELD_COUNT] =
const TABLE_FIELD_TYPE event_table_fields[ET_FIELD_COUNT] =
{
{
{ C_STRING_WITH_LEN("db") },
......@@ -151,6 +151,24 @@ const TABLE_FIELD_W_TYPE event_table_fields[ET_FIELD_COUNT] =
}
};
static const TABLE_FIELD_DEF
event_table_def= {ET_FIELD_COUNT, event_table_fields};
class Event_db_intact : public Table_check_intact
{
protected:
void report_error(uint, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
error_log_print(ERROR_LEVEL, fmt, args);
va_end(args);
}
};
/** In case of an error, a message is printed to the error log. */
static Event_db_intact table_intact;
/**
Puts some data common to CREATE and ALTER EVENT into a row.
......@@ -1117,10 +1135,8 @@ Event_db_repository::check_system_tables(THD *thd)
}
else
{
if (table_check_intact(tables.table, MYSQL_DB_FIELD_COUNT,
mysql_db_table_fields))
if (table_intact.check(tables.table, &mysql_db_table_def))
ret= 1;
/* in case of an error, the message is printed inside table_check_intact */
close_thread_tables(thd);
}
......@@ -1154,9 +1170,8 @@ Event_db_repository::check_system_tables(THD *thd)
}
else
{
if (table_check_intact(tables.table, ET_FIELD_COUNT, event_table_fields))
if (table_intact.check(tables.table, &event_table_def))
ret= 1;
/* in case of an error, the message is printed inside table_check_intact */
close_thread_tables(thd);
}
......
......@@ -70,6 +70,122 @@ enum
MYSQL_PROC_FIELD_COUNT
};
static const
TABLE_FIELD_TYPE proc_table_fields[MYSQL_PROC_FIELD_COUNT] =
{
{
{ C_STRING_WITH_LEN("db") },
{ C_STRING_WITH_LEN("char(64)") },
{ C_STRING_WITH_LEN("utf8") }
},
{
{ C_STRING_WITH_LEN("name") },
{ C_STRING_WITH_LEN("char(64)") },
{ C_STRING_WITH_LEN("utf8") }
},
{
{ C_STRING_WITH_LEN("type") },
{ C_STRING_WITH_LEN("enum('FUNCTION','PROCEDURE')") },
{ NULL, 0 }
},
{
{ C_STRING_WITH_LEN("specific_name") },
{ C_STRING_WITH_LEN("char(64)") },
{ C_STRING_WITH_LEN("utf8") }
},
{
{ C_STRING_WITH_LEN("language") },
{ C_STRING_WITH_LEN("enum('SQL')") },
{ NULL, 0 }
},
{
{ C_STRING_WITH_LEN("sql_data_access") },
{ C_STRING_WITH_LEN("enum('CONTAINS_SQL','NO_SQL','READS_SQL_DATA','MODIFIES_SQL_DATA')") },
{ NULL, 0 }
},
{
{ C_STRING_WITH_LEN("is_deterministic") },
{ C_STRING_WITH_LEN("enum('YES','NO')") },
{ NULL, 0 }
},
{
{ C_STRING_WITH_LEN("security_type") },
{ C_STRING_WITH_LEN("enum('INVOKER','DEFINER')") },
{ NULL, 0 }
},
{
{ C_STRING_WITH_LEN("param_list") },
{ C_STRING_WITH_LEN("blob") },
{ NULL, 0 }
},
{
{ C_STRING_WITH_LEN("returns") },
{ C_STRING_WITH_LEN("longblob") },
{ NULL, 0 }
},
{
{ C_STRING_WITH_LEN("body") },
{ C_STRING_WITH_LEN("longblob") },
{ NULL, 0 }
},
{
{ C_STRING_WITH_LEN("definer") },
{ C_STRING_WITH_LEN("char(77)") },
{ C_STRING_WITH_LEN("utf8") }
},
{
{ C_STRING_WITH_LEN("created") },
{ C_STRING_WITH_LEN("timestamp") },
{ NULL, 0 }
},
{
{ C_STRING_WITH_LEN("modified") },
{ C_STRING_WITH_LEN("timestamp") },
{ NULL, 0 }
},
{
{ C_STRING_WITH_LEN("sql_mode") },
{ C_STRING_WITH_LEN("set('REAL_AS_FLOAT','PIPES_AS_CONCAT','ANSI_QUOTES',"
"'IGNORE_SPACE','NOT_USED','ONLY_FULL_GROUP_BY','NO_UNSIGNED_SUBTRACTION',"
"'NO_DIR_IN_CREATE','POSTGRESQL','ORACLE','MSSQL','DB2','MAXDB',"
"'NO_KEY_OPTIONS','NO_TABLE_OPTIONS','NO_FIELD_OPTIONS','MYSQL323','MYSQL40',"
"'ANSI','NO_AUTO_VALUE_ON_ZERO','NO_BACKSLASH_ESCAPES','STRICT_TRANS_TABLES',"
"'STRICT_ALL_TABLES','NO_ZERO_IN_DATE','NO_ZERO_DATE','INVALID_DATES',"
"'ERROR_FOR_DIVISION_BY_ZERO','TRADITIONAL','NO_AUTO_CREATE_USER',"
"'HIGH_NOT_PRECEDENCE','NO_ENGINE_SUBSTITUTION','PAD_CHAR_TO_FULL_LENGTH')") },
{ NULL, 0 }
},
{
{ C_STRING_WITH_LEN("comment") },
{ C_STRING_WITH_LEN("char(64)") },
{ C_STRING_WITH_LEN("utf8") }
},
{
{ C_STRING_WITH_LEN("character_set_client") },
{ C_STRING_WITH_LEN("char(32)") },
{ C_STRING_WITH_LEN("utf8") }
},
{
{ C_STRING_WITH_LEN("collation_connection") },
{ C_STRING_WITH_LEN("char(32)") },
{ C_STRING_WITH_LEN("utf8") }
},
{
{ C_STRING_WITH_LEN("db_collation") },
{ C_STRING_WITH_LEN("char(32)") },
{ C_STRING_WITH_LEN("utf8") }
},
{
{ C_STRING_WITH_LEN("body_utf8") },
{ C_STRING_WITH_LEN("longblob") },
{ NULL, 0 }
}
};
static const TABLE_FIELD_DEF
proc_table_def= {MYSQL_PROC_FIELD_COUNT, proc_table_fields};
/*************************************************************************/
/**
......@@ -247,6 +363,50 @@ Stored_routine_creation_ctx::load_from_db(THD *thd,
/*************************************************************************/
class Proc_table_intact : public Table_check_intact
{
private:
bool m_print_once;
public:
Proc_table_intact() : m_print_once(TRUE) {}
protected:
void report_error(uint code, const char *fmt, ...);
};
/**
Report failure to validate the mysql.proc table definition.
Print a message to the error log only once.
*/
void Proc_table_intact::report_error(uint code, const char *fmt, ...)
{
va_list args;
char buf[512];
va_start(args, fmt);
my_vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
if (code)
my_message(code, buf, MYF(0));
else
my_error(ER_CANNOT_LOAD_FROM_TABLE, MYF(0), "proc");
if (m_print_once)
{
m_print_once= FALSE;
sql_print_error("%s", buf);
}
};
/** Single instance used to control printing to the error log. */
static Proc_table_intact proc_table_intact;
/**
Open the mysql.proc table for read.
......@@ -266,15 +426,17 @@ TABLE *open_proc_table_for_read(THD *thd, Open_tables_state *backup)
DBUG_ENTER("open_proc_table_for_read");
TABLE_LIST table;
bzero((char*) &table, sizeof(table));
table.db= (char*) "mysql";
table.table_name= table.alias= (char*)"proc";
table.lock_type= TL_READ;
table.init_one_table("mysql", "proc", TL_READ);
if (!open_system_tables_for_read(thd, &table, backup))
if (open_system_tables_for_read(thd, &table, backup))
DBUG_RETURN(NULL);
if (!proc_table_intact.check(table.table, &proc_table_def))
DBUG_RETURN(table.table);
else
DBUG_RETURN(0);
close_system_tables(thd, backup);
DBUG_RETURN(NULL);
}
......@@ -296,13 +458,19 @@ static TABLE *open_proc_table_for_update(THD *thd)
{
DBUG_ENTER("open_proc_table_for_update");
TABLE_LIST table;
bzero((char*) &table, sizeof(table));
table.db= (char*) "mysql";
table.table_name= table.alias= (char*)"proc";
table.lock_type= TL_WRITE;
TABLE *table;
TABLE_LIST table_list;
table_list.init_one_table("mysql", "proc", TL_WRITE);
if (!(table= open_system_table_for_update(thd, &table_list)))
DBUG_RETURN(NULL);
if (!proc_table_intact.check(table, &proc_table_def))
DBUG_RETURN(table);
close_thread_tables(thd);
DBUG_RETURN(open_system_table_for_update(thd, &table));
DBUG_RETURN(NULL);
}
......
......@@ -31,9 +31,8 @@
#include "sp_head.h"
#include "sp.h"
time_t mysql_db_table_last_check= 0L;
TABLE_FIELD_W_TYPE mysql_db_table_fields[MYSQL_DB_FIELD_COUNT] = {
static const
TABLE_FIELD_TYPE mysql_db_table_fields[MYSQL_DB_FIELD_COUNT] = {
{
{ C_STRING_WITH_LEN("Host") },
{ C_STRING_WITH_LEN("char(60)") },
......@@ -146,6 +145,8 @@ TABLE_FIELD_W_TYPE mysql_db_table_fields[MYSQL_DB_FIELD_COUNT] = {
}
};
const TABLE_FIELD_DEF
mysql_db_table_def= {MYSQL_DB_FIELD_COUNT, mysql_db_table_fields};
#ifndef NO_EMBEDDED_ACCESS_CHECKS
......
......@@ -159,8 +159,7 @@ enum mysql_db_table_field
MYSQL_DB_FIELD_COUNT
};
extern TABLE_FIELD_W_TYPE mysql_db_table_fields[];
extern time_t mysql_db_table_last_check;
extern const TABLE_FIELD_DEF mysql_db_table_def;
/* Classes */
......
......@@ -2811,34 +2811,38 @@ bool check_column_name(const char *name)
and such errors never reach the user.
*/
my_bool
table_check_intact(TABLE *table, const uint table_f_count,
const TABLE_FIELD_W_TYPE *table_def)
bool
Table_check_intact::check(TABLE *table, const TABLE_FIELD_DEF *table_def)
{
uint i;
my_bool error= FALSE;
my_bool fields_diff_count;
const TABLE_FIELD_TYPE *field_def= table_def->field;
DBUG_ENTER("table_check_intact");
DBUG_PRINT("info",("table: %s expected_count: %d",
table->alias, table_f_count));
table->alias, table_def->count));
fields_diff_count= (table->s->fields != table_f_count);
if (fields_diff_count)
/* Whether the table definition has already been validated. */
if (table->s->table_field_def_cache == table_def)
DBUG_RETURN(FALSE);
if (table->s->fields != table_def->count)
{
DBUG_PRINT("info", ("Column count has changed, checking the definition"));
/* previous MySQL version */
if (MYSQL_VERSION_ID > table->s->mysql_version)
{
sql_print_error(ER(ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE),
table->alias, table_f_count, table->s->fields,
report_error(ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE,
ER(ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE),
table->alias, table_def->count, table->s->fields,
table->s->mysql_version, MYSQL_VERSION_ID);
DBUG_RETURN(TRUE);
}
else if (MYSQL_VERSION_ID == table->s->mysql_version)
{
sql_print_error(ER(ER_COL_COUNT_DOESNT_MATCH_CORRUPTED), table->alias,
table_f_count, table->s->fields);
report_error(ER_COL_COUNT_DOESNT_MATCH_CORRUPTED,
ER(ER_COL_COUNT_DOESNT_MATCH_CORRUPTED), table->alias,
table_def->count, table->s->fields);
DBUG_RETURN(TRUE);
}
/*
......@@ -2850,7 +2854,7 @@ table_check_intact(TABLE *table, const uint table_f_count,
*/
}
char buffer[STRING_BUFFER_USUAL_SIZE];
for (i=0 ; i < table_f_count; i++, table_def++)
for (i=0 ; i < table_def->count; i++, field_def++)
{
String sql_type(buffer, sizeof(buffer), system_charset_info);
sql_type.length(0);
......@@ -2858,17 +2862,17 @@ table_check_intact(TABLE *table, const uint table_f_count,
{
Field *field= table->field[i];
if (strncmp(field->field_name, table_def->name.str,
table_def->name.length))
if (strncmp(field->field_name, field_def->name.str,
field_def->name.length))
{
/*
Name changes are not fatal, we use ordinal numbers to access columns.
Still this can be a sign of a tampered table, output an error
to the error log.
*/
sql_print_error("Incorrect definition of table %s.%s: "
report_error(0, "Incorrect definition of table %s.%s: "
"expected column '%s' at position %d, found '%s'.",
table->s->db.str, table->alias, table_def->name.str, i,
table->s->db.str, table->alias, field_def->name.str, i,
field->field_name);
}
field->sql_type(sql_type);
......@@ -2889,47 +2893,51 @@ table_check_intact(TABLE *table, const uint table_f_count,
the new table definition is backward compatible with the
original one.
*/
if (strncmp(sql_type.c_ptr_safe(), table_def->type.str,
table_def->type.length - 1))
if (strncmp(sql_type.c_ptr_safe(), field_def->type.str,
field_def->type.length - 1))
{
sql_print_error("Incorrect definition of table %s.%s: "
report_error(0, "Incorrect definition of table %s.%s: "
"expected column '%s' at position %d to have type "
"%s, found type %s.", table->s->db.str, table->alias,
table_def->name.str, i, table_def->type.str,
field_def->name.str, i, field_def->type.str,
sql_type.c_ptr_safe());
error= TRUE;
}
else if (table_def->cset.str && !field->has_charset())
else if (field_def->cset.str && !field->has_charset())
{
sql_print_error("Incorrect definition of table %s.%s: "
report_error(0, "Incorrect definition of table %s.%s: "
"expected the type of column '%s' at position %d "
"to have character set '%s' but the type has no "
"character set.", table->s->db.str, table->alias,
table_def->name.str, i, table_def->cset.str);
field_def->name.str, i, field_def->cset.str);
error= TRUE;
}
else if (table_def->cset.str &&
strcmp(field->charset()->csname, table_def->cset.str))
else if (field_def->cset.str &&
strcmp(field->charset()->csname, field_def->cset.str))
{
sql_print_error("Incorrect definition of table %s.%s: "
report_error(0, "Incorrect definition of table %s.%s: "
"expected the type of column '%s' at position %d "
"to have character set '%s' but found "
"character set '%s'.", table->s->db.str, table->alias,
table_def->name.str, i, table_def->cset.str,
field_def->name.str, i, field_def->cset.str,
field->charset()->csname);
error= TRUE;
}
}
else
{
sql_print_error("Incorrect definition of table %s.%s: "
report_error(0, "Incorrect definition of table %s.%s: "
"expected column '%s' at position %d to have type %s "
" but the column is not found.",
table->s->db.str, table->alias,
table_def->name.str, i, table_def->type.str);
field_def->name.str, i, field_def->type.str);
error= TRUE;
}
}
if (! error)
table->s->table_field_def_cache= table_def;
DBUG_RETURN(error);
}
......
......@@ -285,6 +285,36 @@ typedef enum enum_table_category TABLE_CATEGORY;
TABLE_CATEGORY get_table_category(const LEX_STRING *db,
const LEX_STRING *name);
typedef struct st_table_field_type
{
LEX_STRING name;
LEX_STRING type;
LEX_STRING cset;
} TABLE_FIELD_TYPE;
typedef struct st_table_field_def
{
uint count;
const TABLE_FIELD_TYPE *field;
} TABLE_FIELD_DEF;
class Table_check_intact
{
protected:
virtual void report_error(uint code, const char *fmt, ...)= 0;
public:
Table_check_intact() {}
virtual ~Table_check_intact() {}
/** Checks whether a table is intact. */
bool check(TABLE *table, const TABLE_FIELD_DEF *table_def);
};
/*
This structure is shared between different table objects. There is one
instance of table share per one table in the database.
......@@ -421,6 +451,18 @@ typedef struct st_table_share
handlerton *default_part_db_type;
#endif
/**
Cache the checked structure of this table.
The pointer data is used to describe the structure that
a instance of the table must have. Each element of the
array specifies a field that must exist on the table.
The pointer is cached in order to perform the check only
once -- when the table is loaded from the disk.
*/
const TABLE_FIELD_DEF *table_field_def_cache;
/** place to store storage engine specific data */
void *ha_data;
......@@ -1662,17 +1704,6 @@ typedef struct st_open_table_list{
uint32 in_use,locked;
} OPEN_TABLE_LIST;
typedef struct st_table_field_w_type
{
LEX_STRING name;
LEX_STRING type;
LEX_STRING cset;
} TABLE_FIELD_W_TYPE;
my_bool
table_check_intact(TABLE *table, const uint table_f_count,
const TABLE_FIELD_W_TYPE *table_def);
static inline my_bitmap_map *tmp_use_all_columns(TABLE *table,
MY_BITMAP *bitmap)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment