Commit e03e5222 authored by anozdrin@mysql.com's avatar anozdrin@mysql.com

Fix for BUG#16266: Definer is not fully qualified error during replication.

The idea of the fix is to extend support of non-SUID triggers for backward
compatibility. Formerly non-SUID triggers were appeared when "new" server
is being started against "old" database. Now, they are also created when
"new" slave receives updates from "old" master.
parent db1ecaa1
...@@ -855,3 +855,44 @@ f3 ...@@ -855,3 +855,44 @@ f3
drop trigger trg11; drop trigger trg11;
drop table t21,t31; drop table t21,t31;
drop table t11; drop table t11;
STOP SLAVE;
FLUSH LOGS;
RESET SLAVE;
START SLAVE;
SELECT MASTER_POS_WAIT('master-bin.000001', 513) >= 0;
MASTER_POS_WAIT('master-bin.000001', 513) >= 0
1
SHOW TABLES;
Tables_in_test
t1
t2
SHOW TRIGGERS;
Trigger Event Table Statement Timing Created sql_mode Definer
trg1 INSERT t1 INSERT INTO t2 VALUES(CURRENT_USER()) AFTER NULL
SELECT * FROM t1;
c
1
SELECT * FROM t2;
s
@
INSERT INTO t1 VALUES(2);
SELECT * FROM t1;
c
1
2
SELECT * FROM t2;
s
@
root@localhost
DROP TRIGGER trg1;
Warnings:
Warning 1454 No definer attribute for trigger 'test'.'trg1'. The trigger will be activated under the authorization of the invoker, which may have insufficient privileges. Please recreate the trigger.
DROP TABLE t1;
DROP TABLE t2;
STOP SLAVE;
RESET SLAVE;
SHOW TABLES;
Tables_in_test
SHOW TRIGGERS;
Trigger Event Table Statement Timing Created sql_mode Definer
RESET MASTER;
...@@ -162,6 +162,7 @@ use test; ...@@ -162,6 +162,7 @@ use test;
drop table t1,t2; drop table t1,t2;
drop database other; drop database other;
# #
# Test specific triggers including SELECT into var with replication # Test specific triggers including SELECT into var with replication
# BUG#13227: # BUG#13227:
...@@ -257,6 +258,79 @@ while ($rnd) ...@@ -257,6 +258,79 @@ while ($rnd)
} }
#
# BUG#16266: Definer is not fully qualified error during replication.
#
# The idea of this test is to emulate replication of a trigger from the old
# master (master w/o "DEFINER in triggers" support) to the new slave and check
# that:
# 1. the trigger on the slave will be replicated w/o errors;
# 2. the trigger on the slave will be non-SUID (will have no DEFINER);
# 3. the trigger can be activated later on the slave w/o errors.
#
# In order to emulate this kind of replication, we make the slave playing the binlog,
# recorded by 5.0.16 master. This binlog contains the following statements:
# CREATE TABLE t1(c INT);
# CREATE TABLE t2(s CHAR(200));
# CREATE TRIGGER trg1 AFTER INSERT ON t1
# FOR EACH ROW
# INSERT INTO t2 VALUES(CURRENT_USER());
# INSERT INTO t1 VALUES(1);
#
# 1. Check that the trigger's replication is succeeded.
# Stop the slave.
connection slave;
STOP SLAVE;
# Replace master's binlog.
connection master;
FLUSH LOGS;
exec cp $MYSQL_TEST_DIR/std_data/bug16266.000001 $MYSQLTEST_VARDIR/log/master-bin.000001;
# Make the slave to replay the new binlog.
connection slave;
RESET SLAVE;
START SLAVE;
SELECT MASTER_POS_WAIT('master-bin.000001', 513) >= 0;
# Check that the replication succeeded.
SHOW TABLES;
SHOW TRIGGERS;
SELECT * FROM t1;
SELECT * FROM t2;
# 2. Check that the trigger is non-SUID on the slave;
# 3. Check that the trigger can be activated on the slave.
INSERT INTO t1 VALUES(2);
SELECT * FROM t1;
SELECT * FROM t2;
# That's all, cleanup.
DROP TRIGGER trg1;
DROP TABLE t1;
DROP TABLE t2;
STOP SLAVE;
RESET SLAVE;
# The master should be clean.
connection master;
SHOW TABLES;
SHOW TRIGGERS;
RESET MASTER;
# #
# End of tests # End of tests
......
...@@ -531,6 +531,7 @@ bool create_table_precheck(THD *thd, TABLE_LIST *tables, ...@@ -531,6 +531,7 @@ bool create_table_precheck(THD *thd, TABLE_LIST *tables,
TABLE_LIST *create_table); TABLE_LIST *create_table);
bool get_default_definer(THD *thd, LEX_USER *definer); bool get_default_definer(THD *thd, LEX_USER *definer);
LEX_USER *create_default_definer(THD *thd);
LEX_USER *create_definer(THD *thd, LEX_STRING *user_name, LEX_STRING *host_name); LEX_USER *create_definer(THD *thd, LEX_STRING *user_name, LEX_STRING *host_name);
enum enum_mysql_completiontype { enum enum_mysql_completiontype {
......
...@@ -7206,6 +7206,34 @@ bool get_default_definer(THD *thd, LEX_USER *definer) ...@@ -7206,6 +7206,34 @@ bool get_default_definer(THD *thd, LEX_USER *definer)
} }
/*
Create default definer for the specified THD. Also check that the current
user is conformed to the definers requirements.
SYNOPSIS
create_default_definer()
thd [in] thread handler
RETURN
On success, return a valid pointer to the created and initialized
LEX_USER, which contains definer information.
On error, return 0.
*/
LEX_USER *create_default_definer(THD *thd)
{
LEX_USER *definer;
if (! (definer= (LEX_USER*) thd->alloc(sizeof(LEX_USER))))
return 0;
if (get_default_definer(thd, definer))
return 0;
return definer;
}
/* /*
Create definer with the given user and host names. Also check that the user Create definer with the given user and host names. Also check that the user
and host names satisfy definers requirements. and host names satisfy definers requirements.
...@@ -7218,7 +7246,7 @@ bool get_default_definer(THD *thd, LEX_USER *definer) ...@@ -7218,7 +7246,7 @@ bool get_default_definer(THD *thd, LEX_USER *definer)
RETURN RETURN
On success, return a valid pointer to the created and initialized On success, return a valid pointer to the created and initialized
LEX_STRING, which contains definer information. LEX_USER, which contains definer information.
On error, return 0. On error, return 0.
*/ */
......
...@@ -264,7 +264,17 @@ end: ...@@ -264,7 +264,17 @@ end:
log_query.set((char *) 0, 0, system_charset_info); /* reset log_query */ log_query.set((char *) 0, 0, system_charset_info); /* reset log_query */
log_query.append(STRING_WITH_LEN("CREATE ")); log_query.append(STRING_WITH_LEN("CREATE "));
if (definer_user.str && definer_host.str)
{
/*
Append definer-clause if the trigger is SUID (a usual trigger in
new MySQL versions).
*/
append_definer(thd, &log_query, &definer_user, &definer_host); append_definer(thd, &log_query, &definer_user, &definer_host);
}
log_query.append(thd->lex->trigger_definition_begin); log_query.append(thd->lex->trigger_definition_begin);
} }
...@@ -289,17 +299,30 @@ end: ...@@ -289,17 +299,30 @@ end:
LEX) LEX)
tables - table list containing one open table for which the tables - table list containing one open table for which the
trigger is created. trigger is created.
definer_user - [out] after a call it points to 0-terminated string, definer_user - [out] after a call it points to 0-terminated string or
which contains user name part of the actual trigger contains the NULL-string:
definer. The caller is responsible to provide memory for - 0-terminated is returned if the trigger is SUID. The
string contains user name part of the actual trigger
definer.
- NULL-string is returned if the trigger is non-SUID.
Anyway, the caller is responsible to provide memory for
storing LEX_STRING object. storing LEX_STRING object.
definer_host - [out] after a call it points to 0-terminated string, definer_host - [out] after a call it points to 0-terminated string or
which contains host name part of the actual trigger contains the NULL-string:
definer. The caller is responsible to provide memory for - 0-terminated string is returned if the trigger is
SUID. The string contains host name part of the
actual trigger definer.
- NULL-string is returned if the trigger is non-SUID.
Anyway, the caller is responsible to provide memory for
storing LEX_STRING object. storing LEX_STRING object.
NOTE NOTE
Assumes that trigger name is fully qualified. - Assumes that trigger name is fully qualified.
- NULL-string means the following LEX_STRING instance:
{ str = 0; length = 0 }.
- In other words, definer_user and definer_host should contain
simultaneously NULL-strings (non-SUID/old trigger) or valid strings
(SUID/new trigger).
RETURN VALUE RETURN VALUE
False - success False - success
...@@ -336,12 +359,30 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables, ...@@ -336,12 +359,30 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables,
return 1; return 1;
} }
if (!lex->definer)
{
/* /*
Definer attribute of the Lex instance is always set in sql_yacc.yy when DEFINER-clause is missing.
trigger is created.
If we are in slave thread, this means that we received CREATE TRIGGER
from the master, that does not support definer in triggers. So, we
should mark this trigger as non-SUID. Note that this does not happen
when we parse triggers' definitions during opening .TRG file.
LEX::definer is ignored in that case.
Otherwise, we should use CURRENT_USER() as definer.
NOTE: when CREATE TRIGGER statement is allowed to be executed in PS/SP,
it will be required to create the definer below in persistent MEM_ROOT
of PS/SP.
*/ */
DBUG_ASSERT(lex->definer); if (!thd->slave_thread)
{
if (!(lex->definer= create_default_definer(thd)))
return 1;
}
}
/* /*
If the specified definer differs from the current user, we should check If the specified definer differs from the current user, we should check
...@@ -349,10 +390,11 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables, ...@@ -349,10 +390,11 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables,
under another user one must have SUPER privilege). under another user one must have SUPER privilege).
*/ */
if (strcmp(lex->definer->user.str, thd->security_ctx->priv_user) || if (lex->definer &&
(strcmp(lex->definer->user.str, thd->security_ctx->priv_user) ||
my_strcasecmp(system_charset_info, my_strcasecmp(system_charset_info,
lex->definer->host.str, lex->definer->host.str,
thd->security_ctx->priv_host)) thd->security_ctx->priv_host)))
{ {
if (check_global_access(thd, SUPER_ACL)) if (check_global_access(thd, SUPER_ACL))
{ {
...@@ -446,7 +488,7 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables, ...@@ -446,7 +488,7 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables,
*trg_sql_mode= thd->variables.sql_mode; *trg_sql_mode= thd->variables.sql_mode;
#ifndef NO_EMBEDDED_ACCESS_CHECKS #ifndef NO_EMBEDDED_ACCESS_CHECKS
if (!is_acl_user(lex->definer->host.str, if (lex->definer && !is_acl_user(lex->definer->host.str,
lex->definer->user.str)) lex->definer->user.str))
{ {
push_warning_printf(thd, push_warning_printf(thd,
...@@ -458,12 +500,30 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables, ...@@ -458,12 +500,30 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables,
} }
#endif /* NO_EMBEDDED_ACCESS_CHECKS */ #endif /* NO_EMBEDDED_ACCESS_CHECKS */
if (lex->definer)
{
/* SUID trigger. */
*definer_user= lex->definer->user; *definer_user= lex->definer->user;
*definer_host= lex->definer->host; *definer_host= lex->definer->host;
trg_definer->str= trg_definer_holder; trg_definer->str= trg_definer_holder;
trg_definer->length= strxmov(trg_definer->str, definer_user->str, "@", trg_definer->length= strxmov(trg_definer->str, definer_user->str, "@",
definer_host->str, NullS) - trg_definer->str; definer_host->str, NullS) - trg_definer->str;
}
else
{
/* non-SUID trigger. */
definer_user->str= 0;
definer_user->length= 0;
definer_host->str= 0;
definer_host->length= 0;
trg_definer->str= (char*) "";
trg_definer->length= 0;
}
if (!sql_create_definition_file(&dir, &file, &triggers_file_type, if (!sql_create_definition_file(&dir, &file, &triggers_file_type,
(gptr)this, triggers_file_parameters, 0)) (gptr)this, triggers_file_parameters, 0))
......
...@@ -208,6 +208,26 @@ bool mysql_create_view(THD *thd, ...@@ -208,6 +208,26 @@ bool mysql_create_view(THD *thd,
if (mode != VIEW_CREATE_NEW) if (mode != VIEW_CREATE_NEW)
sp_cache_invalidate(); sp_cache_invalidate();
if (!lex->definer)
{
/*
DEFINER-clause is missing; we have to create default definer in
persistent arena to be PS/SP friendly.
*/
Query_arena original_arena;
Query_arena *ps_arena = thd->activate_stmt_arena_if_needed(&original_arena);
if (!(lex->definer= create_default_definer(thd)))
res= TRUE;
if (ps_arena)
thd->restore_active_arena(ps_arena, &original_arena);
if (res)
goto err;
}
#ifndef NO_EMBEDDED_ACCESS_CHECKS #ifndef NO_EMBEDDED_ACCESS_CHECKS
/* /*
check definer of view: check definer of view:
......
...@@ -778,7 +778,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); ...@@ -778,7 +778,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
%type <symbol> FUNC_ARG0 FUNC_ARG1 FUNC_ARG2 FUNC_ARG3 keyword keyword_sp %type <symbol> FUNC_ARG0 FUNC_ARG1 FUNC_ARG2 FUNC_ARG3 keyword keyword_sp
%type <lex_user> user grant_user get_definer %type <lex_user> user grant_user
%type <charset> %type <charset>
opt_collate opt_collate
...@@ -8960,41 +8960,29 @@ subselect_end: ...@@ -8960,41 +8960,29 @@ subselect_end:
}; };
definer: definer:
get_definer /* empty */
{ {
THD *thd= YYTHD; /*
We have to distinguish missing DEFINER-clause from case when
if (! (thd->lex->definer= create_definer(thd, &$1->user, &$1->host))) CURRENT_USER specified as definer explicitly in order to properly
YYABORT; handle CREATE TRIGGER statements which come to replication thread
from older master servers (i.e. to create non-suid trigger in this
case).
*/
YYTHD->lex->definer= 0;
} }
; | DEFINER_SYM EQ CURRENT_USER optional_braces
get_definer:
opt_current_definer
{ {
THD *thd= YYTHD; if (! (YYTHD->lex->definer= create_default_definer(YYTHD)))
if (!($$=(LEX_USER*) thd->alloc(sizeof(st_lex_user))))
YYABORT;
if (get_default_definer(thd, $$))
YYABORT; YYABORT;
} }
| DEFINER_SYM EQ ident_or_text '@' ident_or_text | DEFINER_SYM EQ ident_or_text '@' ident_or_text
{ {
if (!($$=(LEX_USER*) YYTHD->alloc(sizeof(st_lex_user)))) if (!(YYTHD->lex->definer= create_definer(YYTHD, &$3, &$5)))
YYABORT; YYABORT;
$$->user= $3;
$$->host= $5;
} }
; ;
opt_current_definer:
/* empty */
| DEFINER_SYM EQ CURRENT_USER optional_braces
;
/************************************************************************** /**************************************************************************
CREATE VIEW statement options. CREATE VIEW statement options.
......
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