1
0
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:
Sergei Golubchik
2025-06-30 15:44:50 +02:00
parent 1c7685f5fc
commit c27d78beb5
13 changed files with 224 additions and 18 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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))
{

View File

@@ -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);

View File

@@ -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 ======================*/
/*

View File

@@ -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()

View File

@@ -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
{

View File

@@ -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);
}

View File

@@ -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:

View File

@@ -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;

View File

@@ -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

View File

@@ -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;