Skip to content

Commit 2647fd1

Browse files
committed
MDEV-19445 heap-use-after-free related to innodb_ft_aux_table
Try to fix the race conditions between SET GLOBAL innodb_ft_aux_table = ...; and access to the INFORMATION_SCHEMA tables that depend on this variable. innodb_ft_aux_table: Replaces fts_internal_tbl_name,fts_internal_tbl_name2. Just store the user-specified parameter as is. innodb_ft_aux_table_id: The table_id corresponding to SET GLOBAL innodb_ft_aux_table, or 0 if the table does not exist or does not contain FULLTEXT INDEX. If the table is renamed later, the INFORMATION_SCHEMA tables will continue to refer to the table. If the table is dropped or rebuilt, the INFORMATION_SCHEMA tables will not find the table.
1 parent 1c97e07 commit 2647fd1

File tree

14 files changed

+341
-318
lines changed

14 files changed

+341
-318
lines changed

mysql-test/suite/innodb/r/innodb_skip_innodb_is_tables.result

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,14 +319,24 @@ the
319319
www
320320
select * from information_schema.innodb_ft_deleted;
321321
DOC_ID
322+
Warnings:
323+
Warning 1012 InnoDB: SELECTing from INFORMATION_SCHEMA.innodb_ft_deleted but the InnoDB storage engine is not installed
322324
select * from information_schema.innodb_ft_being_deleted;
323325
DOC_ID
326+
Warnings:
327+
Warning 1012 InnoDB: SELECTing from INFORMATION_SCHEMA.innodb_ft_being_deleted but the InnoDB storage engine is not installed
324328
select * from information_schema.innodb_ft_index_cache;
325329
WORD FIRST_DOC_ID LAST_DOC_ID DOC_COUNT DOC_ID POSITION
330+
Warnings:
331+
Warning 1012 InnoDB: SELECTing from INFORMATION_SCHEMA.innodb_ft_index_cache but the InnoDB storage engine is not installed
326332
select * from information_schema.innodb_ft_index_table;
327333
WORD FIRST_DOC_ID LAST_DOC_ID DOC_COUNT DOC_ID POSITION
334+
Warnings:
335+
Warning 1012 InnoDB: SELECTing from INFORMATION_SCHEMA.innodb_ft_index_table but the InnoDB storage engine is not installed
328336
select * from information_schema.innodb_ft_config;
329337
KEY VALUE
338+
Warnings:
339+
Warning 1012 InnoDB: SELECTing from INFORMATION_SCHEMA.innodb_ft_config but the InnoDB storage engine is not installed
330340
select * from information_schema.innodb_buffer_page;
331341
POOL_ID BLOCK_ID SPACE PAGE_NUMBER PAGE_TYPE FLUSH_TYPE FIX_COUNT IS_HASHED NEWEST_MODIFICATION OLDEST_MODIFICATION ACCESS_TIME TABLE_NAME INDEX_NAME NUMBER_RECORDS DATA_SIZE COMPRESSED_SIZE PAGE_STATE IO_FIX IS_OLD FREE_PAGE_CLOCK
332342
Warnings:
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
CREATE TABLE t1 (v VARCHAR(100), FULLTEXT INDEX (v)) ENGINE=InnoDB;
2+
insert into t1 VALUES('First record'),('Second record'),('Third record');
3+
SET @save_ft_aux_table = @@GLOBAL.innodb_ft_aux_table;
4+
SET GLOBAL innodb_ft_aux_table = 'test/t0';
5+
ERROR 42000: Variable 'innodb_ft_aux_table' can't be set to the value of 'test/t0'
6+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD;
7+
value
8+
a
9+
about
10+
an
11+
are
12+
as
13+
at
14+
be
15+
by
16+
com
17+
de
18+
en
19+
for
20+
from
21+
how
22+
i
23+
in
24+
is
25+
it
26+
la
27+
of
28+
on
29+
or
30+
that
31+
the
32+
this
33+
to
34+
was
35+
what
36+
when
37+
where
38+
who
39+
will
40+
with
41+
und
42+
the
43+
www
44+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DELETED;
45+
DOC_ID
46+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_BEING_DELETED;
47+
DOC_ID
48+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE;
49+
WORD FIRST_DOC_ID LAST_DOC_ID DOC_COUNT DOC_ID POSITION
50+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_TABLE;
51+
WORD FIRST_DOC_ID LAST_DOC_ID DOC_COUNT DOC_ID POSITION
52+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_CONFIG;
53+
KEY VALUE
54+
SET GLOBAL innodb_ft_aux_table = 'test/t1';
55+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DELETED;
56+
DOC_ID
57+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_BEING_DELETED;
58+
DOC_ID
59+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE;
60+
WORD FIRST_DOC_ID LAST_DOC_ID DOC_COUNT DOC_ID POSITION
61+
first 1 1 1 1 0
62+
record 1 3 3 1 6
63+
record 1 3 3 2 7
64+
record 1 3 3 3 6
65+
second 2 2 1 2 0
66+
third 3 3 1 3 0
67+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_TABLE;
68+
WORD FIRST_DOC_ID LAST_DOC_ID DOC_COUNT DOC_ID POSITION
69+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_CONFIG;
70+
KEY VALUE
71+
optimize_checkpoint_limit 180
72+
synced_doc_id 0
73+
stopword_table_name
74+
use_stopword 1
75+
SELECT @@GLOBAL.innodb_ft_aux_table;
76+
@@GLOBAL.innodb_ft_aux_table
77+
test/t1
78+
RENAME TABLE t1 TO t2;
79+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DELETED;
80+
DOC_ID
81+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_BEING_DELETED;
82+
DOC_ID
83+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE;
84+
WORD FIRST_DOC_ID LAST_DOC_ID DOC_COUNT DOC_ID POSITION
85+
first 1 1 1 1 0
86+
record 1 3 3 1 6
87+
record 1 3 3 2 7
88+
record 1 3 3 3 6
89+
second 2 2 1 2 0
90+
third 3 3 1 3 0
91+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_TABLE;
92+
WORD FIRST_DOC_ID LAST_DOC_ID DOC_COUNT DOC_ID POSITION
93+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_CONFIG;
94+
KEY VALUE
95+
optimize_checkpoint_limit 180
96+
synced_doc_id 0
97+
stopword_table_name
98+
use_stopword 1
99+
SELECT @@GLOBAL.innodb_ft_aux_table;
100+
@@GLOBAL.innodb_ft_aux_table
101+
test/t1
102+
DROP TABLE t2;
103+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DELETED;
104+
DOC_ID
105+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_BEING_DELETED;
106+
DOC_ID
107+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE;
108+
WORD FIRST_DOC_ID LAST_DOC_ID DOC_COUNT DOC_ID POSITION
109+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_TABLE;
110+
WORD FIRST_DOC_ID LAST_DOC_ID DOC_COUNT DOC_ID POSITION
111+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_CONFIG;
112+
KEY VALUE
113+
SELECT @@GLOBAL.innodb_ft_aux_table;
114+
@@GLOBAL.innodb_ft_aux_table
115+
test/t1
116+
SET GLOBAL innodb_ft_aux_table = @save_ft_aux_table;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
--innodb_ft_default_stopword
2+
--innodb_ft_deleted
3+
--innodb_ft_being_deleted
4+
--innodb_ft_index_cache
5+
--innodb_ft_index_table
6+
--innodb_ft_config
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
--source include/have_innodb.inc
2+
3+
CREATE TABLE t1 (v VARCHAR(100), FULLTEXT INDEX (v)) ENGINE=InnoDB;
4+
5+
insert into t1 VALUES('First record'),('Second record'),('Third record');
6+
7+
SET @save_ft_aux_table = @@GLOBAL.innodb_ft_aux_table;
8+
9+
connect (con1,localhost,root,,);
10+
--error ER_WRONG_VALUE_FOR_VAR
11+
SET GLOBAL innodb_ft_aux_table = 'test/t0';
12+
connection default;
13+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD;
14+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DELETED;
15+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_BEING_DELETED;
16+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE;
17+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_TABLE;
18+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_CONFIG;
19+
connection con1;
20+
SET GLOBAL innodb_ft_aux_table = 'test/t1';
21+
disconnect con1;
22+
connection default;
23+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DELETED;
24+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_BEING_DELETED;
25+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE;
26+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_TABLE;
27+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_CONFIG;
28+
SELECT @@GLOBAL.innodb_ft_aux_table;
29+
RENAME TABLE t1 TO t2;
30+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DELETED;
31+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_BEING_DELETED;
32+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE;
33+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_TABLE;
34+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_CONFIG;
35+
SELECT @@GLOBAL.innodb_ft_aux_table;
36+
DROP TABLE t2;
37+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DELETED;
38+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_BEING_DELETED;
39+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE;
40+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_TABLE;
41+
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_CONFIG;
42+
SELECT @@GLOBAL.innodb_ft_aux_table;
43+
SET GLOBAL innodb_ft_aux_table = @save_ft_aux_table;

