Skip to content

Commit

Permalink
MDEV-23408 Wrong result upon query from I_S and further Assertion `!a…
Browse files Browse the repository at this point in the history
…lias_arg || strlen(alias_arg->str) == alias_arg->length' failed with certain connection charset

There were two independent problems which lead to the crash
and to the non-relevant records returned in I_S queries:

- The code in the I_S implementation was not secure
  about values with 0x00 bytes.
  It's fixed by using check_db_name() and check_table_name()
  inside make_table_name_list(), and by adding the test for
  0x00 inside check_table_name().

- The code in Item_string::print() did not convert
  strings without introducers when restoring
  the CREATE VIEW statement from an Item tree.
  This made wrong literals inside the "query" line in the view FRM file
  in cases when the VIEW parse time
  character_set_client!=character_set_connection.
  That's fixed by adding a proper conversion.

  This change also fixed a similar problem in SHOW PROCEDURE CODE -
  the literals were displayed in wrong character set in SP instructions
  in cases when the SP parse time
  character_set_client!=character_set_connection.
  • Loading branch information
abarkov committed Oct 14, 2021
1 parent bbae2d3 commit a2a42f4
Show file tree
Hide file tree
Showing 11 changed files with 257 additions and 3 deletions.
33 changes: 33 additions & 0 deletions mysql-test/r/ctype_utf16le.result
Expand Up @@ -3000,5 +3000,38 @@ DROP TABLE t1;
#
SET STORAGE_ENGINE=Default;
#
# MDEV-23408 Wrong result upon query from I_S and further Assertion `!alias_arg || strlen(alias_arg->str) == alias_arg->length' failed with certain connection charset
#
SET NAMES utf8;
SET SESSION character_set_connection= utf16le;
CREATE TABLE kv (v BLOB);
CREATE TABLE t (a INT);
CREATE VIEW v AS SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 't1';
LOAD DATA INFILE 'MYSQLD_DATADIR/test/v.frm' REPLACE INTO TABLE kv;
SELECT * FROM kv WHERE v LIKE _binary'query=%';
v
query=select `information_schema`.`TABLES`.`TABLE_CATALOG` AS `TABLE_CATALOG`,`information_schema`.`TABLES`.`TABLE_SCHEMA` AS `TABLE_SCHEMA`,`information_schema`.`TABLES`.`TABLE_NAME` AS `TABLE_NAME`,`information_schema`.`TABLES`.`TABLE_TYPE` AS `TABLE_TYPE`,`information_schema`.`TABLES`.`ENGINE` AS `ENGINE`,`information_schema`.`TABLES`.`VERSION` AS `VERSION`,`information_schema`.`TABLES`.`ROW_FORMAT` AS `ROW_FORMAT`,`information_schema`.`TABLES`.`TABLE_ROWS` AS `TABLE_ROWS`,`information_schema`.`TABLES`.`AVG_ROW_LENGTH` AS `AVG_ROW_LENGTH`,`information_schema`.`TABLES`.`DATA_LENGTH` AS `DATA_LENGTH`,`information_schema`.`TABLES`.`MAX_DATA_LENGTH` AS `MAX_DATA_LENGTH`,`information_schema`.`TABLES`.`INDEX_LENGTH` AS `INDEX_LENGTH`,`information_schema`.`TABLES`.`DATA_FREE` AS `DATA_FREE`,`information_schema`.`TABLES`.`AUTO_INCREMENT` AS `AUTO_INCREMENT`,`information_schema`.`TABLES`.`CREATE_TIME` AS `CREATE_TIME`,`information_schema`.`TABLES`.`UPDATE_TIME` AS `UPDATE_TIME`,`information_schema`.`TABLES`.`CHECK_TIME` AS `CHECK_TIME`,`information_schema`.`TABLES`.`TABLE_COLLATION` AS `TABLE_COLLATION`,`information_schema`.`TABLES`.`CHECKSUM` AS `CHECKSUM`,`information_schema`.`TABLES`.`CREATE_OPTIONS` AS `CREATE_OPTIONS`,`information_schema`.`TABLES`.`TABLE_COMMENT` AS `TABLE_COMMENT` from `INFORMATION_SCHEMA`.`TABLES` where `information_schema`.`TABLES`.`TABLE_NAME` = 't1'
TRUNCATE TABLE kv;
SELECT * FROM v;
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
LOCK TABLE t WRITE;
UNLOCK TABLES;
DROP VIEW v;
DROP TABLE t;
DROP TABLE kv;
CREATE TABLE t (a INT);
SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME=CONCAT('t',0x00,'1');
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
LOCK TABLE t WRITE;
UNLOCK TABLES;
DROP TABLE t;
CREATE TABLE t (a INT);
SELECT TABLE_NAME, HEX(TABLE_NAME) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME=CONCAT('t',0x00,'1');
TABLE_NAME HEX(TABLE_NAME)
SELECT TABLE_NAME, TABLE_SCHEMA, HEX(TABLE_NAME) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=CONCAT('test',0x00,'1');
TABLE_NAME TABLE_SCHEMA HEX(TABLE_NAME)
DROP TABLE t;
SET NAMES utf8;
#
# End of 10.2 tests
#
17 changes: 17 additions & 0 deletions mysql-test/r/ctype_utf8.result
Expand Up @@ -11239,5 +11239,22 @@ DROP TABLE t1;
#
SET STORAGE_ENGINE=Default;
#
# MDEV-23408 Wrong result upon query from I_S and further Assertion `!alias_arg || strlen(alias_arg->str) == alias_arg->length' failed with certain connection charset
#
SET NAMES utf8;
SET SESSION character_set_connection=latin1;
CREATE VIEW v1 AS SELECT 'ä' AS c1;
SELECT c1, HEX(c1) FROM v1;
c1 HEX(c1)
ä E4
CREATE TABLE kv (v BLOB);
LOAD DATA INFILE 'MYSQLD_DATADIR/test/v1.frm' REPLACE INTO TABLE kv;
SELECT * FROM kv WHERE v LIKE _binary'query=%';
v
query=select 'ä' AS `c1`
DROP TABLE kv;
DROP VIEW v1;
SET NAMES utf8;
#
# End of 10.2 tests
#
20 changes: 20 additions & 0 deletions mysql-test/r/information_schema.result
Expand Up @@ -2210,5 +2210,25 @@ SELECT * FROM v LIMIT ROWS EXAMINED 9;
ERROR HY000: Sort aborted:
DROP VIEW v;
#
# MDEV-23408 Wrong result upon query from I_S and further Assertion `!alias_arg || strlen(alias_arg->str) == alias_arg->length' failed with certain connection charset
#
CREATE TABLE t (a INT);
SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME=CONCAT('t',0x00,'1');
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
SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=CONCAT('test',0x00,'1');
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
DROP TABLE t;
CREATE TABLE `a/~.b` (a INT);
SELECT TABLE_SCHEMA, TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='a/~.b';
TABLE_SCHEMA TABLE_NAME
test a/~.b
DROP TABLE `a/~.b`;
CREATE DATABASE `a/~.b`;
CREATE TABLE `a/~.b`.t1 (a INT);
SELECT TABLE_SCHEMA, TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='a/~.b';
TABLE_SCHEMA TABLE_NAME
a/~.b t1
DROP DATABASE `a/~.b`;
#
# End of 10.2 Test
#
24 changes: 24 additions & 0 deletions mysql-test/r/sp-code.result
Expand Up @@ -971,3 +971,27 @@ Pos Instruction
DROP PROCEDURE testp_bug11763507;
DROP FUNCTION testf_bug11763507;
#END OF BUG#11763507 test.
#
# MDEV-23408 Wrong result upon query from I_S and further Assertion `!alias_arg || strlen(alias_arg->str) == alias_arg->length' failed with certain connection charset
#
SET NAMES utf8;
SET SESSION character_set_connection=latin1;
CREATE PROCEDURE p1()
BEGIN
DECLARE a VARCHAR(10) CHARACTER SET utf8;
SET a='ä';
SELECT a, 'ä' AS b;
END;
$$
SHOW PROCEDURE CODE p1;
Pos Instruction
0 set a@0 NULL
1 set a@0 'ä'
2 stmt 0 "SELECT a, 'ä' AS b"
CALL p1;
a b
ä ä
DROP PROCEDURE p1;
#
# End of 10.2 tests
#
37 changes: 37 additions & 0 deletions mysql-test/t/ctype_utf16le.test
Expand Up @@ -3,6 +3,7 @@
-- source include/have_utf32.inc
-- source include/have_utf8mb4.inc

