From 16f26d2aaf65c2d69e24b7d644cc48a628a55862 Mon Sep 17 00:00:00 2001
From: Alexander Nozdrin <alexander.nozdrin@oracle.com>
Date: Wed, 4 May 2011 16:59:24 +0400
Subject: [PATCH] Patch for Bug#12394306: the sever may crash if mysql.event is
 corrupted.

The problem was that wrong structure of mysql.event was not detected and
the server continued to use wrongly-structured data.

The fix is to check the structure of mysql.event after opening before
any use. That makes operations with events more strict -- some operations
that might work before throw errors now. That seems to be Ok.

Another side-effect of the patch is that if mysql.event is corrupted,
unrelated DROP DATABASE statements issue an SQL warning about inability
to open mysql.event table.
---
 mysql-test/r/events_1.result       | 106 ++++++++++++++++++++--------
 mysql-test/r/events_restart.result |   3 +
 mysql-test/t/events_1.test         | 109 ++++++++++++++++++++++-------
 mysql-test/t/events_restart.test   |   2 +
 sql/event_db_repository.cc         |   8 +++
 5 files changed, 172 insertions(+), 56 deletions(-)

diff --git a/mysql-test/r/events_1.result b/mysql-test/r/events_1.result
index e7b645f5556..e0c137ea877 100644
--- a/mysql-test/r/events_1.result
+++ b/mysql-test/r/events_1.result
@@ -1,3 +1,4 @@
+call mtr.add_suppression("Column count of mysql.event is wrong. Expected .*, found .*\. The table is probably corrupted");
 drop database if exists events_test;
 drop database if exists db_x;
 drop database if exists mysqltest_db2;
@@ -259,33 +260,36 @@ events_test	intact_check	root@localhost	SYSTEM	RECURRING	NULL	10	#	#	NULL	ENABLE
 Try to alter mysql.event: the server should fail to load
 event information after mysql.event was tampered with.
 
-First, let's add a column to the end and make sure everything
-works as before
+First, let's add a column to the end and check the error is emitted.
 
 ALTER TABLE mysql.event ADD dummy INT;
 SHOW EVENTS;
-Db	Name	Definer	Time zone	Type	Execute at	Interval value	Interval field	Starts	Ends	Status	Originator	character_set_client	collation_connection	Database Collation
-events_test	intact_check	root@localhost	SYSTEM	RECURRING	NULL	10	#	#	NULL	ENABLED	1	latin1	latin1_swedish_ci	latin1_swedish_ci
+ERROR HY000: Failed to open mysql.event
 SELECT event_name FROM INFORMATION_SCHEMA.events;
-event_name
-intact_check
+ERROR HY000: Failed to open mysql.event
 SHOW CREATE EVENT intact_check;
-Event	sql_mode	time_zone	Create Event	character_set_client	collation_connection	Database Collation
-intact_check		SYSTEM	CREATE EVENT `intact_check` ON SCHEDULE EVERY 10 HOUR STARTS '#' ON COMPLETION NOT PRESERVE ENABLE DO SELECT "nothing"	latin1	latin1_swedish_ci	latin1_swedish_ci
+ERROR HY000: Failed to open mysql.event
 DROP EVENT no_such_event;
-ERROR HY000: Unknown event 'no_such_event'
+ERROR HY000: Failed to open mysql.event
 CREATE EVENT intact_check_1 ON SCHEDULE EVERY 5 HOUR DO SELECT 5;
+ERROR HY000: Failed to open mysql.event
 ALTER EVENT intact_check_1 ON SCHEDULE EVERY 8 HOUR DO SELECT 8;
+ERROR HY000: Failed to open mysql.event
 ALTER EVENT intact_check_1 RENAME TO intact_check_2;
+ERROR HY000: Failed to open mysql.event
 DROP EVENT intact_check_1;
-ERROR HY000: Unknown event 'intact_check_1'
+ERROR HY000: Failed to open mysql.event
 DROP EVENT intact_check_2;
+ERROR HY000: Failed to open mysql.event
 DROP EVENT intact_check;
+ERROR HY000: Failed to open mysql.event
 DROP DATABASE IF EXISTS mysqltest_no_such_database;
 Warnings:
 Note	1008	Can't drop database 'mysqltest_no_such_database'; database doesn't exist
 CREATE DATABASE mysqltest_db2;
 DROP DATABASE mysqltest_db2;
+Warnings:
+Error	1545	Failed to open mysql.event
 SELECT @@event_scheduler;
 @@event_scheduler
 OFF
