diff --git a/mysql-test/suite/sql_sequence/grant.result b/mysql-test/suite/sql_sequence/grant.result index d631772c740..f0b1bea6865 100644 --- a/mysql-test/suite/sql_sequence/grant.result +++ b/mysql-test/suite/sql_sequence/grant.result @@ -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 diff --git a/mysql-test/suite/sql_sequence/grant.test b/mysql-test/suite/sql_sequence/grant.test index 8c56de16525..becaf6a31fb 100644 --- a/mysql-test/suite/sql_sequence/grant.test +++ b/mysql-test/suite/sql_sequence/grant.test @@ -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 diff --git a/sql/field.h b/sql/field.h index ce61788c653..59dcd229b52 100644 --- a/sql/field.h +++ b/sql/field.h @@ -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, diff --git a/sql/handler.cc b/sql/handler.cc index bc96c35a306..754b5a7e5f8 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -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)) { diff --git a/sql/item.cc b/sql/item.cc index 33e62f32667..4a47b268f52 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -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); diff --git a/sql/item.h b/sql/item.h index 84c2d64218a..58f01cfb73d 100644 --- a/sql/item.h +++ b/sql/item.h @@ -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 ======================*/ /* diff --git a/sql/item_func.cc b/sql/item_func.cc index 85dd193a929..5592f96fa5e 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -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() diff --git a/sql/item_func.h b/sql/item_func.h index 43ea18069aa..5f9b02b32e7 100644 --- a/sql/item_func.h +++ b/sql/item_func.h @@ -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 { diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index ffc68305fa0..fa52164445f 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -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); } diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 431991c1d7a..b2bcf03820c 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -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 fs(select->item_list), vs(stmt->lex->value_list); + while (Item *f= fs++) + vs++->associate_with_target_field(thd, static_cast(f)); + } /* TODO: here we should send types of placeholders to the client. */ DBUG_RETURN(0); error: diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 4014f4101dd..a32bd87b223 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -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; diff --git a/sql/table.cc b/sql/table.cc index 119d949388a..683da12f7d9 100644 --- a/sql/table.cc +++ b/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 diff --git a/sql/table.h b/sql/table.h index ee26e2cd38b..efa369743ce 100644 --- a/sql/table.h +++ b/sql/table.h @@ -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;