mirror of
https://github.com/MariaDB/server.git
synced 2025-11-06 13:10:12 +03:00
MDEV-36870 Spurious unrelated permission error when selecting from table with default that uses nextval(sequence)
Lots of different cases, SELECT, SELECT DEFAULT(), UPDATE t SET x=DEFAULT, prepares statements, opening of a table for the I_S, prelocking (so TL_WRITE), insert with subquery (so SQLCOM_SELECT), etc. Don't check NEXTVAL privileges in fix_fields() anymore, it cannot possibly handle all the cases correctly. Make a special method Item_func_nextval::check_access() for that and invoke it from * fix_fields on explicit SELECT NEXTVAL() (but not if NEXTVAL() is used in a DEFAULT clause) * when DEFAULT bareword in used in, say, UPDATE t SET x=DEFAULT (but not if DEFAULT() itself is used in a DEFAULT clause) * in CREATE TABLE * in ALTER TABLE ALGORITHM=INPLACE (that doesn't go CREATE TABLE path) * on INSERT helpers * Virtual_column_info::check_access() to walk the item tree and invoke Item::check_access() * TABLE::check_sequence_privileges() to iterate default expressions and invoke Virtual_column_info::check_access() also, single-table UPDATE in prepared statements now associates value items with fields just as multi-update already did, fixes the case of PREPARE s "UPDATE t SET x=?"; EXECUTE s USING DEFAULT.
This commit is contained in:
@@ -112,5 +112,76 @@ connection default;
|
|||||||
drop user u_alter;
|
drop user u_alter;
|
||||||
drop database mysqltest_1;
|
drop database mysqltest_1;
|
||||||
#
|
#
|
||||||
# End of 10.11 tests
|
# MDEV-36870 Spurious unrelated permission error when selecting from table with default that uses nextval(sequence)
|
||||||
#
|
#
|
||||||
|
create database db1;
|
||||||
|
use db1;
|
||||||
|
create sequence s1 cache 0;
|
||||||
|
create table t1 (id int unsigned default (10+nextval(s1)));
|
||||||
|
insert t1 values ();
|
||||||
|
create table t2 (id int unsigned default nextval(s1), b int default(default(id)));
|
||||||
|
insert t2 values ();
|
||||||
|
create function f1(x int) returns int sql security invoker
|
||||||
|
begin
|
||||||
|
select id+x into x from t1;
|
||||||
|
return x;
|
||||||
|
insert t1 values ();
|
||||||
|
end|
|
||||||
|
create user u1@localhost;
|
||||||
|
grant select on db1.* to u1@localhost;
|
||||||
|
grant execute on db1.* to u1@localhost;
|
||||||
|
use test;
|
||||||
|
create table t3 (id int unsigned default (20+nextval(db1.s1)), b int);
|
||||||
|
insert t3 values ();
|
||||||
|
create sequence s2 cache 0;
|
||||||
|
create table t4 (id int unsigned default (10+nextval(s2)), b int);
|
||||||
|
insert t4 values ();
|
||||||
|
connect u1,localhost,u1,,db1;
|
||||||
|
select * from t1;
|
||||||
|
id
|
||||||
|
11
|
||||||
|
connection default;
|
||||||
|
flush tables;
|
||||||
|
connection u1;
|
||||||
|
select * from t1;
|
||||||
|
id
|
||||||
|
11
|
||||||
|
select default(id) from t1;
|
||||||
|
ERROR 42000: INSERT command denied to user 'u1'@'localhost' for table `db1`.`s1`
|
||||||
|
select * from t2;
|
||||||
|
id b
|
||||||
|
2 3
|
||||||
|
select f1(100);
|
||||||
|
f1(100)
|
||||||
|
111
|
||||||
|
select column_name, data_type, column_default from information_schema.columns where table_schema='db1' and table_name='t1';
|
||||||
|
column_name data_type column_default
|
||||||
|
id int (10 + nextval(`db1`.`s1`))
|
||||||
|
use test;
|
||||||
|
insert t3 values ();
|
||||||
|
ERROR 42000: INSERT command denied to user 'u1'@'localhost' for table `db1`.`s1`
|
||||||
|
insert t4 values ();
|
||||||
|
insert t3 (b) select 5;
|
||||||
|
ERROR 42000: INSERT command denied to user 'u1'@'localhost' for table `db1`.`s1`
|
||||||
|
insert t4 (b) select 5;
|
||||||
|
update t3 set id=default;
|
||||||
|
ERROR 42000: INSERT command denied to user 'u1'@'localhost' for table `db1`.`s1`
|
||||||
|
update t4 set id=default;
|
||||||
|
prepare stmt from "update t3 set id=?";
|
||||||
|
execute stmt using default;
|
||||||
|
ERROR 42000: INSERT command denied to user 'u1'@'localhost' for table `db1`.`s1`
|
||||||
|
prepare stmt from "update t4 set id=?";
|
||||||
|
execute stmt using default;
|
||||||
|
deallocate prepare stmt;
|
||||||
|
insert t4 (b) values ((select * from db1.t1));
|
||||||
|
insert t4 (b) values ((select default(id) from db1.t1));
|
||||||
|
ERROR 42000: INSERT command denied to user 'u1'@'localhost' for table `db1`.`s1`
|
||||||
|
connection default;
|
||||||
|
disconnect u1;
|
||||||
|
select nextval(db1.s1) as 'must be 5';
|
||||||
|
must be 5
|
||||||
|
5
|
||||||
|
drop user u1@localhost;
|
||||||
|
drop database db1;
|
||||||
|
drop table t3, t4, s2;
|
||||||
|
# End of 10.6 tests
|
||||||
|
|||||||
@@ -121,13 +121,105 @@ alter table t1 modify id int default nextval(s1);
|
|||||||
--disconnect con_alter
|
--disconnect con_alter
|
||||||
--connection default
|
--connection default
|
||||||
drop user u_alter;
|
drop user u_alter;
|
||||||
|
|
||||||
#
|
|
||||||
# Cleanup
|
|
||||||
#
|
|
||||||
|
|
||||||
drop database mysqltest_1;
|
drop database mysqltest_1;
|
||||||
|
|
||||||
--echo #
|
--echo #
|
||||||
--echo # End of 10.11 tests
|
--echo # MDEV-36870 Spurious unrelated permission error when selecting from table with default that uses nextval(sequence)
|
||||||
--echo #
|
--echo #
|
||||||
|
|
||||||
|
# various tests for permission checking on sequences
|
||||||
|
create database db1;
|
||||||
|
use db1;
|
||||||
|
create sequence s1 cache 0;
|
||||||
|
create table t1 (id int unsigned default (10+nextval(s1)));
|
||||||
|
insert t1 values ();
|
||||||
|
|
||||||
|
create table t2 (id int unsigned default nextval(s1), b int default(default(id)));
|
||||||
|
insert t2 values ();
|
||||||
|
|
||||||
|
# INSERT affects prelocking, but is never actually executed
|
||||||
|
delimiter |;
|
||||||
|
create function f1(x int) returns int sql security invoker
|
||||||
|
begin
|
||||||
|
select id+x into x from t1;
|
||||||
|
return x;
|
||||||
|
insert t1 values ();
|
||||||
|
end|
|
||||||
|
delimiter ;|
|
||||||
|
|
||||||
|
create user u1@localhost;
|
||||||
|
grant select on db1.* to u1@localhost;
|
||||||
|
grant execute on db1.* to u1@localhost;
|
||||||
|
|
||||||
|
use test;
|
||||||
|
create table t3 (id int unsigned default (20+nextval(db1.s1)), b int);
|
||||||
|
insert t3 values ();
|
||||||
|
|
||||||
|
create sequence s2 cache 0;
|
||||||
|
create table t4 (id int unsigned default (10+nextval(s2)), b int);
|
||||||
|
insert t4 values ();
|
||||||
|
|
||||||
|
connect u1,localhost,u1,,db1;
|
||||||
|
|
||||||
|
# table already in the cache. must be re-fixed
|
||||||
|
# SELECT * - no error
|
||||||
|
select * from t1;
|
||||||
|
|
||||||
|
# not in cache
|
||||||
|
connection default;
|
||||||
|
flush tables;
|
||||||
|
connection u1;
|
||||||
|
# SELECT * - no error
|
||||||
|
select * from t1;
|
||||||
|
|
||||||
|
# SELECT DEFAULT() - error
|
||||||
|
--error ER_TABLEACCESS_DENIED_ERROR
|
||||||
|
select default(id) from t1;
|
||||||
|
|
||||||
|
# default(default(nextval))
|
||||||
|
select * from t2;
|
||||||
|
|
||||||
|
# SELECT but table has TL_WRITE because of prelocking
|
||||||
|
select f1(100);
|
||||||
|
|
||||||
|
# opening the table for I_S
|
||||||
|
select column_name, data_type, column_default from information_schema.columns where table_schema='db1' and table_name='t1';
|
||||||
|
|
||||||
|
use test;
|
||||||
|
# insert
|
||||||
|
--error ER_TABLEACCESS_DENIED_ERROR
|
||||||
|
insert t3 values ();
|
||||||
|
insert t4 values ();
|
||||||
|
#insert select
|
||||||
|
--error ER_TABLEACCESS_DENIED_ERROR
|
||||||
|
insert t3 (b) select 5;
|
||||||
|
insert t4 (b) select 5;
|
||||||
|
#update
|
||||||
|
--error ER_TABLEACCESS_DENIED_ERROR
|
||||||
|
update t3 set id=default;
|
||||||
|
update t4 set id=default;
|
||||||
|
|
||||||
|
# PS UPDATE with ? = DEFAULT
|
||||||
|
prepare stmt from "update t3 set id=?";
|
||||||
|
--error ER_TABLEACCESS_DENIED_ERROR
|
||||||
|
execute stmt using default;
|
||||||
|
prepare stmt from "update t4 set id=?";
|
||||||
|
execute stmt using default;
|
||||||
|
deallocate prepare stmt;
|
||||||
|
|
||||||
|
# SELECT * in a subquery, like INSERT t3 VALUES ((SELECT * FROM t1));
|
||||||
|
# with sequences both on t3 and t1
|
||||||
|
insert t4 (b) values ((select * from db1.t1));
|
||||||
|
--error ER_TABLEACCESS_DENIED_ERROR
|
||||||
|
insert t4 (b) values ((select default(id) from db1.t1));
|
||||||
|
|
||||||
|
connection default;
|
||||||
|
disconnect u1;
|
||||||
|
--disable_ps2_protocol
|
||||||
|
select nextval(db1.s1) as 'must be 5';
|
||||||
|
--enable_ps2_protocol
|
||||||
|
drop user u1@localhost;
|
||||||
|
drop database db1;
|
||||||
|
drop table t3, t4, s2;
|
||||||
|
|
||||||
|
--echo # End of 10.6 tests
|
||||||
|
|||||||
@@ -658,6 +658,7 @@ public:
|
|||||||
bool fix_session_expr(THD *thd);
|
bool fix_session_expr(THD *thd);
|
||||||
bool cleanup_session_expr();
|
bool cleanup_session_expr();
|
||||||
bool fix_and_check_expr(THD *thd, TABLE *table);
|
bool fix_and_check_expr(THD *thd, TABLE *table);
|
||||||
|
bool check_access(THD *thd);
|
||||||
inline bool is_equal(const Virtual_column_info* vcol) const;
|
inline bool is_equal(const Virtual_column_info* vcol) const;
|
||||||
/* Same as is_equal() but for comparing with different table */
|
/* Same as is_equal() but for comparing with different table */
|
||||||
bool is_equivalent(THD *thd, TABLE_SHARE *share, TABLE_SHARE *vcol_share,
|
bool is_equivalent(THD *thd, TABLE_SHARE *share, TABLE_SHARE *vcol_share,
|
||||||
|
|||||||
@@ -6113,7 +6113,8 @@ int ha_create_table(THD *thd, const char *path, const char *db,
|
|||||||
|
|
||||||
name= get_canonical_filename(table.file, share.path.str, name_buff);
|
name= get_canonical_filename(table.file, share.path.str, name_buff);
|
||||||
|
|
||||||
error= table.file->ha_create(name, &table, create_info);
|
error= table.check_sequence_privileges(thd) ? 1 :
|
||||||
|
table.file->ha_create(name, &table, create_info);
|
||||||
|
|
||||||
if (unlikely(error))
|
if (unlikely(error))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5222,6 +5222,11 @@ static Field *make_default_field(THD *thd, Field *field_arg)
|
|||||||
if (!newptr)
|
if (!newptr)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
|
/* Don't check privileges, if it's parse_vcol_defs() */
|
||||||
|
if (def_field->table->pos_in_table_list &&
|
||||||
|
def_field->default_value->check_access(thd))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
if (should_mark_column(thd->column_usage))
|
if (should_mark_column(thd->column_usage))
|
||||||
def_field->default_value->expr->update_used_tables();
|
def_field->default_value->expr->update_used_tables();
|
||||||
def_field->move_field(newptr + 1, def_field->maybe_null() ? newptr : 0, 1);
|
def_field->move_field(newptr + 1, def_field->maybe_null() ? newptr : 0, 1);
|
||||||
|
|||||||
@@ -2432,6 +2432,7 @@ public:
|
|||||||
If there is some, sets a bit for this key in the proper key map.
|
If there is some, sets a bit for this key in the proper key map.
|
||||||
*/
|
*/
|
||||||
virtual bool check_index_dependence(void *arg) { return 0; }
|
virtual bool check_index_dependence(void *arg) { return 0; }
|
||||||
|
virtual bool check_sequence_privileges(void *arg) { return 0; }
|
||||||
/*============== End of Item processor list ======================*/
|
/*============== End of Item processor list ======================*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -7085,15 +7085,14 @@ longlong Item_func_cursor_rowcount::val_int()
|
|||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
SEQUENCE functions
|
SEQUENCE functions
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
bool Item_func_nextval::check_access_and_fix_fields(THD *thd, Item **ref,
|
bool Item_func_nextval::check_access(THD *thd, privilege_t want_access)
|
||||||
privilege_t want_access)
|
|
||||||
{
|
{
|
||||||
table_list->sequence= false;
|
table_list->sequence= false;
|
||||||
bool error= check_single_table_access(thd, want_access, table_list, false);
|
bool error= check_single_table_access(thd, want_access, table_list, false);
|
||||||
table_list->sequence= true;
|
table_list->sequence= true;
|
||||||
if (error && table_list->belong_to_view)
|
if (error && table_list->belong_to_view)
|
||||||
table_list->replace_view_error_with_generic(thd);
|
table_list->replace_view_error_with_generic(thd);
|
||||||
return error || Item_longlong_func::fix_fields(thd, ref);
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
longlong Item_func_nextval::val_int()
|
longlong Item_func_nextval::val_int()
|
||||||
|
|||||||
@@ -4228,7 +4228,7 @@ class Item_func_nextval :public Item_longlong_func
|
|||||||
protected:
|
protected:
|
||||||
TABLE_LIST *table_list;
|
TABLE_LIST *table_list;
|
||||||
TABLE *table;
|
TABLE *table;
|
||||||
bool check_access_and_fix_fields(THD *, Item **ref, privilege_t);
|
bool check_access(THD *, privilege_t);
|
||||||
public:
|
public:
|
||||||
Item_func_nextval(THD *thd, TABLE_LIST *table_list_arg):
|
Item_func_nextval(THD *thd, TABLE_LIST *table_list_arg):
|
||||||
Item_longlong_func(thd), table_list(table_list_arg) {}
|
Item_longlong_func(thd), table_list(table_list_arg) {}
|
||||||
@@ -4239,7 +4239,13 @@ public:
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
bool fix_fields(THD *thd, Item **ref) override
|
bool fix_fields(THD *thd, Item **ref) override
|
||||||
{ return check_access_and_fix_fields(thd, ref, INSERT_ACL | SELECT_ACL); }
|
{
|
||||||
|
/* Don't check privileges, if it's parse_vcol_defs() */
|
||||||
|
return (table_list->table && check_sequence_privileges(thd)) ||
|
||||||
|
Item_longlong_func::fix_fields(thd, ref);
|
||||||
|
}
|
||||||
|
bool check_sequence_privileges(void *thd) override
|
||||||
|
{ return check_access((THD*)thd, INSERT_ACL | SELECT_ACL); }
|
||||||
bool fix_length_and_dec() override
|
bool fix_length_and_dec() override
|
||||||
{
|
{
|
||||||
unsigned_flag= 0;
|
unsigned_flag= 0;
|
||||||
@@ -4281,8 +4287,8 @@ class Item_func_lastval :public Item_func_nextval
|
|||||||
public:
|
public:
|
||||||
Item_func_lastval(THD *thd, TABLE_LIST *table_list_arg):
|
Item_func_lastval(THD *thd, TABLE_LIST *table_list_arg):
|
||||||
Item_func_nextval(thd, table_list_arg) {}
|
Item_func_nextval(thd, table_list_arg) {}
|
||||||
bool fix_fields(THD *thd, Item **ref) override
|
bool check_sequence_privileges(void *thd) override
|
||||||
{ return check_access_and_fix_fields(thd, ref, SELECT_ACL); }
|
{ return check_access((THD*)thd, SELECT_ACL); }
|
||||||
longlong val_int() override;
|
longlong val_int() override;
|
||||||
LEX_CSTRING func_name_cstring() const override
|
LEX_CSTRING func_name_cstring() const override
|
||||||
{
|
{
|
||||||
@@ -4307,8 +4313,8 @@ public:
|
|||||||
: Item_func_nextval(thd, table_list_arg),
|
: Item_func_nextval(thd, table_list_arg),
|
||||||
nextval(nextval_arg), round(round_arg), is_used(is_used_arg)
|
nextval(nextval_arg), round(round_arg), is_used(is_used_arg)
|
||||||
{}
|
{}
|
||||||
bool fix_fields(THD *thd, Item **ref) override
|
bool check_sequence_privileges(void *thd) override
|
||||||
{ return check_access_and_fix_fields(thd, ref, INSERT_ACL); }
|
{ return check_access((THD*)thd, INSERT_ACL); }
|
||||||
longlong val_int() override;
|
longlong val_int() override;
|
||||||
LEX_CSTRING func_name_cstring() const override
|
LEX_CSTRING func_name_cstring() const override
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1622,6 +1622,9 @@ static bool mysql_prepare_insert_check_table(THD *thd, TABLE_LIST *table_list,
|
|||||||
DBUG_RETURN(insert_view_fields(thd, &fields, table_list));
|
DBUG_RETURN(insert_view_fields(thd, &fields, table_list));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (table_list->table->check_sequence_privileges(thd))
|
||||||
|
DBUG_RETURN(TRUE);
|
||||||
|
|
||||||
DBUG_RETURN(FALSE);
|
DBUG_RETURN(FALSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1506,6 +1506,11 @@ static int mysql_test_update(Prepared_statement *stmt,
|
|||||||
0, NULL, 0, THD_WHERE::SET_LIST) ||
|
0, NULL, 0, THD_WHERE::SET_LIST) ||
|
||||||
check_unique_table(thd, table_list))
|
check_unique_table(thd, table_list))
|
||||||
goto error;
|
goto error;
|
||||||
|
{
|
||||||
|
List_iterator_fast<Item> fs(select->item_list), vs(stmt->lex->value_list);
|
||||||
|
while (Item *f= fs++)
|
||||||
|
vs++->associate_with_target_field(thd, static_cast<Item_field*>(f));
|
||||||
|
}
|
||||||
/* TODO: here we should send types of placeholders to the client. */
|
/* TODO: here we should send types of placeholders to the client. */
|
||||||
DBUG_RETURN(0);
|
DBUG_RETURN(0);
|
||||||
error:
|
error:
|
||||||
|
|||||||
@@ -10789,7 +10789,8 @@ do_continue:;
|
|||||||
thd->count_cuted_fields= CHECK_FIELD_EXPRESSION;
|
thd->count_cuted_fields= CHECK_FIELD_EXPRESSION;
|
||||||
altered_table.reset_default_fields();
|
altered_table.reset_default_fields();
|
||||||
if (altered_table.default_field &&
|
if (altered_table.default_field &&
|
||||||
altered_table.update_default_fields(true))
|
(altered_table.check_sequence_privileges(thd) ||
|
||||||
|
altered_table.update_default_fields(true)))
|
||||||
{
|
{
|
||||||
cleanup_table_after_inplace_alter(&altered_table);
|
cleanup_table_after_inplace_alter(&altered_table);
|
||||||
goto err_new_table_cleanup;
|
goto err_new_table_cleanup;
|
||||||
|
|||||||
20
sql/table.cc
20
sql/table.cc
@@ -3744,6 +3744,19 @@ Vcol_expr_context::~Vcol_expr_context()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool TABLE::check_sequence_privileges(THD *thd)
|
||||||
|
{
|
||||||
|
if (internal_tables)
|
||||||
|
for (Field **fp= field; *fp; fp++)
|
||||||
|
{
|
||||||
|
Virtual_column_info *vcol= (*fp)->default_value;
|
||||||
|
if (vcol && vcol->check_access(thd))
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool TABLE::vcol_fix_expr(THD *thd)
|
bool TABLE::vcol_fix_expr(THD *thd)
|
||||||
{
|
{
|
||||||
if (pos_in_table_list->placeholder() || vcol_refix_list.is_empty())
|
if (pos_in_table_list->placeholder() || vcol_refix_list.is_empty())
|
||||||
@@ -3880,6 +3893,13 @@ bool Virtual_column_info::fix_and_check_expr(THD *thd, TABLE *table)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool Virtual_column_info::check_access(THD *thd)
|
||||||
|
{
|
||||||
|
return flags & VCOL_NEXTVAL &&
|
||||||
|
expr->walk(&Item::check_sequence_privileges, 0, thd);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@brief
|
@brief
|
||||||
Unpack the definition of a virtual column from its linear representation
|
Unpack the definition of a virtual column from its linear representation
|
||||||
|
|||||||
@@ -1716,6 +1716,7 @@ public:
|
|||||||
TABLE *tmp_table,
|
TABLE *tmp_table,
|
||||||
TMP_TABLE_PARAM *tmp_table_param,
|
TMP_TABLE_PARAM *tmp_table_param,
|
||||||
bool with_cleanup);
|
bool with_cleanup);
|
||||||
|
bool check_sequence_privileges(THD *thd);
|
||||||
bool vcol_fix_expr(THD *thd);
|
bool vcol_fix_expr(THD *thd);
|
||||||
bool vcol_cleanup_expr(THD *thd);
|
bool vcol_cleanup_expr(THD *thd);
|
||||||
Field *find_field_by_name(LEX_CSTRING *str) const;
|
Field *find_field_by_name(LEX_CSTRING *str) const;
|
||||||
|
|||||||
Reference in New Issue
Block a user