let $MYSQLD_DATADIR= `select @@datadir`;

SET TIME_ZONE='+03:00';

Expand Down Expand Up @@ -810,6 +811,42 @@ let $coll='utf16le_nopad_bin';
let $coll_pad='utf16le_bin';
--source include/ctype_pad_all_engines.inc

--echo #
--echo # MDEV-23408 Wrong result upon query from I_S and further Assertion `!alias_arg || strlen(alias_arg->str) == alias_arg->length' failed with certain connection charset
--echo #



SET NAMES utf8;
SET SESSION character_set_connection= utf16le;

CREATE TABLE kv (v BLOB);
CREATE TABLE t (a INT);
CREATE VIEW v AS SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 't1';
--replace_result $MYSQLD_DATADIR MYSQLD_DATADIR
eval LOAD DATA INFILE '$MYSQLD_DATADIR/test/v.frm' REPLACE INTO TABLE kv;
SELECT * FROM kv WHERE v LIKE _binary'query=%';
TRUNCATE TABLE kv;
SELECT * FROM v;
LOCK TABLE t WRITE;
UNLOCK TABLES;
DROP VIEW v;
DROP TABLE t;
DROP TABLE kv;

CREATE TABLE t (a INT);
SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME=CONCAT('t',0x00,'1');
LOCK TABLE t WRITE;
UNLOCK TABLES;
DROP TABLE t;