@@ -294,6 +298,7 @@ Variable_name	Value
 event_scheduler	OFF
 SET GLOBAL event_scheduler=OFF;
 ALTER TABLE mysql.event DROP dummy;
+DROP EVENT intact_check;
 CREATE EVENT intact_check ON SCHEDULE EVERY 10 HOUR DO SELECT "nothing";
 
 Now let's add a column to the first position: the server
@@ -301,30 +306,32 @@ expects to see event schema name there
 
 ALTER TABLE mysql.event ADD dummy INT FIRST;
 SHOW EVENTS;
-ERROR HY000: Cannot load from mysql.event. The table is probably corrupted
+ERROR HY000: Failed to open mysql.event
 SELECT event_name FROM INFORMATION_SCHEMA.events;
-ERROR HY000: Cannot load from mysql.event. The table is probably corrupted
+ERROR HY000: Failed to open mysql.event
 SHOW CREATE EVENT intact_check;
-ERROR HY000: Unknown event 'intact_check'
+ERROR HY000: Failed to open mysql.event
 DROP EVENT no_such_event;
-ERROR HY000: Unknown event 'no_such_event'
+ERROR HY000: Failed to open mysql.event
 CREATE EVENT intact_check_1 ON SCHEDULE EVERY 5 HOUR DO SELECT 5;
-ERROR HY000: Failed to store event name. Error code 2 from storage engine.
+ERROR HY000: Failed to open mysql.event
 ALTER EVENT intact_check_1 ON SCHEDULE EVERY 8 HOUR DO SELECT 8;
-ERROR HY000: Unknown event 'intact_check_1'
+ERROR HY000: Failed to open mysql.event
 ALTER EVENT intact_check_1 RENAME TO intact_check_2;
-ERROR HY000: Unknown event 'intact_check_1'
+ERROR HY000: Failed to open mysql.event
 DROP EVENT intact_check_1;
-ERROR HY000: Unknown event 'intact_check_1'
+ERROR HY000: Failed to open mysql.event
 DROP EVENT intact_check_2;
-ERROR HY000: Unknown event 'intact_check_2'
+ERROR HY000: Failed to open mysql.event
 DROP EVENT intact_check;
-ERROR HY000: Unknown event 'intact_check'
+ERROR HY000: Failed to open mysql.event
 DROP DATABASE IF EXISTS mysqltest_no_such_database;
 Warnings:
 Note	1008	Can't drop database 'mysqltest_no_such_database'; database doesn't exist
 CREATE DATABASE mysqltest_db2;
 DROP DATABASE mysqltest_db2;
+Warnings:
+Error	1545	Failed to open mysql.event
 SELECT @@event_scheduler;
 @@event_scheduler
 OFF
@@ -345,29 +352,32 @@ Drop some columns and try more checks.
 
 ALTER TABLE mysql.event DROP comment, DROP starts;
 SHOW EVENTS;
-ERROR HY000: Cannot load from mysql.event. The table is probably corrupted
+ERROR HY000: Failed to open mysql.event
 SELECT event_name FROM INFORMATION_SCHEMA.EVENTS;
-ERROR HY000: Cannot load from mysql.event. The table is probably corrupted
+ERROR HY000: Failed to open mysql.event
 SHOW CREATE EVENT intact_check;
-ERROR HY000: Cannot load from mysql.event. The table is probably corrupted
+ERROR HY000: Failed to open mysql.event
 DROP EVENT no_such_event;
-ERROR HY000: Unknown event 'no_such_event'
+ERROR HY000: Failed to open mysql.event
 CREATE EVENT intact_check_1 ON SCHEDULE EVERY 5 HOUR DO SELECT 5;
-ERROR HY000: Column count of mysql.event is wrong. Expected 22, found 20. The table is probably corrupted
+ERROR HY000: Failed to open mysql.event
 ALTER EVENT intact_check_1 ON SCHEDULE EVERY 8 HOUR DO SELECT 8;
-ERROR HY000: Unknown event 'intact_check_1'
+ERROR HY000: Failed to open mysql.event
 ALTER EVENT intact_check_1 RENAME TO intact_check_2;
-ERROR HY000: Unknown event 'intact_check_1'
+ERROR HY000: Failed to open mysql.event
 DROP EVENT intact_check_1;
-ERROR HY000: Unknown event 'intact_check_1'
+ERROR HY000: Failed to open mysql.event
 DROP EVENT intact_check_2;
-ERROR HY000: Unknown event 'intact_check_2'
+ERROR HY000: Failed to open mysql.event
 DROP EVENT intact_check;
