Commit b6e4b103 authored by unknown's avatar unknown

Patch for BUG#31111: --read-only crashes MySQL (events fail to load).

There actually were several problems here:
  - WRITE-lock is required to load events from the mysql.event table,
    but in the read-only mode an ordinary user can not acquire it;
  - Security_context::master_access attribute was not properly
    initialized in Security_context::init(), which led to differences
    in behavior with and without debug configure options.
  - if the server failed to load events from mysql.event, it forgot to
    close the mysql.event table, that led to the coredump, described
    in the bug report.

The patch is to fix all these problems:
  - Use the super-user to acquire WRITE-lock on the mysql.even table;
  - The WRITE-lock is acquired by the event scheduler in two cases:
    - on initial loading of events from the database;
    - when an event has been executed, so its attributes should
      be updated.
    Other cases when WRITE-lock is needed for the mysql.event table
    happen under the user account. So, nothing should be changed there
    for the read-only mode. The user is able to create/update/drop
    an event only if he is a super-user.
  - Initialize Security_context::master_access;
  - Close the mysql.event table in case something went wrong.


mysql-test/r/events_bugs.result:
  Update result file.
mysql-test/t/events_bugs.test:
  A test case for BUG#31111: --read-only crashes MySQL (events fail
  to load).
sql/event_data_objects.cc:
  When the worker thread is going to drop event after the execution
  we should do it under the super-user privileges in order to be able
  to lock the mysql.event table for writing in the read-only mode.
  
  This is a system operation, where user SQL can not be executed.
  So, there is no risk in compromising security by dropping an event
  under the super-user privileges.
sql/event_db_repository.cc:
  1. Close tables if something went wrong in simple_open_n_lock_tables();
  2. As soon as the system event scheduler thread is running under
     the super-user privileges, we should always be able to acquire
     WRITE-lock on the mysql.event table. However, let's have an assert
     to check this.
sql/event_scheduler.cc:
  Run the system event scheduler thread under the super-user privileges.
  In particular, this is needed to be able to lock the mysql.event table
  for writing when the server is running in the read-only mode.
  
  The event scheduler executes only system operations and does not
  execute user SQL (this is what the worker threads for). So, there
  is no risk in compromising security by running the event scheduler
  under the super-user privileges.
sql/events.cc:
  Open the mysql.event table as the super user to be able to acquire
  WRITE-lock in the read-only mode.
sql/sql_class.cc:
  Initialize Security_context::master_acces.