CREATE TABLE t (a INT);
SELECT TABLE_NAME, HEX(TABLE_NAME) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME=CONCAT('t',0x00,'1');
SELECT TABLE_NAME, TABLE_SCHEMA, HEX(TABLE_NAME) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=CONCAT('test',0x00,'1');
DROP TABLE t;

SET NAMES utf8;

--echo #
--echo # End of 10.2 tests
--echo #
18 changes: 18 additions & 0 deletions mysql-test/t/ctype_utf8.test
Expand Up @@ -2,6 +2,8 @@
# Tests with the utf8 character set
#

let $MYSQLD_DATADIR= `select @@datadir`;

let collation=utf8_unicode_ci;
--source include/have_collation.inc
SET TIME_ZONE='+03:00';
Expand Down Expand Up @@ -2165,6 +2167,22 @@ let $coll='utf8_nopad_bin';
let $coll_pad='utf8_bin';
--source include/ctype_pad_all_engines.inc

--echo #
--echo # MDEV-23408 Wrong result upon query from I_S and further Assertion `!alias_arg || strlen(alias_arg->str) == alias_arg->length' failed with certain connection charset
--echo #

SET NAMES utf8;
SET SESSION character_set_connection=latin1;
CREATE VIEW v1 AS SELECT 'ä' AS c1;
SELECT c1, HEX(c1) FROM v1;
CREATE TABLE kv (v BLOB);
--replace_result $MYSQLD_DATADIR MYSQLD_DATADIR
eval LOAD DATA INFILE '$MYSQLD_DATADIR/test/v1.frm' REPLACE INTO TABLE kv;
SELECT * FROM kv WHERE v LIKE _binary'query=%';
DROP TABLE kv;
DROP VIEW v1;
SET NAMES utf8;

--echo #
--echo # End of 10.2 tests
--echo #
21 changes: 21 additions & 0 deletions mysql-test/t/information_schema.test
Expand Up @@ -1934,6 +1934,27 @@ SELECT * FROM v LIMIT ROWS EXAMINED 9;

DROP VIEW v;

--echo #
--echo # MDEV-23408 Wrong result upon query from I_S and further Assertion `!alias_arg || strlen(alias_arg->str) == alias_arg->length' failed with certain connection charset
--echo #

# Expect empty sets if requested TABLE_NAME or TABLE_SCHEMA with zero bytes
CREATE TABLE t (a INT);
SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME=CONCAT('t',0x00,'1');
SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=CONCAT('test',0x00,'1');
DROP TABLE t;

# Make sure check_table_name() does not reject special characters
CREATE TABLE `a/~.b` (a INT);
SELECT TABLE_SCHEMA, TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='a/~.b';
DROP TABLE `a/~.b`;

# Make sure check_db_name() does not reject special characters
CREATE DATABASE `a/~.b`;
CREATE TABLE `a/~.b`.t1 (a INT);
SELECT TABLE_SCHEMA, TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='a/~.b';
DROP DATABASE `a/~.b`;

--echo #
--echo # End of 10.2 Test
--echo #
24 changes: 24 additions & 0 deletions mysql-test/t/sp-code.test
Expand Up @@ -735,3 +735,27 @@ DROP PROCEDURE testp_bug11763507;
DROP FUNCTION testf_bug11763507;

