mirror of
https://github.com/MariaDB/server.git
synced 2025-01-16 20:12:31 +01:00
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.
This commit is contained in:
parent
edec79d60a
commit
c60397ef19
7 changed files with 360 additions and 16 deletions
|
@ -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 @@ end_no_lex_start:
|
|||
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 @@ end_no_lex_start:
|
|||
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
|
||||
|
|
Loading…
Reference in a new issue