+ERROR HY000: Failed to open mysql.event
 DROP DATABASE IF EXISTS mysqltest_no_such_database;
 Warnings:
 Note	1008	Can't drop database 'mysqltest_no_such_database'; database doesn't exist
 CREATE DATABASE mysqltest_db2;
 DROP DATABASE mysqltest_db2;
+Warnings:
+Error	1545	Failed to open mysql.event
 SELECT @@event_scheduler;
 @@event_scheduler
 OFF
@@ -425,4 +435,42 @@ CREATE TABLE mysql.event like event_like;
 DROP TABLE event_like;
 SHOW EVENTS;
 Db	Name	Definer	Time zone	Type	Execute at	Interval value	Interval field	Starts	Ends	Status	Originator	character_set_client	collation_connection	Database Collation
+
+#
+# Bug#12394306: the sever may crash if mysql.event is corrupted
+#
+
+CREATE EVENT ev1 ON SCHEDULE EVERY 5 HOUR DO SELECT 5;
+ALTER EVENT ev1 ON SCHEDULE EVERY 8 HOUR DO SELECT 8;
+
+CREATE TABLE event_original LIKE mysql.event;
+INSERT INTO event_original SELECT * FROM mysql.event;
+
+ALTER TABLE mysql.event MODIFY modified CHAR(1);
+Warnings:
+Warning	1265	Data truncated for column 'modified' at row 1
+
+SHOW EVENTS;
+ERROR HY000: Failed to open mysql.event
+
+SELECT event_name, created, last_altered FROM information_schema.events;
+ERROR HY000: Failed to open mysql.event
+
+CREATE EVENT ev2 ON SCHEDULE EVERY 5 HOUR DO SELECT 5;
+ERROR HY000: Failed to open mysql.event
+
+ALTER EVENT ev1 ON SCHEDULE EVERY 9 HOUR DO SELECT 9;
+ERROR HY000: Failed to open mysql.event
+
+DROP TABLE mysql.event;
+RENAME TABLE event_original TO mysql.event;
+
+DROP EVENT ev1;
+
+SHOW EVENTS;
+Db	Name	Definer	Time zone	Type	Execute at	Interval value	Interval field	Starts	Ends	Status	Originator	character_set_client	collation_connection	Database Collation
+
+# 
+# End of tests
+#
 drop database events_test;
diff --git a/mysql-test/r/events_restart.result b/mysql-test/r/events_restart.result
index 4db61d357ce..6a751fa29f8 100644
--- a/mysql-test/r/events_restart.result
+++ b/mysql-test/r/events_restart.result
@@ -1,3 +1,4 @@
+call mtr.add_suppression("Column count of mysql.event is wrong. Expected .*, found .*\. The table is probably corrupted");
 set global event_scheduler=off;
 drop database if exists events_test;
 create database events_test;
@@ -52,6 +53,8 @@ Warnings:
 Note	1008	Can't drop database 'mysqltest_database_not_exists'; database doesn't exist
 create database mysqltest_db1;
 drop database mysqltest_db1;
+Warnings:
+Error	1545	Failed to open mysql.event
 Restore the original mysql.event table
 drop table mysql.event;
 rename table event_like to mysql.event;
diff --git a/mysql-test/t/events_1.test b/mysql-test/t/events_1.test
index ccdeb70d291..7f31e3fc881 100644
--- a/mysql-test/t/events_1.test
+++ b/mysql-test/t/events_1.test
@@ -4,6 +4,8 @@
 # Can't test with embedded server that doesn't support grants
 -- source include/not_embedded.inc
 
+call mtr.add_suppression("Column count of mysql.event is wrong. Expected .*, found .*\. The table is probably corrupted");
+
 --disable_warnings
 drop database if exists events_test;
 drop database if exists db_x;
@@ -270,23 +272,28 @@ SHOW EVENTS;
 --echo Try to alter mysql.event: the server should fail to load
 --echo event information after mysql.event was tampered with.
 --echo 
---echo First, let's add a column to the end and make sure everything
---echo works as before
+--echo First, let's add a column to the end and check the error is emitted.
 --echo 
 ALTER TABLE mysql.event ADD dummy INT;
---replace_column 8 # 9 #
+--error ER_EVENT_OPEN_TABLE_FAILED
 SHOW EVENTS;
+--error ER_EVENT_OPEN_TABLE_FAILED
 SELECT event_name FROM INFORMATION_SCHEMA.events;
---replace_regex /STARTS '[^']+'/STARTS '#'/
+--error ER_EVENT_OPEN_TABLE_FAILED
 SHOW CREATE EVENT intact_check;