--echo #END OF BUG#11763507 test.


--echo #
--echo # MDEV-23408 Wrong result upon query from I_S and further Assertion `!alias_arg || strlen(alias_arg->str) == alias_arg->length' failed with certain connection charset
--echo #

SET NAMES utf8;
SET SESSION character_set_connection=latin1;
DELIMITER $$;
CREATE PROCEDURE p1()
BEGIN
DECLARE a VARCHAR(10) CHARACTER SET utf8;
SET a='ä';
SELECT a, 'ä' AS b;
END;
$$
DELIMITER ;$$
SHOW PROCEDURE CODE p1;
CALL p1;
DROP PROCEDURE p1;

--echo #
--echo # End of 10.2 tests
--echo #
44 changes: 42 additions & 2 deletions sql/item.cc
Expand Up @@ -3338,8 +3338,48 @@ void Item_string::print(String *str, enum_query_type query_type)
}
else
{
// Caller wants a result in the charset of str_value.
str_value.print(str);
/*
We're restoring a parse-able statement from an Item tree.
Make sure to revert character set conversions that previously
happened in the parser when Item_string was created.
*/
if (print_introducer)
{
/*
Print the string as is, without conversion:
Strings with introducers are not converted in the parser.
*/
str_value.print(str);
}
else
{
/*
Print the string with conversion.
Strings without introducers are converted in the parser,
from character_set_client to character_set_connection.
When restoring a CREATE VIEW statement,
- str_value.charsets() contains parse time character_set_connection
- str->charset() contains parse time character_set_client
So we convert the string back from parse-time character_set_connection
to parse time character_set_client.
In some cases, e.g. SHOW PROCEDURE CODE, it's also possible
that str->charset() is "utf8mb3" instead of parse time
character_set_client. In these cases we convert
here from the parse-time character_set_connection to utf8mb3.
QQ: perhaps the code behind SHOW PROCEDURE CODE should
also request the result in the parse-time character_set_client
(like the code restoring CREATE VIEW statements does),
rather than in utf8mb3:
- utf8mb3 does not work well with non-BMP characters (e.g. emoji).
- Simply changing utf8mb3 to utf8mb4 will not fully help:
some character sets have unassigned characters,
they get lost during during cs->utf8mb4->cs round trip.
*/
str_value.print_with_conversion(str, str->charset());
}
}

str->append('\'');
Expand Down
7 changes: 6 additions & 1 deletion sql/sql_show.cc
Expand Up @@ -4227,7 +4227,9 @@ make_table_name_list(THD *thd, Dynamic_array<LEX_STRING*> *table_names,
if (!lookup_field_vals->wild_table_value &&
lookup_field_vals->table_value.str)
{
if (lookup_field_vals->table_value.length > NAME_LEN)
if (check_table_name(lookup_field_vals->table_value.str,
lookup_field_vals->table_value.length,
false))
{
/*
Impossible value for a table name,
Expand Down Expand Up @@ -4264,6 +4266,9 @@ make_table_name_list(THD *thd, Dynamic_array<LEX_STRING*> *table_names,
return (schema_tables_add(thd, table_names,
lookup_field_vals->table_value.str));

if (check_db_name((LEX_STRING*)db_name))
return 0; // Impossible TABLE_SCHEMA name

find_files_result res= find_files(thd, table_names, db_name, path,
&lookup_field_vals->table_value);
if (res != FIND_FILES_OK)
Expand Down
15 changes: 15 additions & 0 deletions sql/table.cc
Expand Up @@ -4183,6 +4183,21 @@ bool check_table_name(const char *name, size_t length, bool check_for_path_chars
if (check_for_path_chars &&
(*name == '/' || *name == '\\' || *name == '~' || *name == FN_EXTCHAR))
return 1;
/*
We don't allow zero byte in table/schema names:
- Some code still uses NULL-terminated strings.
Zero bytes will confuse this code.
- There is a little practical use of zero bytes in names anyway.
Note, if the string passed as "name" comes here
from the parser as an identifier, it does not contain zero bytes,
as the parser rejects zero bytes in identifiers.
But "name" can also come here from queries like this:
SELECT * FROM I_S.TABLES WHERE TABLE_NAME='str';
In this case "name" is a general string expression
and it can have any arbitrary bytes, including zero bytes.
*/
if (*name == 0x00)
return 1;
name++;
name_length++;
}
Expand Down

0 comments on commit a2a42f4

Please sign in to comment.