storage/innobase/fts/fts0fts.cc

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,6 @@ UNIV_INTERN mysql_pfs_key_t fts_doc_id_mutex_key;
107107
UNIV_INTERN mysql_pfs_key_t fts_pll_tokenize_mutex_key;
108108
#endif /* UNIV_PFS_MUTEX */
109109

110-
/** variable to record innodb_fts_internal_tbl_name for information
111-
schema table INNODB_FTS_INSERTED etc. */
112-
UNIV_INTERN char* fts_internal_tbl_name = NULL;
113-
UNIV_INTERN char* fts_internal_tbl_name2 = NULL;
114-
115110
/** InnoDB default stopword list:
116111
There are different versions of stopwords, the stop words listed
117112
below comes from "Google Stopword" list. Reference:

storage/innobase/handler/ha_innodb.cc

Lines changed: 30 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -17118,98 +17118,36 @@ innodb_stopword_table_validate(
1711817118
return(ret);
1711917119
}
1712017120

17121-
/*************************************************************//**
17122-
Check whether valid argument given to "innodb_fts_internal_tbl_name"
17123-
This function is registered as a callback with MySQL.
17124-
@return 0 for valid stopword table */
17125-
static
17126-
int
17127-
innodb_internal_table_validate(
17128-
/*===========================*/
17129-
THD* thd, /*!< in: thread handle */
17130-
struct st_mysql_sys_var* var, /*!< in: pointer to system
17131-
variable */
17132-
void* save, /*!< out: immediate result
17133-
for update function */
17134-
struct st_mysql_value* value) /*!< in: incoming string */
17135-
{
17136-
const char* table_name;
17137-
char buff[STRING_BUFFER_USUAL_SIZE];
17138-
int len = sizeof(buff);
17139-
int ret = 1;
17140-
dict_table_t* user_table;
17141-
17142-
ut_a(save != NULL);
17143-
ut_a(value != NULL);
17144-
17145-
table_name = value->val_str(value, buff, &len);
17146-
17147-
if (!table_name) {
17148-
*static_cast<const char**>(save) = NULL;
17149-
return(0);
17150-
}
17151-
17152-
user_table = dict_table_open_on_name(
17153-
table_name, FALSE, TRUE, DICT_ERR_IGNORE_NONE);
17154-
17155-
if (user_table) {
17156-
if (dict_table_has_fts_index(user_table)) {
17157-
*static_cast<const char**>(save) = table_name;
17158-
ret = 0;
17121+
/** The latest assigned innodb_ft_aux_table name */
17122+
static char* innodb_ft_aux_table;
17123+
17124+
/** Update innodb_ft_aux_table_id on SET GLOBAL innodb_ft_aux_table.
17125+
@param[out] save new value of innodb_ft_aux_table
17126+
@param[in] value user-specified value */
17127+
static int innodb_ft_aux_table_validate(THD*, st_mysql_sys_var*,
17128+
void* save, st_mysql_value* value)
17129+
{
17130+
char buf[STRING_BUFFER_USUAL_SIZE];
17131+
int len = sizeof buf;
17132+
17133+
if (const char* table_name = value->val_str(value, buf, &len)) {
17134+
if (dict_table_t* table = dict_table_open_on_name(
17135+
table_name, FALSE, TRUE, DICT_ERR_IGNORE_NONE)) {
17136+
const table_id_t id = dict_table_has_fts_index(table)
17137+
? table->id : 0;
17138+
dict_table_close(table, FALSE, FALSE);
17139+
if (id) {
17140+
innodb_ft_aux_table_id = id;
17141+
*static_cast<const char**>(save) = table_name;
17142+
return 0;
17143+
}
1715917144
}
1716017145

17161-
dict_table_close(user_table, FALSE, TRUE);
17162-
17163-
DBUG_EXECUTE_IF("innodb_evict_autoinc_table",
17164-
mutex_enter(&dict_sys->mutex);
17165-
dict_table_remove_from_cache_low(user_table, TRUE);
17166-
mutex_exit(&dict_sys->mutex);
17167-
);
17168-
}
17169-
17170-
return(ret);
17171-
}
17172-
17173-
/****************************************************************//**
17174-
Update global variable "fts_internal_tbl_name" with the "saved"
17175-
stopword table name value. This function is registered as a callback
17176-
with MySQL. */
17177-
static
17178-
void
17179-
innodb_internal_table_update(
17180-
/*=========================*/
17181-
THD* thd, /*!< in: thread handle */
17182-
struct st_mysql_sys_var* var, /*!< in: pointer to
17183-
system variable */
17184-
void* var_ptr,/*!< out: where the
17185-
formal string goes */
17186-
const void* save) /*!< in: immediate result
17187-
from check function */
17188-
{
17189-
const char* table_name;
17190-
char* old;
17191-
17192-
ut_a(save != NULL);
17193-
ut_a(var_ptr != NULL);
17194-
17195-
table_name = *static_cast<const char*const*>(save);
17196-
old = *(char**) var_ptr;
17197-
17198-
if (table_name) {
17199-
*(char**) var_ptr = my_strdup(table_name, MYF(0));
17200-
} else {
17201-
*(char**) var_ptr = NULL;
17202-
}
17203-
17204-
if (old) {
17205-
my_free(old);
17206-
}
17207-
17208-
fts_internal_tbl_name2 = *(char**) var_ptr;
17209-
if (fts_internal_tbl_name2 == NULL) {
17210-
fts_internal_tbl_name = const_cast<char*>("default");
17146+
return 1;
1721117147
} else {
17212-
fts_internal_tbl_name = fts_internal_tbl_name2;
17148+
*static_cast<char**>(save) = NULL;
17149+
innodb_ft_aux_table_id = 0;
17150+
return 0;
1721317151
}
1721417152
}
1721517153

@@ -19476,11 +19414,10 @@ static MYSQL_SYSVAR_BOOL(disable_sort_file_cache, srv_disable_sort_file_cache,
1947619414
"Whether to disable OS system file cache for sort I/O",
1947719415
NULL, NULL, FALSE);
1947819416

19479-
static MYSQL_SYSVAR_STR(ft_aux_table, fts_internal_tbl_name2,
19480-
PLUGIN_VAR_NOCMDARG,
19417+
static MYSQL_SYSVAR_STR(ft_aux_table, innodb_ft_aux_table,
19418+
PLUGIN_VAR_NOCMDARG | PLUGIN_VAR_MEMALLOC,
1948119419
"FTS internal auxiliary table to be checked",
19482-
innodb_internal_table_validate,
19483-
innodb_internal_table_update, NULL);
19420+
innodb_ft_aux_table_validate, NULL, NULL);
1948419421

1948519422
static MYSQL_SYSVAR_ULONG(ft_cache_size, fts_max_cache_size,
1948619423
PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,

0 commit comments

Comments
 (0)