---error ER_EVENT_DOES_NOT_EXIST
+--error ER_EVENT_OPEN_TABLE_FAILED
 DROP EVENT no_such_event;
+--error ER_EVENT_OPEN_TABLE_FAILED
 CREATE EVENT intact_check_1 ON SCHEDULE EVERY 5 HOUR DO SELECT 5;
+--error ER_EVENT_OPEN_TABLE_FAILED
 ALTER EVENT intact_check_1 ON SCHEDULE EVERY 8 HOUR DO SELECT 8;
+--error ER_EVENT_OPEN_TABLE_FAILED
 ALTER EVENT intact_check_1 RENAME TO intact_check_2;
---error ER_EVENT_DOES_NOT_EXIST
+--error ER_EVENT_OPEN_TABLE_FAILED
 DROP EVENT intact_check_1;
+--error ER_EVENT_OPEN_TABLE_FAILED
 DROP EVENT intact_check_2;
+--error ER_EVENT_OPEN_TABLE_FAILED
 DROP EVENT intact_check;
 DROP DATABASE IF EXISTS mysqltest_no_such_database;
 CREATE DATABASE mysqltest_db2;
@@ -296,6 +303,7 @@ SHOW VARIABLES LIKE 'event_scheduler';
 SET GLOBAL event_scheduler=OFF;
 # Clean up
 ALTER TABLE mysql.event DROP dummy;
+DROP EVENT intact_check;
 CREATE EVENT intact_check ON SCHEDULE EVERY 10 HOUR DO SELECT "nothing";
 --echo 
 --echo Now let's add a column to the first position: the server
@@ -303,24 +311,26 @@ CREATE EVENT intact_check ON SCHEDULE EVERY 10 HOUR DO SELECT "nothing";
 --echo 
 ALTER TABLE mysql.event ADD dummy INT FIRST;
 --error ER_CANNOT_LOAD_FROM_TABLE
+--error ER_EVENT_OPEN_TABLE_FAILED
 SHOW EVENTS;
 --error ER_CANNOT_LOAD_FROM_TABLE
+--error ER_EVENT_OPEN_TABLE_FAILED
 SELECT event_name FROM INFORMATION_SCHEMA.events;
---error ER_EVENT_DOES_NOT_EXIST
+--error ER_EVENT_OPEN_TABLE_FAILED
 SHOW CREATE EVENT intact_check;
---error ER_EVENT_DOES_NOT_EXIST
+--error ER_EVENT_OPEN_TABLE_FAILED
 DROP EVENT no_such_event;
---error ER_EVENT_STORE_FAILED
+--error ER_EVENT_OPEN_TABLE_FAILED
 CREATE EVENT intact_check_1 ON SCHEDULE EVERY 5 HOUR DO SELECT 5;
---error ER_EVENT_DOES_NOT_EXIST
+--error ER_EVENT_OPEN_TABLE_FAILED
 ALTER EVENT intact_check_1 ON SCHEDULE EVERY 8 HOUR DO SELECT 8;
---error ER_EVENT_DOES_NOT_EXIST
+--error ER_EVENT_OPEN_TABLE_FAILED
 ALTER EVENT intact_check_1 RENAME TO intact_check_2;
---error ER_EVENT_DOES_NOT_EXIST
+--error ER_EVENT_OPEN_TABLE_FAILED
 DROP EVENT intact_check_1;
---error ER_EVENT_DOES_NOT_EXIST
+--error ER_EVENT_OPEN_TABLE_FAILED
 DROP EVENT intact_check_2;
---error ER_EVENT_DOES_NOT_EXIST
+--error ER_EVENT_OPEN_TABLE_FAILED
 DROP EVENT intact_check;
 # Should work OK
 DROP DATABASE IF EXISTS mysqltest_no_such_database;
@@ -341,25 +351,25 @@ INSERT INTO event_like SELECT * FROM mysql.event;
 --echo
 --echo 
 ALTER TABLE mysql.event DROP comment, DROP starts;
---error ER_CANNOT_LOAD_FROM_TABLE
+--error ER_EVENT_OPEN_TABLE_FAILED
 SHOW EVENTS;
---error ER_CANNOT_LOAD_FROM_TABLE
+--error ER_EVENT_OPEN_TABLE_FAILED
 SELECT event_name FROM INFORMATION_SCHEMA.EVENTS;
---error ER_CANNOT_LOAD_FROM_TABLE
+--error ER_EVENT_OPEN_TABLE_FAILED
 SHOW CREATE EVENT intact_check;
