Fix for BUG#27337: Privileges are not properly restored.

The problem was that THD::db_access variable was not restored after
database switch in stored-routine-execution code.

The fix is to restore THD::db_access in this case.

Unfortunately, this fix requires additional changes,
because in prepare_schema_table(), called on the parsing stage, we checked
privileges. That was wrong according to our design, but this flaw haven't
struck so far, because it was masked. All privilege checkings must be
done on the execution stage in order to be compatible with prepared statements
and stored routines. So, this patch also contains patch for
prepare_schema_table(), which moves the checkings to the execution phase.
parent 44c32c0e
......@@ -1019,4 +1019,79 @@ DROP DATABASE mysqltest2;
DROP DATABASE mysqltest3;
DROP DATABASE mysqltest4;
DROP USER mysqltest_1@localhost;
DROP DATABASE IF EXISTS mysqltest1;
DROP DATABASE IF EXISTS mysqltest2;
CREATE DATABASE mysqltest1;
CREATE DATABASE mysqltest2;
GRANT ALL PRIVILEGES ON mysqltest1.* TO mysqltest_1@localhost;
GRANT SELECT ON mysqltest2.* TO mysqltest_1@localhost;
CREATE PROCEDURE mysqltest1.p1() SQL SECURITY INVOKER
SELECT 1;
---> connection: bug27337_con1
CREATE TABLE t1(c INT);
ERROR 42000: CREATE command denied to user 'mysqltest_1'@'localhost' for table 't1'
CALL mysqltest1.p1();
1
1
CREATE TABLE t1(c INT);
ERROR 42000: CREATE command denied to user 'mysqltest_1'@'localhost' for table 't1'
---> connection: bug27337_con2
CREATE TABLE t1(c INT);
ERROR 42000: CREATE command denied to user 'mysqltest_1'@'localhost' for table 't1'
SHOW TABLES;
Tables_in_mysqltest2
---> connection: default
DROP DATABASE mysqltest1;
DROP DATABASE mysqltest2;
DROP USER mysqltest_1@localhost;
DROP DATABASE IF EXISTS mysqltest1;
DROP DATABASE IF EXISTS mysqltest2;
CREATE DATABASE mysqltest1;
CREATE DATABASE mysqltest2;
CREATE TABLE mysqltest1.t1(c INT);
CREATE TABLE mysqltest2.t2(c INT);
GRANT SELECT ON mysqltest1.t1 TO mysqltest_1@localhost;
GRANT SELECT ON mysqltest2.t2 TO mysqltest_2@localhost;
---> connection: bug27337_con1
SHOW TABLES FROM mysqltest1;
Tables_in_mysqltest1
t1
PREPARE stmt1 FROM 'SHOW TABLES FROM mysqltest1';
EXECUTE stmt1;
Tables_in_mysqltest1
t1
---> connection: bug27337_con2
SHOW COLUMNS FROM mysqltest2.t2;
Field Type Null Key Default Extra
c int(11) YES NULL
PREPARE stmt2 FROM 'SHOW COLUMNS FROM mysqltest2.t2';
EXECUTE stmt2;
Field Type Null Key Default Extra
c int(11) YES NULL
---> connection: default
REVOKE SELECT ON mysqltest1.t1 FROM mysqltest_1@localhost;
REVOKE SELECT ON mysqltest2.t2 FROM mysqltest_2@localhost;
---> connection: bug27337_con1
SHOW TABLES FROM mysqltest1;
ERROR 42000: Access denied for user 'mysqltest_1'@'localhost' to database 'mysqltest1'
EXECUTE stmt1;
ERROR 42000: Access denied for user 'mysqltest_1'@'localhost' to database 'mysqltest1'
---> connection: bug27337_con2
SHOW COLUMNS FROM mysqltest2.t2;
ERROR 42000: SELECT command denied to user 'mysqltest_2'@'localhost' for table 't2'
EXECUTE stmt2;
ERROR 42000: SELECT command denied to user 'mysqltest_2'@'localhost' for table 't2'
---> connection: default
DROP DATABASE mysqltest1;
DROP DATABASE mysqltest2;
DROP USER mysqltest_1@localhost;
End of 5.0 tests
......@@ -958,4 +958,148 @@ DROP DATABASE mysqltest4;
DROP USER mysqltest_1@localhost;
#
# BUG#27337: Privileges are not restored properly.
#
# Actually, the patch for this bugs fixes two problems. So, here are two test
# cases.
# Test case 1: privileges are not restored properly after calling a stored
# routine defined with SQL SECURITY INVOKER clause.
# Prepare.
--disable_warnings
DROP DATABASE IF EXISTS mysqltest1;
DROP DATABASE IF EXISTS mysqltest2;
--enable_warnings
CREATE DATABASE mysqltest1;
CREATE DATABASE mysqltest2;
GRANT ALL PRIVILEGES ON mysqltest1.* TO mysqltest_1@localhost;
GRANT SELECT ON mysqltest2.* TO mysqltest_1@localhost;
CREATE PROCEDURE mysqltest1.p1() SQL SECURITY INVOKER
SELECT 1;
# Test.
--connect (bug27337_con1,localhost,mysqltest_1,,mysqltest2)
--echo
--echo ---> connection: bug27337_con1
--error ER_TABLEACCESS_DENIED_ERROR
CREATE TABLE t1(c INT);
CALL mysqltest1.p1();
--error ER_TABLEACCESS_DENIED_ERROR
CREATE TABLE t1(c INT);
--disconnect bug27337_con1
--connect (bug27337_con2,localhost,mysqltest_1,,mysqltest2)
--echo
--echo ---> connection: bug27337_con2
--error ER_TABLEACCESS_DENIED_ERROR
CREATE TABLE t1(c INT);
SHOW TABLES;
# Cleanup.
--connection default
--echo
--echo ---> connection: default
--disconnect bug27337_con2
DROP DATABASE mysqltest1;
DROP DATABASE mysqltest2;
DROP USER mysqltest_1@localhost;
# Test case 2: priveleges are not checked properly for prepared statements.
# Prepare.
--disable_warnings
DROP DATABASE IF EXISTS mysqltest1;
DROP DATABASE IF EXISTS mysqltest2;
--enable_warnings
CREATE DATABASE mysqltest1;
CREATE DATABASE mysqltest2;
CREATE TABLE mysqltest1.t1(c INT);
CREATE TABLE mysqltest2.t2(c INT);
GRANT SELECT ON mysqltest1.t1 TO mysqltest_1@localhost;
GRANT SELECT ON mysqltest2.t2 TO mysqltest_2@localhost;
# Test.
--connect (bug27337_con1,localhost,mysqltest_1,,mysqltest1)
--echo
--echo ---> connection: bug27337_con1
SHOW TABLES FROM mysqltest1;
PREPARE stmt1 FROM 'SHOW TABLES FROM mysqltest1';
EXECUTE stmt1;
--connect (bug27337_con2,localhost,mysqltest_2,,mysqltest2)
--echo
--echo ---> connection: bug27337_con2
SHOW COLUMNS FROM mysqltest2.t2;
PREPARE stmt2 FROM 'SHOW COLUMNS FROM mysqltest2.t2';
EXECUTE stmt2;
--connection default
--echo
--echo ---> connection: default
REVOKE SELECT ON mysqltest1.t1 FROM mysqltest_1@localhost;
REVOKE SELECT ON mysqltest2.t2 FROM mysqltest_2@localhost;
--connection bug27337_con1
--echo
--echo ---> connection: bug27337_con1
--error ER_DBACCESS_DENIED_ERROR
SHOW TABLES FROM mysqltest1;
--error ER_DBACCESS_DENIED_ERROR
EXECUTE stmt1;
--connection bug27337_con2
--echo
--echo ---> connection: bug27337_con2
--error ER_TABLEACCESS_DENIED_ERROR
SHOW COLUMNS FROM mysqltest2.t2;
--error ER_TABLEACCESS_DENIED_ERROR
EXECUTE stmt2;
# Cleanup.
--connection default
--echo
--echo ---> connection: default
--disconnect bug27337_con2
DROP DATABASE mysqltest1;
DROP DATABASE mysqltest2;
DROP USER mysqltest_1@localhost;
--echo End of 5.0 tests
......@@ -955,6 +955,8 @@ int fill_schema_table_privileges(THD *thd, TABLE_LIST *tables, COND *cond);
int fill_schema_column_privileges(THD *thd, TABLE_LIST *tables, COND *cond);
bool get_schema_tables_result(JOIN *join,
enum enum_schema_table_state executed_place);
enum enum_schema_tables get_schema_table_idx(ST_SCHEMA_TABLE *schema_table);
#define is_schema_db(X) \
!my_strcasecmp(system_charset_info, INFORMATION_SCHEMA_NAME.str, (X))
......
......@@ -1308,30 +1308,27 @@ bool mysql_change_db(THD *thd, const LEX_STRING *new_db_name, bool force_switch)
DBUG_PRINT("info",("Use database: %s", new_db_file_name.str));
#ifndef NO_EMBEDDED_ACCESS_CHECKS
if (!force_switch) /* FIXME: this is BUG#27337. */
db_access=
test_all_bits(sctx->master_access, DB_ACLS) ?
DB_ACLS :
acl_get(sctx->host,
sctx->ip,
sctx->priv_user,
new_db_file_name.str,
FALSE) | sctx->master_access;
if (!force_switch &&
!(db_access & DB_ACLS) &&
(!grant_option || check_grant_db(thd, new_db_file_name.str)))
{
db_access=
test_all_bits(sctx->master_access, DB_ACLS) ?
DB_ACLS :
acl_get(sctx->host,
sctx->ip,
sctx->priv_user,
new_db_file_name.str,
FALSE) | sctx->master_access;
if (!force_switch &&
!(db_access & DB_ACLS) &&
(!grant_option || check_grant_db(thd, new_db_file_name.str)))
{
my_error(ER_DBACCESS_DENIED_ERROR, MYF(0),
sctx->priv_user,
sctx->priv_host,
new_db_file_name.str);
mysql_log.write(thd, COM_INIT_DB, ER(ER_DBACCESS_DENIED_ERROR),
sctx->priv_user, sctx->priv_host, new_db_file_name.str);
my_free(new_db_file_name.str, MYF(0));
DBUG_RETURN(TRUE);
}
my_error(ER_DBACCESS_DENIED_ERROR, MYF(0),
sctx->priv_user,
sctx->priv_host,
new_db_file_name.str);
mysql_log.write(thd, COM_INIT_DB, ER(ER_DBACCESS_DENIED_ERROR),
sctx->priv_user, sctx->priv_host, new_db_file_name.str);
my_free(new_db_file_name.str, MYF(0));
DBUG_RETURN(TRUE);
}
#endif
......
......@@ -2237,7 +2237,8 @@ int prepare_schema_table(THD *thd, LEX *lex, Table_ident *table_ident,
enum enum_schema_tables schema_table_idx)
{
DBUG_ENTER("prepare_schema_table");
SELECT_LEX *sel= 0;
SELECT_LEX *schema_select_lex= NULL;
switch (schema_table_idx) {
case SCH_SCHEMATA:
#if defined(DONT_ALLOW_SHOW_COMMANDS)
......@@ -2245,11 +2246,9 @@ int prepare_schema_table(THD *thd, LEX *lex, Table_ident *table_ident,
ER(ER_NOT_ALLOWED_COMMAND), MYF(0)); /* purecov: inspected */
DBUG_RETURN(1);
#else
if ((specialflag & SPECIAL_SKIP_SHOW_DB) &&
check_global_access(thd, SHOW_DB_ACL))
DBUG_RETURN(1);
break;
#endif
case SCH_TABLE_NAMES:
case SCH_TABLES:
case SCH_VIEWS:
......@@ -2259,32 +2258,25 @@ int prepare_schema_table(THD *thd, LEX *lex, Table_ident *table_ident,
ER(ER_NOT_ALLOWED_COMMAND), MYF(0)); /* purecov: inspected */
DBUG_RETURN(1);
#else
if (lex->select_lex.db == NULL &&
thd->copy_db_to(&lex->select_lex.db, NULL))
{
char *db;
if (lex->select_lex.db == NULL &&
thd->copy_db_to(&lex->select_lex.db, 0))
{
DBUG_RETURN(1);
}
db= lex->select_lex.db;
remove_escape(db); // Fix escaped '_'
if (check_db_name(db))
{
my_error(ER_WRONG_DB_NAME, MYF(0), db);
DBUG_RETURN(1);
}
if (check_access(thd, SELECT_ACL, db, &thd->col_access, 0, 0,
is_schema_db(db)))
DBUG_RETURN(1); /* purecov: inspected */
if (!thd->col_access && check_grant_db(thd,db))
{
my_error(ER_DBACCESS_DENIED_ERROR, MYF(0),
thd->security_ctx->priv_user, thd->security_ctx->priv_host,
db);
DBUG_RETURN(1);
}
break;
DBUG_RETURN(1);
}
schema_select_lex= new SELECT_LEX();
schema_select_lex->db= lex->select_lex.db;
schema_select_lex->table_list.first= NULL;
remove_escape(schema_select_lex->db); // Fix escaped '_'
if (check_db_name(schema_select_lex->db))
{
my_error(ER_WRONG_DB_NAME, MYF(0), schema_select_lex->db);
DBUG_RETURN(1);
}
break;
#endif
case SCH_COLUMNS:
case SCH_STATISTICS:
......@@ -2293,28 +2285,23 @@ int prepare_schema_table(THD *thd, LEX *lex, Table_ident *table_ident,
ER(ER_NOT_ALLOWED_COMMAND), MYF(0)); /* purecov: inspected */
DBUG_RETURN(1);
#else
if (table_ident)
{
DBUG_ASSERT(table_ident);
TABLE_LIST **query_tables_last= lex->query_tables_last;
sel= new SELECT_LEX();
schema_select_lex= new SELECT_LEX();
/* 'parent_lex' is used in init_query() so it must be before it. */
sel->parent_lex= lex;
sel->init_query();
if (!sel->add_table_to_list(thd, table_ident, 0, 0, TL_READ,
(List<String> *) 0, (List<String> *) 0))
schema_select_lex->parent_lex= lex;
schema_select_lex->init_query();
if (!schema_select_lex->add_table_to_list(thd, table_ident, 0, 0, TL_READ,
(List<String> *) 0, (List<String> *) 0))
DBUG_RETURN(1);
lex->query_tables_last= query_tables_last;
TABLE_LIST *table_list= (TABLE_LIST*) sel->table_list.first;
char *db= table_list->db;
remove_escape(db); // Fix escaped '_'
remove_escape(table_list->table_name);
if (check_access(thd,SELECT_ACL | EXTRA_ACL,db,
&table_list->grant.privilege, 0, 0,
test(table_list->schema_table)))
DBUG_RETURN(1); /* purecov: inspected */
if (grant_option && check_grant(thd, SELECT_ACL, table_list, 2,
UINT_MAX, 0))
DBUG_RETURN(1);
TABLE_LIST *dst_table= (TABLE_LIST*) schema_select_lex->table_list.first;
remove_escape(dst_table->db); // Fix escaped '_'
remove_escape(dst_table->table_name);
break;
}
#endif
......@@ -2341,7 +2328,7 @@ int prepare_schema_table(THD *thd, LEX *lex, Table_ident *table_ident,
DBUG_RETURN(1);
}
TABLE_LIST *table_list= (TABLE_LIST*) select_lex->table_list.first;
table_list->schema_select_lex= sel;
table_list->schema_select_lex= schema_select_lex;
table_list->schema_table_reformed= 1;
statistic_increment(thd->status_var.com_stat[lex->orig_sql_command],
&LOCK_status);
......@@ -5390,6 +5377,83 @@ bool check_global_access(THD *thd, ulong want_access)
}
static bool check_show_access(THD *thd, TABLE_LIST *table)
{
switch (get_schema_table_idx(table->schema_table))
{
case SCH_SCHEMATA:
return (specialflag & SPECIAL_SKIP_SHOW_DB) &&
check_global_access(thd, SHOW_DB_ACL);
case SCH_TABLE_NAMES:
case SCH_TABLES:
case SCH_VIEWS:
case SCH_TRIGGERS:
{
const char *dst_db_name= table->schema_select_lex->db;
DBUG_ASSERT(dst_db_name);
if (check_access(thd, SELECT_ACL, dst_db_name,
&thd->col_access, FALSE, FALSE,
is_schema_db(dst_db_name)))
{
return TRUE;
}
if (!thd->col_access && check_grant_db(thd, dst_db_name))
{
my_error(ER_DBACCESS_DENIED_ERROR, MYF(0),
thd->security_ctx->priv_user,
thd->security_ctx->priv_host,
dst_db_name);
return TRUE;
}
return FALSE;
}
case SCH_COLUMNS:
case SCH_STATISTICS:
{
TABLE_LIST *dst_table=
(TABLE_LIST *) table->schema_select_lex->table_list.first;
DBUG_ASSERT(dst_table);
if (check_access(thd, SELECT_ACL | EXTRA_ACL,
dst_table->db,
&dst_table->grant.privilege,
FALSE, FALSE,
test(dst_table->schema_table)))
{
return FALSE;
}
return grant_option &&
check_grant(thd, SELECT_ACL, dst_table, 2, UINT_MAX, FALSE);
}
case SCH_OPEN_TABLES:
case SCH_VARIABLES:
case SCH_STATUS:
case SCH_PROCEDURES:
case SCH_CHARSETS:
case SCH_COLLATIONS:
case SCH_COLLATION_CHARACTER_SET_APPLICABILITY:
case SCH_USER_PRIVILEGES:
case SCH_SCHEMA_PRIVILEGES:
case SCH_TABLE_PRIVILEGES:
case SCH_COLUMN_PRIVILEGES:
case SCH_TABLE_CONSTRAINTS:
case SCH_KEY_COLUMN_USAGE:
break;
}
return FALSE;
}
/*
Check the privilege for all used tables.
......@@ -5450,7 +5514,16 @@ check_table_access(THD *thd, ulong want_access,TABLE_LIST *tables,
Remove SHOW_VIEW_ACL, because it will be checked during making view
*/
tables->grant.orig_want_privilege= (want_access & ~SHOW_VIEW_ACL);
if (tables->derived || tables->schema_table ||
if (tables->schema_table_reformed)
{
if (check_show_access(thd, tables))
goto deny;
continue;
}
if (tables->derived ||
(tables->table && (int)tables->table->s->tmp_table) ||
my_tz_check_n_skip_implicit_tables(&tables,
thd->lex->time_zone_tables_used))
......
......@@ -2148,7 +2148,7 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond)
*/
thd->reset_n_backup_open_tables_state(&open_tables_state_backup);
if (lsel)
if (lsel && lsel->table_list.first)
{
TABLE_LIST *show_table_list= (TABLE_LIST*) lsel->table_list.first;
bool res;
......
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