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 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
|
||||
--connection default
|
||||
drop user u_alter;
|
||||
|
||||
#
|
||||
# Cleanup
|
||||
#
|
||||
|
||||
drop database mysqltest_1;
|
||||
|
||||
--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 #
|
||||
|
||||
# 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 cleanup_session_expr();
|
||||
bool fix_and_check_expr(THD *thd, TABLE *table);
|
||||
bool check_access(THD *thd);
|
||||
inline bool is_equal(const Virtual_column_info* vcol) const;
|
||||
/* Same as is_equal() but for comparing with different table */
|
||||
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);
|
||||
|
||||
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))
|
||||
{
|
||||
|
||||
@@ -5222,6 +5222,11 @@ static Field *make_default_field(THD *thd, Field *field_arg)
|
||||
if (!newptr)
|
||||
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))
|
||||
def_field->default_value->expr->update_used_tables();
|
||||
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.
|
||||
*/
|
||||
virtual bool check_index_dependence(void *arg) { return 0; }
|
||||
virtual bool check_sequence_privileges(void *arg) { return 0; }
|
||||
/*============== End of Item processor list ======================*/
|
||||
|
||||
/*
|
||||
|
||||
@@ -7085,15 +7085,14 @@ longlong Item_func_cursor_rowcount::val_int()
|
||||
/*****************************************************************************
|
||||
SEQUENCE functions
|
||||
*****************************************************************************/
|
||||
bool Item_func_nextval::check_access_and_fix_fields(THD *thd, Item **ref,
|
||||
privilege_t want_access)
|
||||
bool Item_func_nextval::check_access(THD *thd, privilege_t want_access)
|
||||
{
|
||||
table_list->sequence= false;
|
||||
bool error= check_single_table_access(thd, want_access, table_list, false);
|
||||
table_list->sequence= true;
|
||||
if (error && table_list->belong_to_view)
|
||||
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()
|
||||
|
||||
@@ -4228,7 +4228,7 @@ class Item_func_nextval :public Item_longlong_func
|
||||
protected:
|
||||
TABLE_LIST *table_list;
|
||||
TABLE *table;
|
||||
bool check_access_and_fix_fields(THD *, Item **ref, privilege_t);
|
||||
bool check_access(THD *, privilege_t);
|
||||
public:
|
||||
Item_func_nextval(THD *thd, TABLE_LIST *table_list_arg):
|
||||
Item_longlong_func(thd), table_list(table_list_arg) {}
|
||||
@@ -4239,7 +4239,13 @@ public:
|
||||
return name;
|
||||
}
|
||||
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
|
||||
{
|
||||
unsigned_flag= 0;
|
||||
@@ -4281,8 +4287,8 @@ class Item_func_lastval :public Item_func_nextval
|
||||
public:
|
||||
Item_func_lastval(THD *thd, TABLE_LIST *table_list_arg):
|
||||
Item_func_nextval(thd, table_list_arg) {}
|
||||
bool fix_fields(THD *thd, Item **ref) override
|
||||
{ return check_access_and_fix_fields(thd, ref, SELECT_ACL); }
|
||||
bool check_sequence_privileges(void *thd) override
|
||||
{ return check_access((THD*)thd, SELECT_ACL); }
|
||||
longlong val_int() override;
|
||||
LEX_CSTRING func_name_cstring() const override
|
||||
{
|
||||
@@ -4307,8 +4313,8 @@ public:
|
||||
: Item_func_nextval(thd, table_list_arg),
|
||||
nextval(nextval_arg), round(round_arg), is_used(is_used_arg)
|
||||
{}
|
||||
bool fix_fields(THD *thd, Item **ref) override
|
||||
{ return check_access_and_fix_fields(thd, ref, INSERT_ACL); }
|
||||
bool check_sequence_privileges(void *thd) override
|
||||
{ return check_access((THD*)thd, INSERT_ACL); }
|
||||
longlong val_int() 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));
|
||||
}
|
||||
|
||||
if (table_list->table->check_sequence_privileges(thd))
|
||||
DBUG_RETURN(TRUE);
|
||||
|
||||
DBUG_RETURN(FALSE);
|
||||
}
|
||||
|
||||
|
||||
@@ -1506,6 +1506,11 @@ static int mysql_test_update(Prepared_statement *stmt,
|
||||
0, NULL, 0, THD_WHERE::SET_LIST) ||
|
||||
check_unique_table(thd, table_list))
|
||||
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. */
|
||||
DBUG_RETURN(0);
|
||||
error:
|
||||
|
||||
@@ -10789,7 +10789,8 @@ do_continue:;
|
||||
thd->count_cuted_fields= CHECK_FIELD_EXPRESSION;
|
||||
altered_table.reset_default_fields();
|
||||
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);
|
||||
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)
|
||||
{
|
||||
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
|
||||
Unpack the definition of a virtual column from its linear representation
|
||||
|
||||
@@ -1716,6 +1716,7 @@ public:
|
||||
TABLE *tmp_table,
|
||||
TMP_TABLE_PARAM *tmp_table_param,
|
||||
bool with_cleanup);
|
||||
bool check_sequence_privileges(THD *thd);
|
||||
bool vcol_fix_expr(THD *thd);
|
||||
bool vcol_cleanup_expr(THD *thd);
|
||||
Field *find_field_by_name(LEX_CSTRING *str) const;
|
||||
|
||||
Reference in New Issue
Block a user