---error ER_EVENT_DOES_NOT_EXIST
+--error ER_EVENT_OPEN_TABLE_FAILED
 DROP EVENT no_such_event;
---error ER_COL_COUNT_DOESNT_MATCH_CORRUPTED
+--error ER_EVENT_OPEN_TABLE_FAILED
 CREATE EVENT intact_check_1 ON SCHEDULE EVERY 5 HOUR DO SELECT 5;
---error ER_EVENT_DOES_NOT_EXIST
+--error ER_EVENT_OPEN_TABLE_FAILED
 ALTER EVENT intact_check_1 ON SCHEDULE EVERY 8 HOUR DO SELECT 8;
---error ER_EVENT_DOES_NOT_EXIST
+--error ER_EVENT_OPEN_TABLE_FAILED
 ALTER EVENT intact_check_1 RENAME TO intact_check_2;
---error ER_EVENT_DOES_NOT_EXIST
+--error ER_EVENT_OPEN_TABLE_FAILED
 DROP EVENT intact_check_1;
---error ER_EVENT_DOES_NOT_EXIST
+--error ER_EVENT_OPEN_TABLE_FAILED
 DROP EVENT intact_check_2;
-# Should succeed
+--error ER_EVENT_OPEN_TABLE_FAILED
 DROP EVENT intact_check;
 DROP DATABASE IF EXISTS mysqltest_no_such_database;
 CREATE DATABASE mysqltest_db2;
@@ -407,9 +417,54 @@ CREATE TABLE mysql.event like event_like;
 DROP TABLE event_like;
 --replace_column 8 # 9 #
 SHOW EVENTS;
-# 
-# End of tests
-#
+
+--echo
+--echo #
+--echo # Bug#12394306: the sever may crash if mysql.event is corrupted
+--echo #
+
+--echo
+CREATE EVENT ev1 ON SCHEDULE EVERY 5 HOUR DO SELECT 5;
+ALTER EVENT ev1 ON SCHEDULE EVERY 8 HOUR DO SELECT 8;
+
+--echo
+CREATE TABLE event_original LIKE mysql.event;
+INSERT INTO event_original SELECT * FROM mysql.event;
+
+--echo
+ALTER TABLE mysql.event MODIFY modified CHAR(1);
+
+--echo
+--error ER_EVENT_OPEN_TABLE_FAILED
+SHOW EVENTS;
+
+--echo
+--error ER_EVENT_OPEN_TABLE_FAILED
+SELECT event_name, created, last_altered FROM information_schema.events;
+
+--echo
+--error ER_EVENT_OPEN_TABLE_FAILED
+CREATE EVENT ev2 ON SCHEDULE EVERY 5 HOUR DO SELECT 5;
+
+--echo
+--error ER_EVENT_OPEN_TABLE_FAILED
+ALTER EVENT ev1 ON SCHEDULE EVERY 9 HOUR DO SELECT 9;
+
+--echo
+DROP TABLE mysql.event;
+RENAME TABLE event_original TO mysql.event;
+
+--echo
+DROP EVENT ev1;
+
+--echo
+SHOW EVENTS;
+
+
+--echo
+--echo # 
+--echo # End of tests
+--echo #
 
 let $wait_condition=
   select count(*) = 0 from information_schema.processlist
diff --git a/mysql-test/t/events_restart.test b/mysql-test/t/events_restart.test
index e155fe2ea16..facf2912087 100644
--- a/mysql-test/t/events_restart.test
+++ b/mysql-test/t/events_restart.test
@@ -1,6 +1,8 @@
 # Can't test with embedded server that doesn't support grants
 -- source include/not_embedded.inc
 
+call mtr.add_suppression("Column count of mysql.event is wrong. Expected .*, found .*\. The table is probably corrupted");
+
 #
 # Test that when the server is restarted, it checks mysql.event table,
 # and disables the scheduler if it's not up to date.
diff --git a/sql/event_db_repository.cc b/sql/event_db_repository.cc
index 7473cf47188..a0765dc6d15 100644
--- a/sql/event_db_repository.cc
+++ b/sql/event_db_repository.cc
@@ -582,6 +582,14 @@ Event_db_repository::open_event_table(THD *thd, enum thr_lock_type lock_type,
 
   *table= tables.table;
   tables.table->use_all_columns();
+
+  if (table_intact.check(*table, &event_table_def))
+  {
+    close_thread_tables(thd);
+    my_error(ER_EVENT_OPEN_TABLE_FAILED, MYF(0));
+    DBUG_RETURN(TRUE);
+  }
+
   DBUG_RETURN(FALSE);
 }