parent 9248b192
......@@ -610,7 +610,6 @@ id ev_nm ev_cnt
6 ev_sched_1823 6
DROP TABLE event_log;
SET GLOBAL event_scheduler = OFF;
DROP DATABASE events_test;
SET GLOBAL event_scheduler= ON;
CREATE EVENT bug28641 ON SCHEDULE AT '2038.01.18 03:00:00'
DO BEGIN
......@@ -618,3 +617,105 @@ SELECT 1;
END;|
SET GLOBAL event_scheduler= OFF;
DROP EVENT bug28641;
#####################################################################
#
# BUG#31111: --read-only crashes MySQL (events fail to load).
#
#####################################################################
DROP USER mysqltest_u1@localhost;
DROP EVENT IF EXISTS e1;
DROP EVENT IF EXISTS e2;
GRANT EVENT ON *.* TO mysqltest_u1@localhost;
SET GLOBAL READ_ONLY = 1;
#
# Connection: u1_con (mysqltest_u1@localhost/events_test).
#
CREATE EVENT e1 ON SCHEDULE AT '2020-01-01 00:00:00' DO SET @a = 1;
ERROR HY000: The MySQL server is running with the --read-only option so it cannot execute this statement
ALTER EVENT e1 COMMENT 'comment';
ERROR HY000: The MySQL server is running with the --read-only option so it cannot execute this statement
DROP EVENT e1;
ERROR HY000: The MySQL server is running with the --read-only option so it cannot execute this statement
#
# Connection: root_con (root@localhost/events_test).
#
CREATE EVENT e1 ON SCHEDULE AT '2020-01-01 00:00:00' DO SET @a = 1;
ALTER EVENT e1 COMMENT 'comment';
DROP EVENT e1;
SET GLOBAL READ_ONLY = 0;
#
# Connection: u1_con (mysqltest_u1@localhost/test).
#
CREATE EVENT e1 ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 SECOND DO SET @a = 1;
CREATE EVENT e2 ON SCHEDULE EVERY 1 SECOND DO SET @a = 1;
SELECT
event_name,
last_executed IS NULL,
definer
FROM INFORMATION_SCHEMA.EVENTS
WHERE event_schema = 'events_test';
event_name last_executed IS NULL definer
e1 1 mysqltest_u1@localhost
e2 1 mysqltest_u1@localhost
#
# Connection: root_con (root@localhost/events_test).
#
SET GLOBAL READ_ONLY = 1;
SET GLOBAL EVENT_SCHEDULER = ON;
# Waiting for the event scheduler to execute and drop event e1...
# Waiting for the event scheduler to execute and update event e2...
SET GLOBAL EVENT_SCHEDULER = OFF;
SELECT
event_name,
last_executed IS NULL,
definer
FROM INFORMATION_SCHEMA.EVENTS
WHERE event_schema = 'events_test';
event_name last_executed IS NULL definer
e2 0 mysqltest_u1@localhost
DROP EVENT e1;
ERROR HY000: Unknown event 'e1'
# Cleanup.
DROP EVENT e2;
SET GLOBAL READ_ONLY = 0;
#
# Connection: default
#
DROP USER mysqltest_u1@localhost;
#####################################################################
#
# End of BUG#31111.
#
#####################################################################
DROP DATABASE events_test;
......@@ -712,18 +712,6 @@ DROP TABLE event_log;
#DROP DATABASE ev_db_1;
SET GLOBAL event_scheduler = OFF;
#
# End of tests
#
let $wait_condition=
select count(*) = 0 from information_schema.processlist
where db='events_test' and command = 'Connect' and user=current_user();
--source include/wait_condition.inc
DROP DATABASE events_test;
#
# Bug#28641 CREATE EVENT with '2038.01.18 03:00:00' let server crash.
#
......@@ -737,3 +725,215 @@ CREATE EVENT bug28641 ON SCHEDULE AT '2038.01.18 03:00:00'
DELIMITER ;|
SET GLOBAL event_scheduler= OFF;
DROP EVENT bug28641;
###########################################################################
--echo
--echo #####################################################################
--echo #
--echo # BUG#31111: --read-only crashes MySQL (events fail to load).
--echo #
--echo #####################################################################
--echo
--error 0,ER_CANNOT_USER
DROP USER mysqltest_u1@localhost;
--disable_warnings
DROP EVENT IF EXISTS e1;
DROP EVENT IF EXISTS e2;
--enable_warnings
--echo
# Check that an ordinary user can not create/update/drop events in the
# read-only mode.
GRANT EVENT ON *.* TO mysqltest_u1@localhost;
--echo
SET GLOBAL READ_ONLY = 1;
--echo
--echo #
--echo # Connection: u1_con (mysqltest_u1@localhost/events_test).
--echo #
--connect(u1_con,localhost,mysqltest_u1,,events_test)
--echo
--error ER_OPTION_PREVENTS_STATEMENT
CREATE EVENT e1 ON SCHEDULE AT '2020-01-01 00:00:00' DO SET @a = 1;
--echo
--error ER_OPTION_PREVENTS_STATEMENT
ALTER EVENT e1 COMMENT 'comment';
--echo
--error ER_OPTION_PREVENTS_STATEMENT
DROP EVENT e1;
--echo
# Check that the super user still can create/update/drop events.
--echo #
--echo # Connection: root_con (root@localhost/events_test).
--echo #
--connect(root_con,localhost,root,,events_test)
--echo
CREATE EVENT e1 ON SCHEDULE AT '2020-01-01 00:00:00' DO SET @a = 1;
--echo
ALTER EVENT e1 COMMENT 'comment';
--echo
DROP EVENT e1;
--echo
#
# Switch to read-write mode; create test events under the user mysqltest_u1;
# switch back to read-only mode.
#
SET GLOBAL READ_ONLY = 0;
--echo
--echo #
--echo # Connection: u1_con (mysqltest_u1@localhost/test).
--echo #
--connection u1_con
--echo
CREATE EVENT e1 ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 SECOND DO SET @a = 1;
CREATE EVENT e2 ON SCHEDULE EVERY 1 SECOND DO SET @a = 1;
--echo
SELECT
event_name,
last_executed IS NULL,
definer
FROM INFORMATION_SCHEMA.EVENTS
WHERE event_schema = 'events_test';
--echo
--echo #
--echo # Connection: root_con (root@localhost/events_test).
--echo #
--connection root_con
--echo
SET GLOBAL READ_ONLY = 1;
# Check that the event scheduler is able to update event.
--echo
SET GLOBAL EVENT_SCHEDULER = ON;
--echo
--echo # Waiting for the event scheduler to execute and drop event e1...
let $wait_timeout = 2;
let $wait_condition =
SELECT COUNT(*) = 0
FROM INFORMATION_SCHEMA.EVENTS
WHERE event_schema = 'events_test' AND event_name = 'e1';
--source include/wait_condition.inc
--echo
--echo # Waiting for the event scheduler to execute and update event e2...
let $wait_condition =
SELECT last_executed IS NOT NULL
FROM INFORMATION_SCHEMA.EVENTS
WHERE event_schema = 'events_test' AND event_name = 'e2';
--source include/wait_condition.inc
--echo
SET GLOBAL EVENT_SCHEDULER = OFF;
--echo
SELECT
event_name,
last_executed IS NULL,
definer
FROM INFORMATION_SCHEMA.EVENTS
WHERE event_schema = 'events_test';
--echo
--error ER_EVENT_DOES_NOT_EXIST
DROP EVENT e1;
--echo
--echo # Cleanup.
--echo
DROP EVENT e2;
--echo
SET GLOBAL READ_ONLY = 0;
--echo
--echo #
--echo # Connection: default
--echo #
--disconnect u1_con
--disconnect root_con
--connection default
--echo
DROP USER mysqltest_u1@localhost;
--echo
--echo #####################################################################
--echo #
--echo # End of BUG#31111.
--echo #
--echo #####################################################################
--echo
###########################################################################
#
# End of tests
#
# !!! KEEP this section AT THE END of this file !!!
#
###########################################################################
let $wait_condition=
select count(*) = 0 from information_schema.processlist
where db='events_test' and command = 'Connect' and user=current_user();
--source include/wait_condition.inc
DROP DATABASE events_test;
# THIS MUST BE THE LAST LINE in this file.
......@@ -2017,6 +2017,7 @@ Event_job_data::execute(THD *thd, bool drop)
ret= 1;
else
{
ulong saved_master_access;
/*
Peculiar initialization order is a crutch to avoid races in SHOW
PROCESSLIST which reads thd->{query/query_length} without a mutex.
......@@ -2024,8 +2025,19 @@ Event_job_data::execute(THD *thd, bool drop)
thd->query_length= 0;
thd->query= sp_sql.c_ptr_safe();
thd->query_length= sp_sql.length();
if (Events::drop_event(thd, dbname, name, FALSE))
ret= 1;
/*
NOTE: even if we run in read-only mode, we should be able to lock
the mysql.event table for writing. In order to achieve this, we
should call mysql_lock_tables() under the super-user.
*/
saved_master_access= thd->security_ctx->master_access;
thd->security_ctx->master_access |= SUPER_ACL;
ret= Events::drop_event(thd, dbname, name, FALSE);
thd->security_ctx->master_access= saved_master_access;
}
}
#ifndef NO_EMBEDDED_ACCESS_CHECKS
......
......@@ -525,6 +525,10 @@ Event_db_repository::fill_schema_events(THD *thd, TABLE_LIST *tables,
- whether this open mode would work under LOCK TABLES, or inside a
stored function or trigger.
Note that if the table can't be locked successfully this operation will
close it. Therefore it provides guarantee that it either opens and locks
table or fails without leaving any tables open.
@param[in] thd Thread context
@param[in] lock_type How to lock the table
@param[out] table We will store the open table here
......@@ -544,7 +548,10 @@ Event_db_repository::open_event_table(THD *thd, enum thr_lock_type lock_type,
tables.init_one_table("mysql", "event", lock_type);
if (simple_open_n_lock_tables(thd, &tables))
{
close_thread_tables(thd, FALSE, FALSE);
DBUG_RETURN(TRUE);
}
*table= tables.table;
tables.table->use_all_columns();
......@@ -995,6 +1002,8 @@ update_timing_fields_for_event(THD *thd,
if (thd->current_stmt_binlog_row_based)
thd->clear_current_stmt_binlog_row_based();
DBUG_ASSERT(thd->security_ctx->master_access & SUPER_ACL);
if (open_event_table(thd, TL_WRITE, &table))
goto end;
......
......@@ -399,6 +399,13 @@ Event_scheduler::start()
new_thd->system_thread= SYSTEM_THREAD_EVENT_SCHEDULER;
new_thd->command= COM_DAEMON;
/*
We should run the event scheduler thread under the super-user privileges.
In particular, this is needed to be able to lock the mysql.event table
for writing when the server is running in the read-only mode.
*/
new_thd->security_ctx->master_access |= SUPER_ACL;
scheduler_param_value=
(struct scheduler_param *)my_malloc(sizeof(struct scheduler_param), MYF(0));
scheduler_param_value->thd= new_thd;
......
......@@ -1124,11 +1124,25 @@ Events::load_events_from_db(THD *thd)
READ_RECORD read_record_info;
bool ret= TRUE;
uint count= 0;
ulong saved_master_access;
DBUG_ENTER("Events::load_events_from_db");
DBUG_PRINT("enter", ("thd: 0x%lx", (long) thd));
if (db_repository->open_event_table(thd, TL_WRITE, &table))
/*
NOTE: even if we run in read-only mode, we should be able to lock the
mysql.event table for writing. In order to achieve this, we should call
mysql_lock_tables() under the super user.
*/
saved_master_access= thd->security_ctx->master_access;
thd->security_ctx->master_access |= SUPER_ACL;
ret= db_repository->open_event_table(thd, TL_WRITE, &table);
thd->security_ctx->master_access= saved_master_access;
if (ret)
{
sql_print_error("Event Scheduler: Failed to open table mysql.event");
DBUG_RETURN(TRUE);
......
......@@ -2404,6 +2404,7 @@ void Security_context::init()
host= user= priv_user= ip= 0;
host_or_ip= "connecting host";
priv_host[0]= '\0';
master_access= 0;
#ifndef NO_EMBEDDED_ACCESS_CHECKS
db_access= NO_ACCESS;
#endif
......
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