summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Barkov <bar@mariadb.com>2022-04-13 17:17:17 +0400
committerAlexander Barkov <bar@mariadb.com>2022-04-21 09:51:11 +0400
commit2be617d869e614e2c0e7313fd77649a03c891cfd (patch)
treed1f788014294c6f451f98a36937b8eea0f76e4d7
parent5ba77222e9fe7af8ff403816b5338b18b342053c (diff)
downloadmariadb-git-2be617d869e614e2c0e7313fd77649a03c891cfd.tar.gz
MDEV-25243 ASAN heap-use-after-free in Item_func_sp::execute_impl upon concurrent view DDL and I_S query with view and function
-rw-r--r--mysql-test/r/information_schema_columns.result47
-rw-r--r--mysql-test/r/information_schema_tables.result77
-rw-r--r--mysql-test/t/information_schema_columns.test48
-rw-r--r--mysql-test/t/information_schema_tables.test95
-rw-r--r--sql/sp_cache.cc12
-rw-r--r--sql/sql_class.cc9
-rw-r--r--sql/sql_class.h50
-rw-r--r--sql/sql_show.cc31
8 files changed, 358 insertions, 11 deletions
diff --git a/mysql-test/r/information_schema_columns.result b/mysql-test/r/information_schema_columns.result
new file mode 100644
index 00000000000..3624a6db368
--- /dev/null
+++ b/mysql-test/r/information_schema_columns.result
@@ -0,0 +1,47 @@
+#
+# Start of 10.2 tests
+#
+#
+# MDEV-25243 ASAN heap-use-after-free in Item_func_sp::execute_impl upon concurrent view DDL and I_S query with view and function
+#
+CREATE FUNCTION f1() RETURNS INT RETURN 1;
+CREATE VIEW v01 AS SELECT f1();
+CREATE VIEW v02 AS SELECT f1();
+connect con1,localhost,root,,;
+SELECT GET_LOCK('v01',30);
+GET_LOCK('v01',30)
+1
+SELECT GET_LOCK('v02',30);
+GET_LOCK('v02',30)
+1
+connection default;
+SELECT * FROM INFORMATION_SCHEMA.COLUMNS
+WHERE TABLE_SCHEMA='test'
+ AND TABLE_NAME LIKE 'v0%'
+ AND GET_LOCK(TABLE_NAME,30)
+AND RELEASE_LOCK(TABLE_NAME)
+AND f1()=1
+ORDER BY TABLE_NAME;
+connection con1;
+connection con1;
+SELECT RELEASE_LOCK('v01') /* Let the first row evaluate f1 */;
+RELEASE_LOCK('v01')
+1
+CREATE FUNCTION f2() RETURNS INT RETURN 1 /* Invalidate SP cache*/;
+SELECT RELEASE_LOCK('v02') /* Let the second row evaluate f1() */;
+RELEASE_LOCK('v02')
+1
+DROP FUNCTION f2;
+disconnect con1;
+connection default;
+SELECT RELEASE_LOCK('v01');
+RELEASE_LOCK('v01')
+NULL
+SELECT RELEASE_LOCK('v02');
+RELEASE_LOCK('v02')
+NULL
+DROP VIEW v01, v02;
+DROP FUNCTION f1;
+#
+# End of 10.2 tests
+#
diff --git a/mysql-test/r/information_schema_tables.result b/mysql-test/r/information_schema_tables.result
new file mode 100644
index 00000000000..33dafaa39eb
--- /dev/null
+++ b/mysql-test/r/information_schema_tables.result
@@ -0,0 +1,77 @@
+#
+# Start of 10.2 tests
+#
+#
+# MDEV-25243 ASAN heap-use-after-free in Item_func_sp::execute_impl upon concurrent view DDL and I_S query with view and function
+#
+# The originally reported non-deterministic test.
+# It did not fail reliably on every run.
+CREATE TABLE t (a INT);
+INSERT INTO t VALUES (1),(2);
+CREATE FUNCTION f(b INT) RETURNS INT RETURN 1;
+CREATE VIEW v AS SELECT f(SUM(a)) FROM t;
+connect con1,localhost,root,,test;
+LOOP
+CREATE OR REPLACE VIEW vv AS SELECT 1;
+END LOOP $
+connection default;
+SELECT v.* FROM v JOIN INFORMATION_SCHEMA.TABLES WHERE DATA_LENGTH = -1;
+f(SUM(a))
+KILL CONID;
+disconnect con1;
+connection default;
+DROP VIEW IF EXISTS vv;
+DROP VIEW v;
+DROP FUNCTION f;
+DROP TABLE t;
+# The second test version from the MDEV.
+# It failed more reliably, but still was not deterministic.
+CREATE FUNCTION f() RETURNS INT RETURN 1;
+CREATE VIEW v AS SELECT f() FROM seq_1_to_10;
+SELECT * FROM INFORMATION_SCHEMA.TABLES, v;;
+connect con1,localhost,root,,;
+CREATE VIEW v2 AS SELECT 1;
+connection default;
+disconnect con1;
+DROP VIEW v;
+DROP VIEW v2;
+DROP FUNCTION f;
+# The third test version from the MDEV.
+# It failed reliably, and should be deterninistic.
+CREATE FUNCTION f1() RETURNS INT RETURN 1;
+CREATE VIEW v01 AS SELECT f1();
+CREATE VIEW v02 AS SELECT f1();
+connect con1,localhost,root,,;
+SELECT GET_LOCK('v01',30);
+GET_LOCK('v01',30)
+1
+SELECT GET_LOCK('v02',30);
+GET_LOCK('v02',30)
+1
+connection default;
+SELECT * FROM INFORMATION_SCHEMA.TABLES
+WHERE TABLE_SCHEMA='test'
+ AND TABLE_NAME LIKE 'v0%'
+ AND GET_LOCK(TABLE_NAME,30)
+AND RELEASE_LOCK(TABLE_NAME)
+AND f1()=1
+ORDER BY TABLE_NAME;
+connection con1;
+SELECT RELEASE_LOCK('v01') /* Let the first row evaluate f1 */;
+RELEASE_LOCK('v01')
+1
+CREATE FUNCTION f2() RETURNS INT RETURN 1 /* Invalidate SP cache*/;
+SELECT RELEASE_LOCK('v02') /* Let the second row evaluate f1() */;
+RELEASE_LOCK('v02')
+1
+DROP FUNCTION f2;
+disconnect con1;
+connection default;
+TABLE_CATALOG TABLE_SCHEMA TABLE_NAME TABLE_TYPE ENGINE VERSION ROW_FORMAT TABLE_ROWS AVG_ROW_LENGTH DATA_LENGTH MAX_DATA_LENGTH INDEX_LENGTH DATA_FREE AUTO_INCREMENT CREATE_TIME UPDATE_TIME CHECK_TIME TABLE_COLLATION CHECKSUM CREATE_OPTIONS TABLE_COMMENT
+def test v01 VIEW NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL VIEW
+def test v02 VIEW NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL VIEW
+DROP VIEW v01, v02;
+DROP FUNCTION f1;
+#
+# End of 10.2 tests
+#
diff --git a/mysql-test/t/information_schema_columns.test b/mysql-test/t/information_schema_columns.test
new file mode 100644
index 00000000000..0171b221110
--- /dev/null
+++ b/mysql-test/t/information_schema_columns.test
@@ -0,0 +1,48 @@
+--echo #
+--echo # Start of 10.2 tests
+--echo #
+
+--echo #
+--echo # MDEV-25243 ASAN heap-use-after-free in Item_func_sp::execute_impl upon concurrent view DDL and I_S query with view and function
+--echo #
+
+
+CREATE FUNCTION f1() RETURNS INT RETURN 1;
+CREATE VIEW v01 AS SELECT f1();
+CREATE VIEW v02 AS SELECT f1();
+
+--connect(con1,localhost,root,,)
+SELECT GET_LOCK('v01',30);
+SELECT GET_LOCK('v02',30);
+--connection default
+
+--send
+ SELECT * FROM INFORMATION_SCHEMA.COLUMNS
+ WHERE TABLE_SCHEMA='test'
+ AND TABLE_NAME LIKE 'v0%'
+ AND GET_LOCK(TABLE_NAME,30)
+ AND RELEASE_LOCK(TABLE_NAME)
+ AND f1()=1
+ ORDER BY TABLE_NAME;
+
+--connection con1
+--connection con1
+SELECT RELEASE_LOCK('v01') /* Let the first row evaluate f1 */;
+CREATE FUNCTION f2() RETURNS INT RETURN 1 /* Invalidate SP cache*/;
+SELECT RELEASE_LOCK('v02') /* Let the second row evaluate f1() */;
+DROP FUNCTION f2;
+--disconnect con1
+
+--connection default
+--disable_result_log
+--reap
+--enable_result_log
+SELECT RELEASE_LOCK('v01');
+SELECT RELEASE_LOCK('v02');
+
+DROP VIEW v01, v02;
+DROP FUNCTION f1;
+
+--echo #
+--echo # End of 10.2 tests
+--echo #
diff --git a/mysql-test/t/information_schema_tables.test b/mysql-test/t/information_schema_tables.test
new file mode 100644
index 00000000000..ebb1e21a65b
--- /dev/null
+++ b/mysql-test/t/information_schema_tables.test
@@ -0,0 +1,95 @@
+--source include/have_sequence.inc
+
+--echo #
+--echo # Start of 10.2 tests
+--echo #
+
+--echo #
+--echo # MDEV-25243 ASAN heap-use-after-free in Item_func_sp::execute_impl upon concurrent view DDL and I_S query with view and function
+--echo #
+
+--echo # The originally reported non-deterministic test.
+--echo # It did not fail reliably on every run.
+
+CREATE TABLE t (a INT);
+INSERT INTO t VALUES (1),(2);
+CREATE FUNCTION f(b INT) RETURNS INT RETURN 1;
+CREATE VIEW v AS SELECT f(SUM(a)) FROM t;
+--connect (con1,localhost,root,,test)
+--let $conid= `SELECT CONNECTION_ID()`
+--delimiter $
+--send
+LOOP
+ CREATE OR REPLACE VIEW vv AS SELECT 1;
+END LOOP $
+--delimiter ;
+--connection default
+--disable_warnings
+SELECT v.* FROM v JOIN INFORMATION_SCHEMA.TABLES WHERE DATA_LENGTH = -1;
+--enable_warnings
+# Cleanup
+--replace_result $conid CONID
+--eval KILL $conid
+--disconnect con1
+--connection default
+DROP VIEW IF EXISTS vv;
+DROP VIEW v;
+DROP FUNCTION f;
+DROP TABLE t;
+
+
+--echo # The second test version from the MDEV.
+--echo # It failed more reliably, but still was not deterministic.
+
+
+CREATE FUNCTION f() RETURNS INT RETURN 1;
+CREATE VIEW v AS SELECT f() FROM seq_1_to_10;
+--send SELECT * FROM INFORMATION_SCHEMA.TABLES, v;
+--connect(con1,localhost,root,,)
+CREATE VIEW v2 AS SELECT 1;
+--connection default
+--disable_result_log
+--reap
+--enable_result_log
+--disconnect con1
+DROP VIEW v;
+DROP VIEW v2;
+DROP FUNCTION f;
+
+--echo # The third test version from the MDEV.
+--echo # It failed reliably, and should be deterninistic.
+
+CREATE FUNCTION f1() RETURNS INT RETURN 1;
+CREATE VIEW v01 AS SELECT f1();
+CREATE VIEW v02 AS SELECT f1();
+
+--connect(con1,localhost,root,,)
+SELECT GET_LOCK('v01',30);
+SELECT GET_LOCK('v02',30);
+--connection default
+
+--send
+ SELECT * FROM INFORMATION_SCHEMA.TABLES
+ WHERE TABLE_SCHEMA='test'
+ AND TABLE_NAME LIKE 'v0%'
+ AND GET_LOCK(TABLE_NAME,30)
+ AND RELEASE_LOCK(TABLE_NAME)
+ AND f1()=1
+ ORDER BY TABLE_NAME;
+
+--connection con1
+SELECT RELEASE_LOCK('v01') /* Let the first row evaluate f1 */;
+CREATE FUNCTION f2() RETURNS INT RETURN 1 /* Invalidate SP cache*/;
+SELECT RELEASE_LOCK('v02') /* Let the second row evaluate f1() */;
+DROP FUNCTION f2;
+--disconnect con1
+--connection default
+--reap
+
+
+DROP VIEW v01, v02;
+DROP FUNCTION f1;
+
+--echo #
+--echo # End of 10.2 tests
+--echo #
diff --git a/sql/sp_cache.cc b/sql/sp_cache.cc
index bc91634eb32..9829167d29d 100644
--- a/sql/sp_cache.cc
+++ b/sql/sp_cache.cc
@@ -313,3 +313,15 @@ sp_cache::cleanup()
{
my_hash_free(&m_hashtable);
}
+
+
+void Sp_caches::sp_caches_clear()
+{
+ sp_cache_clear(&sp_proc_cache);
+ sp_cache_clear(&sp_func_cache);
+#if MYSQL_VERSION_ID >= 100300
+#error Remove the preprocessor condition, !!!but keep the code!!!
+ sp_cache_clear(&sp_package_spec_cache);
+ sp_cache_clear(&sp_package_body_cache);
+#endif
+}
diff --git a/sql/sql_class.cc b/sql/sql_class.cc
index 4edf573e596..aa64e42e144 100644
--- a/sql/sql_class.cc
+++ b/sql/sql_class.cc
@@ -806,9 +806,6 @@ THD::THD(my_thread_id id, bool is_wsrep_applier)
(my_hash_get_key) get_var_key,
(my_hash_free_key) free_user_var, HASH_THREAD_SPECIFIC);
- sp_proc_cache= NULL;
- sp_func_cache= NULL;
-
/* For user vars replication*/
if (opt_bin_log)
my_init_dynamic_array(&user_var_events,
@@ -1353,8 +1350,7 @@ void THD::change_user(void)
my_hash_init(&user_vars, system_charset_info, USER_VARS_HASH_SIZE, 0, 0,
(my_hash_get_key) get_var_key,
(my_hash_free_key) free_user_var, 0);
- sp_cache_clear(&sp_proc_cache);
- sp_cache_clear(&sp_func_cache);
+ sp_caches_clear();
}
@@ -1410,8 +1406,7 @@ void THD::cleanup(void)
#endif /* defined(ENABLED_DEBUG_SYNC) */
my_hash_free(&user_vars);
- sp_cache_clear(&sp_proc_cache);
- sp_cache_clear(&sp_func_cache);
+ sp_caches_clear();
auto_inc_intervals_forced.empty();
auto_inc_intervals_in_cur_stmt_for_binlog.empty();
diff --git a/sql/sql_class.h b/sql/sql_class.h
index 54c9d4ac870..cceefb1793c 100644
--- a/sql/sql_class.h
+++ b/sql/sql_class.h
@@ -2116,6 +2116,51 @@ struct wait_for_commit
};
+class Sp_caches
+{
+public:
+ sp_cache *sp_proc_cache;
+ sp_cache *sp_func_cache;
+#if MYSQL_VERSION_ID >= 100300
+#error Remove the preprocessor condition, !!!but keep the code!!!
+ sp_cache *sp_package_spec_cache;
+ sp_cache *sp_package_body_cache;
+#endif
+ Sp_caches()
+ :sp_proc_cache(NULL),
+ sp_func_cache(NULL)
+#if MYSQL_VERSION_ID >= 100300
+#error Remove the preprocessor condition, !!!but keep the code!!!
+ ,
+ sp_package_spec_cache(NULL),
+ sp_package_body_cache(NULL)
+#endif
+ { }
+ ~Sp_caches()
+ {
+ // All caches must be freed by the caller explicitly
+ DBUG_ASSERT(sp_proc_cache == NULL);
+ DBUG_ASSERT(sp_func_cache == NULL);
+#if MYSQL_VERSION_ID >= 100300
+#error Remove the preprocessor condition, !!!but keep the code!!!
+ DBUG_ASSERT(sp_package_spec_cache == NULL);
+ DBUG_ASSERT(sp_package_body_cache == NULL);
+#endif
+ }
+ void sp_caches_swap(Sp_caches &rhs)
+ {
+ swap_variables(sp_cache*, sp_proc_cache, rhs.sp_proc_cache);
+ swap_variables(sp_cache*, sp_func_cache, rhs.sp_func_cache);
+#if MYSQL_VERSION_ID >= 100300
+#error Remove the preprocessor condition, !!!but keep the code!!!
+ swap_variables(sp_cache*, sp_package_spec_cache, rhs.sp_package_spec_cache);
+ swap_variables(sp_cache*, sp_package_body_cache, rhs.sp_package_body_cache);
+#endif
+ }
+ void sp_caches_clear();
+};
+
+
extern "C" void my_message_sql(uint error, const char *str, myf MyFlags);
class THD;
@@ -2139,7 +2184,8 @@ class THD :public Statement,
*/
public Item_change_list,
public MDL_context_owner,
- public Open_tables_state
+ public Open_tables_state,
+ public Sp_caches
{
private:
inline bool is_stmt_prepare() const
@@ -3089,8 +3135,6 @@ public:
int slave_expected_error;
sp_rcontext *spcont; // SP runtime context
- sp_cache *sp_proc_cache;
- sp_cache *sp_func_cache;
/** number of name_const() substitutions, see sp_head.cc:subst_spvars() */
uint query_name_consts;
diff --git a/sql/sql_show.cc b/sql/sql_show.cc
index c708849d049..ee295eb3834 100644
--- a/sql/sql_show.cc
+++ b/sql/sql_show.cc
@@ -4907,6 +4907,7 @@ public:
int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond)
{
+ DBUG_ENTER("get_all_tables");
LEX *lex= thd->lex;
TABLE *table= tables->table;
TABLE_LIST table_acl_check;
@@ -4923,7 +4924,28 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond)
#endif
uint table_open_method= tables->table_open_method;
bool can_deadlock;
- DBUG_ENTER("get_all_tables");
+ /*
+ We're going to open FRM files for tables.
+ In case of VIEWs that contain stored function calls,
+ these stored functions will be parsed and put to the SP cache.
+
+ Suppose we have a view containing a stored function call:
+ CREATE VIEW v1 AS SELECT f1() AS c1;
+ and now we're running:
+ SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME=f1();
+ If a parallel thread invalidates the cache,
+ e.g. by creating or dropping some stored routine,
+ the SELECT query will re-parse f1() when processing "v1"
+ and replace the outdated cached version of f1() to a new one.
+ But the old version of f1() is referenced from the m_sp member
+ of the Item_func_sp instances used in the WHERE condition.
+ We cannot destroy it. To avoid such clashes, let's remember
+ all old routines into a temporary SP cache collection
+ and process tables with a new empty temporary SP cache collection.
+ Then restore to the old SP cache collection at the end.
+ */
+ Sp_caches old_sp_caches;
+ old_sp_caches.sp_caches_swap(*thd);
/*
In cases when SELECT from I_S table being filled by this call is
@@ -5092,6 +5114,13 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond)
err:
thd->restore_backup_open_tables_state(&open_tables_state_backup);
+ /*
+ Now restore to the saved SP cache collection
+ and clear the temporary SP cache collection.
+ */
+ old_sp_caches.sp_caches_swap(*thd);
+ old_sp_caches.sp_caches_clear();
+
DBUG_RETURN(error);
}