Commit 5b497b16 authored by unknown's avatar unknown

fix for bug #17289 Events: missing privilege check for drop database

Events were executed with all privileges possible on planet Earth :(
WL#1034


mysql-test/r/events.result:
  update test results
mysql-test/t/events.test:
  test for bug#17289 Events: missing privilege check for drop database
sql/event.h:
  -add two new methods to event_timed to change and restore
   the security context
sql/event_executor.cc:
  - move code regarding privilieges checking to event_timed::execute()
  - add a new function evex_print_warnings() which prints the notes/warnings/errors
    to the console (easily capturable with logs-into-tables) so one can see what
    has happened if there was an error of some sort!
sql/event_timed.cc:
  - fix documentation
  - add a new error code -99, EVENT was revoked from the user on the DB
  - set_sec_ctx, execute, restore_sex_ctx
sql/sql_error.cc:
  - make warning_level_names public to be used in event_executor.cc
  - change from 2 arrays to a LEX_STRING array
parent 094fcd26
create database if not exists events_test; create database if not exists events_test;
use events_test; use events_test;
CREATE USER pauline@localhost;
CREATE DATABASE db_x;
GRANT EVENT ON db_x.* TO pauline@localhost;
USE db_x;
CREATE TABLE x_table(a int);
CREATE EVENT e_x1 ON SCHEDULE EVERY 1 SECOND DO DROP DATABASE db_x;
CREATE EVENT e_x2 ON SCHEDULE EVERY 1 SECOND DO DROP TABLE x_table;
SHOW DATABASES LIKE 'db_x';
Database (db_x)
db_x
SET GLOBAL event_scheduler=1;
SHOW DATABASES LIKE 'db_x';
Database (db_x)
db_x
SHOW TABLES FROM db_x;
Tables_in_db_x
x_table
SET GLOBAL event_scheduler=0;
DROP EVENT e_x1;
DROP EVENT e_x2;
DROP DATABASE db_x;
DROP USER pauline@localhost;
USE events_test;
drop event if exists event1; drop event if exists event1;
Warnings: Warnings:
Note 1305 Event event1 does not exist Note 1305 Event event1 does not exist
...@@ -166,7 +189,7 @@ show processlist; ...@@ -166,7 +189,7 @@ show processlist;
Id User Host db Command Time State Info Id User Host db Command Time State Info
# root localhost events_test Query # NULL show processlist # root localhost events_test Query # NULL show processlist
# event_scheduler NULL Connect # Sleeping NULL # event_scheduler NULL Connect # Sleeping NULL
# root events_test Connect # User lock select get_lock("test_lock2", 20) # root localhost events_test Connect # User lock select get_lock("test_lock2", 20)
"Release the mutex, the event worker should finish." "Release the mutex, the event worker should finish."
select release_lock("test_lock2"); select release_lock("test_lock2");
release_lock("test_lock2") release_lock("test_lock2")
...@@ -184,6 +207,8 @@ set global event_scheduler=0; ...@@ -184,6 +207,8 @@ set global event_scheduler=0;
show processlist; show processlist;
Id User Host db Command Time State Info Id User Host db Command Time State Info
# root localhost events_test Query # NULL show processlist # root localhost events_test Query # NULL show processlist
# event_scheduler NULL Connect # Sleeping NULL
# root localhost events_test Connect # User lock select get_lock("test_lock2_1", 20)
"Release the lock so the child process should finish. Hence the scheduler also" "Release the lock so the child process should finish. Hence the scheduler also"
select release_lock("test_lock2_1"); select release_lock("test_lock2_1");
release_lock("test_lock2_1") release_lock("test_lock2_1")
......
create database if not exists events_test; create database if not exists events_test;
use events_test; use events_test;
#
# START: BUG #17289 Events: missing privilege check for drop database
#
CREATE USER pauline@localhost;
CREATE DATABASE db_x;
GRANT EVENT ON db_x.* TO pauline@localhost;
USE db_x;
CREATE TABLE x_table(a int);
connect (priv_conn,localhost,pauline,,db_x);
CREATE EVENT e_x1 ON SCHEDULE EVERY 1 SECOND DO DROP DATABASE db_x;
CREATE EVENT e_x2 ON SCHEDULE EVERY 1 SECOND DO DROP TABLE x_table;
connection default;
SHOW DATABASES LIKE 'db_x';
SET GLOBAL event_scheduler=1;
--sleep 2
SHOW DATABASES LIKE 'db_x';
SHOW TABLES FROM db_x;
SET GLOBAL event_scheduler=0;
--sleep 1
connection priv_conn;
DROP EVENT e_x1;
DROP EVENT e_x2;
disconnect priv_conn;
connection default;
DROP DATABASE db_x;
DROP USER pauline@localhost;
USE events_test;
--sleep 1
#
# END: BUG #17289 Events: missing privilege check for drop database
#
drop event if exists event1; drop event if exists event1;
create event event1 on schedule every 15 minute starts now() ends date_add(now(), interval 5 hour) DO begin end; create event event1 on schedule every 15 minute starts now() ends date_add(now(), interval 5 hour) DO begin end;
alter event event1 rename to event2 enable; alter event event1 rename to event2 enable;
......
...@@ -203,6 +203,12 @@ class event_timed ...@@ -203,6 +203,12 @@ class event_timed
delete sphead; delete sphead;
sphead= 0; sphead= 0;
} }
protected:
bool
change_security_context(THD *thd, Security_context **backup);
void
restore_security_context(THD *thd, Security_context *backup);
}; };
......
...@@ -49,7 +49,8 @@ static uint workers_count; ...@@ -49,7 +49,8 @@ static uint workers_count;
static int static int
evex_load_events_from_db(THD *thd); evex_load_events_from_db(THD *thd);
bool
evex_print_warnings(THD *thd, event_timed *et);
/* /*
TODO Andrey: Check for command line option whether to start TODO Andrey: Check for command line option whether to start
...@@ -135,7 +136,8 @@ init_event_thread(THD* thd) ...@@ -135,7 +136,8 @@ init_event_thread(THD* thd)
{ {
DBUG_ENTER("init_event_thread"); DBUG_ENTER("init_event_thread");
thd->client_capabilities= 0; thd->client_capabilities= 0;
thd->security_ctx->skip_grants(); thd->security_ctx->master_access= 0;
thd->security_ctx->db_access= 0;
thd->security_ctx->host= (char*)my_localhost; thd->security_ctx->host= (char*)my_localhost;
my_net_init(&thd->net, 0); my_net_init(&thd->net, 0);
thd->net.read_timeout = slave_net_timeout; thd->net.read_timeout = slave_net_timeout;
...@@ -204,7 +206,7 @@ event_executor_main(void *arg) ...@@ -204,7 +206,7 @@ event_executor_main(void *arg)
if (init_event_thread(thd)) if (init_event_thread(thd))
goto err; goto err;
thd->security_ctx->skip_grants();
// make this thread invisible it has no vio -> show processlist won't see // make this thread invisible it has no vio -> show processlist won't see
thd->system_thread= 1; thd->system_thread= 1;
...@@ -481,21 +483,8 @@ event_executor_worker(void *event_void) ...@@ -481,21 +483,8 @@ event_executor_worker(void *event_void)
thd= current_thd; thd= current_thd;
#endif #endif
// thd->security_ctx->priv_host is char[MAX_HOSTNAME]
strxnmov(thd->security_ctx->priv_host, sizeof(thd->security_ctx->priv_host),
event->definer_host.str, NullS);
thd->security_ctx->user= thd->security_ctx->priv_user=
my_strdup(event->definer_user.str, MYF(0));
thd->db= event->dbname.str;
if (!check_access(thd, EVENT_ACL, event->dbname.str, 0, 0, 0,
is_schema_db(event->dbname.str)))
{ {
int ret; int ret;
DBUG_PRINT("info", (" EVEX EXECUTING event %s.%s [EXPR:%d]",
event->dbname.str, event->name.str,(int) event->expression));
sql_print_information(" EVEX EXECUTING event %s.%s [EXPR:%d]", sql_print_information(" EVEX EXECUTING event %s.%s [EXPR:%d]",
event->dbname.str, event->name.str,(int) event->expression); event->dbname.str, event->name.str,(int) event->expression);
...@@ -507,10 +496,7 @@ event_executor_worker(void *event_void) ...@@ -507,10 +496,7 @@ event_executor_worker(void *event_void)
if (ret == EVEX_COMPILE_ERROR) if (ret == EVEX_COMPILE_ERROR)
sql_print_information(" EVEX COMPILE ERROR for event %s.%s", sql_print_information(" EVEX COMPILE ERROR for event %s.%s",
event->dbname.str, event->name.str); event->dbname.str, event->name.str);
evex_print_warnings(thd, event);
DBUG_PRINT("info", (" EVEX EXECUTED event %s.%s [EXPR:%d]. RetCode=%d",
event->dbname.str, event->name.str,
(int) event->expression, ret));
} }
if ((event->flags & EVENT_EXEC_NO_MORE) || event->status==MYSQL_EVENT_DISABLED) if ((event->flags & EVENT_EXEC_NO_MORE) || event->status==MYSQL_EVENT_DISABLED)
{ {
...@@ -521,7 +507,6 @@ event_executor_worker(void *event_void) ...@@ -521,7 +507,6 @@ event_executor_worker(void *event_void)
delete event; delete event;
} }
thd->db= 0;
err: err:
VOID(pthread_mutex_lock(&LOCK_thread_count)); VOID(pthread_mutex_lock(&LOCK_thread_count));
...@@ -666,3 +651,62 @@ bool sys_var_event_executor::update(THD *thd, set_var *var) ...@@ -666,3 +651,62 @@ bool sys_var_event_executor::update(THD *thd, set_var *var)
DBUG_RETURN(0); DBUG_RETURN(0);
} }
extern LEX_STRING warning_level_names[];
/*
Prints the stack of infos, warnings, errors from thd to
the console so it can be fetched by the logs-into-tables and
checked later.
Synopsis
evex_print_warnings
thd - thread used during the execution of the event
et - the event itself
Returns
0 - OK (always)
*/
bool
evex_print_warnings(THD *thd, event_timed *et)
{
MYSQL_ERROR *err;
DBUG_ENTER("evex_show_warnings");
char msg_buf[1024];
char prefix_buf[512];
String prefix(prefix_buf, sizeof(prefix_buf), system_charset_info);
prefix.length(0);
List_iterator_fast<MYSQL_ERROR> it(thd->warn_list);
while ((err= it++))
{
String err_msg(msg_buf, sizeof(msg_buf), system_charset_info);
err_msg.length(0);// set it to 0 or we start adding at the end
if (!prefix.length())
{
prefix.append("SCHEDULER: [");
append_identifier(thd,&prefix,et->definer_user.str,et->definer_user.length);
prefix.append('@');
append_identifier(thd,&prefix,et->definer_host.str,et->definer_host.length);
prefix.append("][", 2);
append_identifier(thd,&prefix, et->dbname.str, et->dbname.length);
prefix.append('.');
append_identifier(thd,&prefix, et->name.str, et->name.length);
prefix.append("] ", 2);
}
err_msg.append(prefix);
err_msg.append('[');
err_msg.append(warning_level_names[err->level].str,
warning_level_names[err->level].length, system_charset_info);
err_msg.append("] [");
err_msg.append(err->msg, strlen(err->msg), system_charset_info);
err_msg.append("]");
sql_print_information("%*s", err_msg.length(), err_msg.c_ptr());
}
DBUG_RETURN(FALSE);
}
...@@ -982,12 +982,13 @@ event_timed::get_show_create_event(THD *thd, uint32 *length) ...@@ -982,12 +982,13 @@ event_timed::get_show_create_event(THD *thd, uint32 *length)
Executes the event (the underlying sp_head object); Executes the event (the underlying sp_head object);
SYNOPSIS SYNOPSIS
evex_fill_row() event_timed::execute()
thd THD thd THD
mem_root If != NULL use it to compile the event on it mem_root If != NULL use it to compile the event on it
Returns Returns
0 - success 0 - success
-99 - No access to the database.
-100 - event in execution (parallel execution is impossible) -100 - event in execution (parallel execution is impossible)
others - retcodes of sp_head::execute_procedure() others - retcodes of sp_head::execute_procedure()
...@@ -996,10 +997,12 @@ event_timed::get_show_create_event(THD *thd, uint32 *length) ...@@ -996,10 +997,12 @@ event_timed::get_show_create_event(THD *thd, uint32 *length)
int int
event_timed::execute(THD *thd, MEM_ROOT *mem_root) event_timed::execute(THD *thd, MEM_ROOT *mem_root)
{ {
List<Item> empty_item_list; Security_context *save_ctx;
int ret= 0; int ret= 0;
DBUG_ENTER("event_timed::execute"); DBUG_ENTER("event_timed::execute");
DBUG_PRINT("info", (" EVEX EXECUTING event %s.%s [EXPR:%d]",
dbname.str, name.str, (int) expression));
VOID(pthread_mutex_lock(&this->LOCK_running)); VOID(pthread_mutex_lock(&this->LOCK_running));
if (running) if (running)
...@@ -1011,12 +1014,35 @@ event_timed::execute(THD *thd, MEM_ROOT *mem_root) ...@@ -1011,12 +1014,35 @@ event_timed::execute(THD *thd, MEM_ROOT *mem_root)
VOID(pthread_mutex_unlock(&this->LOCK_running)); VOID(pthread_mutex_unlock(&this->LOCK_running));
// TODO Andrey : make this as member variable and delete in destructor // TODO Andrey : make this as member variable and delete in destructor
empty_item_list.empty();
if (!sphead && (ret= compile(thd, mem_root))) if (!sphead && (ret= compile(thd, mem_root)))
goto done; goto done;
ret= sphead->execute_procedure(thd, &empty_item_list); thd->db= dbname.str;
thd->db_length= dbname.length;
DBUG_PRINT("info", ("master_access=%d db_access=%d",
thd->security_ctx->master_access, thd->security_ctx->db_access));
change_security_context(thd, &save_ctx);
DBUG_PRINT("info", ("master_access=%d db_access=%d",
thd->security_ctx->master_access, thd->security_ctx->db_access));
// if (mysql_change_db(thd, dbname.str, 0))
if (!check_access(thd, EVENT_ACL,dbname.str, 0, 0, 0,is_schema_db(dbname.str)))
{
List<Item> empty_item_list;
empty_item_list.empty();
ret= sphead->execute_procedure(thd, &empty_item_list);
}
else
{
DBUG_PRINT("error", ("%s@%s has no rights on %s", definer_user.str,
definer_host.str, dbname.str));
ret= -99;
}
restore_security_context(thd, save_ctx);
DBUG_PRINT("info", ("master_access=%d db_access=%d",
thd->security_ctx->master_access, thd->security_ctx->db_access));
thd->db= 0;
VOID(pthread_mutex_lock(&this->LOCK_running)); VOID(pthread_mutex_lock(&this->LOCK_running));
running= false; running= false;
...@@ -1029,11 +1055,61 @@ event_timed::execute(THD *thd, MEM_ROOT *mem_root) ...@@ -1029,11 +1055,61 @@ event_timed::execute(THD *thd, MEM_ROOT *mem_root)
delete sphead; delete sphead;
sphead= 0; sphead= 0;
} }
DBUG_PRINT("info", (" EVEX EXECUTED event %s.%s [EXPR:%d]. RetCode=%d",
dbname.str, name.str, (int) expression, ret));
DBUG_RETURN(ret); DBUG_RETURN(ret);
} }
/*
Switches the security context
Synopsis
event_timed::change_security_context()
thd - thread
backup - where to store the old context
RETURN
0 - OK
1 - Error (generates error too)
*/
bool
event_timed::change_security_context(THD *thd, Security_context **backup)
{
DBUG_ENTER("event_timed::change_security_context");
DBUG_PRINT("info",("%s@%s@%s",definer_user.str,definer_host.str, dbname.str));
*backup= 0;
if (acl_getroot_no_password(&sphead->m_security_ctx, definer_user.str,
definer_host.str, definer_host.str, dbname.str))
{
my_error(ER_NO_SUCH_USER, MYF(0), definer_user.str, definer_host.str);
DBUG_RETURN(TRUE);
}
*backup= thd->security_ctx;
thd->security_ctx= &sphead->m_security_ctx;
DBUG_RETURN(FALSE);
}
/*
Restores the security context
Synopsis
event_timed::restore_security_context()
thd - thread
backup - switch to this context
*/
void
event_timed::restore_security_context(THD *thd, Security_context *backup)
{
DBUG_ENTER("event_timed::change_security_context");
if (backup)
thd->security_ctx= backup;
DBUG_VOID_RETURN;
}
/* /*
Returns Returns
0 - Success 0 - Success
......
...@@ -211,8 +211,13 @@ void push_warning_printf(THD *thd, MYSQL_ERROR::enum_warning_level level, ...@@ -211,8 +211,13 @@ void push_warning_printf(THD *thd, MYSQL_ERROR::enum_warning_level level,
TRUE Error sending data to client TRUE Error sending data to client
*/ */
static const char *warning_level_names[]= {"Note", "Warning", "Error", "?"}; LEX_STRING warning_level_names[]=
static int warning_level_length[]= { 4, 7, 5, 1 }; {
{(char*) STRING_WITH_LEN("Note")},
{(char*) STRING_WITH_LEN("Warning")},
{(char*) STRING_WITH_LEN("Error")},
{(char*) STRING_WITH_LEN("?")}
};
bool mysqld_show_warnings(THD *thd, ulong levels_to_show) bool mysqld_show_warnings(THD *thd, ulong levels_to_show)
{ {
...@@ -246,8 +251,8 @@ bool mysqld_show_warnings(THD *thd, ulong levels_to_show) ...@@ -246,8 +251,8 @@ bool mysqld_show_warnings(THD *thd, ulong levels_to_show)
if (idx > unit->select_limit_cnt) if (idx > unit->select_limit_cnt)
break; break;
protocol->prepare_for_resend(); protocol->prepare_for_resend();
protocol->store(warning_level_names[err->level], protocol->store(warning_level_names[err->level].str,
warning_level_length[err->level], system_charset_info); warning_level_names[err->level].length, system_charset_info);
protocol->store((uint32) err->code); protocol->store((uint32) err->code);
protocol->store(err->msg, strlen(err->msg), system_charset_info); protocol->store(err->msg, strlen(err->msg), system_charset_info);
if (protocol->write()) if (protocol->write())
......
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