summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mysql-test/r/grant.result16
-rw-r--r--mysql-test/suite/engines/README12
-rw-r--r--mysql-test/t/grant.test25
-rw-r--r--sql/mysql_priv.h2
-rw-r--r--sql/partition_info.cc4
-rw-r--r--sql/sql_parse.cc9
-rw-r--r--sql/sql_table.cc14
-rw-r--r--sql/sql_yacc.yy2
-rw-r--r--sql/table.cc29
-rw-r--r--tests/mysql_client_test.c45
10 files changed, 144 insertions, 14 deletions
diff --git a/mysql-test/r/grant.result b/mysql-test/r/grant.result
index 92beccd2a9e..6831ef6183d 100644
--- a/mysql-test/r/grant.result
+++ b/mysql-test/r/grant.result
@@ -1413,3 +1413,19 @@ DROP USER 'user1';
DROP USER 'user1'@'localhost';
DROP USER 'user2';
DROP DATABASE db1;
+CREATE DATABASE db1;
+CREATE DATABASE db2;
+GRANT SELECT ON db1.* to 'testbug'@localhost;
+USE db2;
+CREATE TABLE t1 (a INT);
+USE test;
+SELECT * FROM `../db2/tb2`;
+ERROR 42S02: Table 'db1.../db2/tb2' doesn't exist
+SELECT * FROM `../db2`.tb2;
+ERROR 42000: SELECT command denied to user 'testbug'@'localhost' for table 'tb2'
+SELECT * FROM `#mysql50#/../db2/tb2`;
+ERROR 42S02: Table 'db1.#mysql50#/../db2/tb2' doesn't exist
+DROP USER 'testbug'@localhost;
+DROP TABLE db2.t1;
+DROP DATABASE db1;
+DROP DATABASE db2;
diff --git a/mysql-test/suite/engines/README b/mysql-test/suite/engines/README
index 3ff6fccc689..0f89d5af67f 100644
--- a/mysql-test/suite/engines/README
+++ b/mysql-test/suite/engines/README
@@ -50,22 +50,22 @@ The following suites are included:
Known Issues
------------
-1) The 'funcs' and 'iuds' suites currently runs only against MySQL 5.1 server.
- Running them against MySQL 5.5 will generate errors.
-
-2) The folowing tests in the 'iuds' suite:
+1) The folowing tests in the 'iuds' suite:
- delete_decimal
- insert_decimal
- update_decimal
will return a 'Warning 1264 - Out of range value...' warning if run in a 32-bit environment.
Add the '--force' option to prevent the test run from aborting.
-3) The following tests in the 'funcs' suite will fail when run against the innodb_plugin:
+2) The following tests in the 'funcs' suite will fail when run against the innodb_plugin:
- crash_manycolumns_string (bug 50495)
- ix_unique_lob (bug 52056, masked by an 'Out of memory error' on some 32-bit platforms)
- ix_unique_string_length (bug 52056, masked by an 'Out of memory error' on some 32-bit platforms)
Add the '--force' option to prevent the test run from aborting.
-4) Some of the rpl_xxx tests in the 'funcs' suite require a secific binlog_forat setting and will be
+3) Some of the rpl_xxx tests in the 'funcs' suite require a secific binlog_forat setting and will be
skipped otherwise.
+4) Some of the rpl_xxx tests in the 'funcs' suite will report a 'Statement unsafe for replication' warning
+ when run againsr a server configured to use statement based replication.
+
diff --git a/mysql-test/t/grant.test b/mysql-test/t/grant.test
index bcd393bd6ab..cb8d3c63be8 100644
--- a/mysql-test/t/grant.test
+++ b/mysql-test/t/grant.test
@@ -1525,5 +1525,30 @@ DROP USER 'user1'@'localhost';
DROP USER 'user2';
DROP DATABASE db1;
+
+#
+# Bug #53371: COM_FIELD_LIST can be abused to bypass table level grants.
+#
+
+CREATE DATABASE db1;
+CREATE DATABASE db2;
+GRANT SELECT ON db1.* to 'testbug'@localhost;
+USE db2;
+CREATE TABLE t1 (a INT);
+USE test;
+connect (con1,localhost,testbug,,db1);
+--error ER_NO_SUCH_TABLE
+SELECT * FROM `../db2/tb2`;
+--error ER_TABLEACCESS_DENIED_ERROR
+SELECT * FROM `../db2`.tb2;
+--error ER_NO_SUCH_TABLE
+SELECT * FROM `#mysql50#/../db2/tb2`;
+connection default;
+disconnect con1;
+DROP USER 'testbug'@localhost;
+DROP TABLE db2.t1;
+DROP DATABASE db1;
+DROP DATABASE db2;
+
# Wait till we reached the initial number of concurrent sessions
--source include/wait_until_count_sessions.inc
diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h
index f410f6dbcc2..cbebdc6a144 100644
--- a/sql/mysql_priv.h
+++ b/sql/mysql_priv.h
@@ -2269,7 +2269,7 @@ void update_create_info_from_table(HA_CREATE_INFO *info, TABLE *form);
int rename_file_ext(const char * from,const char * to,const char * ext);
bool check_db_name(LEX_STRING *db);
bool check_column_name(const char *name);
-bool check_table_name(const char *name, uint length);
+bool check_table_name(const char *name, uint length, bool check_for_path_chars);
char *get_field(MEM_ROOT *mem, Field *field);
bool get_field(MEM_ROOT *mem, Field *field, class String *res);
int wild_case_compare(CHARSET_INFO *cs, const char *str,const char *wildstr);
diff --git a/sql/partition_info.cc b/sql/partition_info.cc
index ba9ea0e876e..6e2f7dfad26 100644
--- a/sql/partition_info.cc
+++ b/sql/partition_info.cc
@@ -972,7 +972,7 @@ bool partition_info::check_partition_info(THD *thd, handlerton **eng_type,
part_elem->engine_type= default_engine_type;
}
if (check_table_name(part_elem->partition_name,
- strlen(part_elem->partition_name)))
+ strlen(part_elem->partition_name), FALSE))
{
my_error(ER_WRONG_PARTITION_NAME, MYF(0));
goto end;
@@ -990,7 +990,7 @@ bool partition_info::check_partition_info(THD *thd, handlerton **eng_type,
{
sub_elem= sub_it++;
if (check_table_name(sub_elem->partition_name,
- strlen(sub_elem->partition_name)))
+ strlen(sub_elem->partition_name), FALSE))
{
my_error(ER_WRONG_PARTITION_NAME, MYF(0));
goto end;
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
index 11481933c8a..93d80164ffb 100644
--- a/sql/sql_parse.cc
+++ b/sql/sql_parse.cc
@@ -1310,6 +1310,13 @@ bool dispatch_command(enum enum_server_command command, THD *thd,
}
thd->convert_string(&conv_name, system_charset_info,
packet, arg_length, thd->charset());
+ if (check_table_name(conv_name.str, conv_name.length, FALSE))
+ {
+ /* this is OK due to convert_string() null-terminating the string */
+ my_error(ER_WRONG_TABLE_NAME, MYF(0), conv_name.str);
+ break;
+ }
+
table_list.alias= table_list.table_name= conv_name.str;
packet= arg_end + 1;
@@ -6233,7 +6240,7 @@ TABLE_LIST *st_select_lex::add_table_to_list(THD *thd,
DBUG_RETURN(0); // End of memory
alias_str= alias ? alias->str : table->table.str;
if (!test(table_options & TL_OPTION_ALIAS) &&
- check_table_name(table->table.str, table->table.length))
+ check_table_name(table->table.str, table->table.length, FALSE))
{
my_error(ER_WRONG_TABLE_NAME, MYF(0), table->table.str);
DBUG_RETURN(0);
diff --git a/sql/sql_table.cc b/sql/sql_table.cc
index 1101be67d5e..2a2daacf724 100644
--- a/sql/sql_table.cc
+++ b/sql/sql_table.cc
@@ -435,7 +435,21 @@ uint tablename_to_filename(const char *from, char *to, uint to_length)
DBUG_PRINT("enter", ("from '%s'", from));
if ((length= check_n_cut_mysql50_prefix(from, to, to_length)))
+ {
+ /*
+ Check if the name supplied is a valid mysql 5.0 name and
+ make the name a zero length string if it's not.
+ Note that just returning zero length is not enough :
+ a lot of places don't check the return value and expect
+ a zero terminated string.
+ */
+ if (check_table_name(to, length, TRUE))
+ {
+ to[0]= 0;
+ length= 0;
+ }
DBUG_RETURN(length);
+ }
length= strconvert(system_charset_info, from,
&my_charset_filename, to, to_length, &errors);
if (check_if_legal_tablename(to) &&
diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy
index 4f43ab8bebd..f815da006b1 100644
--- a/sql/sql_yacc.yy
+++ b/sql/sql_yacc.yy
@@ -6133,7 +6133,7 @@ alter_list_item:
{
MYSQL_YYABORT;
}
- if (check_table_name($3->table.str,$3->table.length) ||
+ if (check_table_name($3->table.str,$3->table.length, FALSE) ||
($3->db.str && check_db_name(&$3->db)))
{
my_error(ER_WRONG_TABLE_NAME, MYF(0), $3->table.str);
diff --git a/sql/table.cc b/sql/table.cc
index a4e2c59fb87..04d7b3a8d0a 100644
--- a/sql/table.cc
+++ b/sql/table.cc
@@ -494,6 +494,26 @@ inline bool is_system_table_name(const char *name, uint length)
}
+/**
+ Check if a string contains path elements
+*/
+
+static inline bool has_disabled_path_chars(const char *str)
+{
+ for (; *str; str++)
+ switch (*str)
+ {
+ case FN_EXTCHAR:
+ case '/':
+ case '\\':
+ case '~':
+ case '@':
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
/*
Read table definition from a binary / text based .frm file
@@ -549,7 +569,8 @@ int open_table_def(THD *thd, TABLE_SHARE *share, uint db_flags)
This kind of tables must have been opened only by the
my_open() above.
*/
- if (strchr(share->table_name.str, '@') ||
+ if (has_disabled_path_chars(share->table_name.str) ||
+ has_disabled_path_chars(share->db.str) ||
!strncmp(share->db.str, MYSQL50_TABLE_NAME_PREFIX,
MYSQL50_TABLE_NAME_PREFIX_LENGTH) ||
!strncmp(share->table_name.str, MYSQL50_TABLE_NAME_PREFIX,
@@ -2711,7 +2732,6 @@ bool check_db_name(LEX_STRING *org_name)
(name_length > NAME_CHAR_LEN)); /* purecov: inspected */
}
-
/*
Allow anything as a table name, as long as it doesn't contain an
' ' at the end
@@ -2719,7 +2739,7 @@ bool check_db_name(LEX_STRING *org_name)
*/
-bool check_table_name(const char *name, uint length)
+bool check_table_name(const char *name, uint length, bool check_for_path_chars)
{
uint name_length= 0; // name length in symbols
const char *end= name+length;
@@ -2746,6 +2766,9 @@ bool check_table_name(const char *name, uint length)
continue;
}
}
+ if (check_for_path_chars &&
+ (*name == '/' || *name == '\\' || *name == '~' || *name == FN_EXTCHAR))
+ return 1;
#endif
name++;
name_length++;
diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c
index f65e549fd96..b99461ecd06 100644
--- a/tests/mysql_client_test.c
+++ b/tests/mysql_client_test.c
@@ -18049,6 +18049,50 @@ static void test_bug44495()
DBUG_VOID_RETURN;
}
+static void test_bug53371()
+{
+ int rc;
+ MYSQL_RES *result;
+
+ myheader("test_bug53371");
+
+ rc= mysql_query(mysql, "DROP TABLE IF EXISTS t1");
+ myquery(rc);
+ rc= mysql_query(mysql, "DROP DATABASE IF EXISTS bug53371");
+ myquery(rc);
+ rc= mysql_query(mysql, "DROP USER 'testbug'@localhost");
+
+ rc= mysql_query(mysql, "CREATE TABLE t1 (a INT)");
+ myquery(rc);
+ rc= mysql_query(mysql, "CREATE DATABASE bug53371");
+ myquery(rc);
+ rc= mysql_query(mysql, "GRANT SELECT ON bug53371.* to 'testbug'@localhost");
+ myquery(rc);
+
+ rc= mysql_change_user(mysql, "testbug", NULL, "bug53371");
+ myquery(rc);
+
+ rc= mysql_query(mysql, "SHOW COLUMNS FROM client_test_db.t1");
+ DIE_UNLESS(rc);
+ DIE_UNLESS(mysql_errno(mysql) == 1142);
+
+ result= mysql_list_fields(mysql, "../client_test_db/t1", NULL);
+ DIE_IF(result);
+
+ result= mysql_list_fields(mysql, "#mysql50#/../client_test_db/t1", NULL);
+ DIE_IF(result);
+
+ rc= mysql_change_user(mysql, opt_user, opt_password, current_db);
+ myquery(rc);
+ rc= mysql_query(mysql, "DROP TABLE t1");
+ myquery(rc);
+ rc= mysql_query(mysql, "DROP DATABASE bug53371");
+ myquery(rc);
+ rc= mysql_query(mysql, "DROP USER 'testbug'@localhost");
+ myquery(rc);
+}
+
+
/*
Read and parse arguments and MySQL options from my.cnf
*/
@@ -18358,6 +18402,7 @@ static struct my_tests_st my_tests[]= {
{ "test_bug30472", test_bug30472 },
{ "test_bug20023", test_bug20023 },
{ "test_bug45010", test_bug45010 },
+ { "test_bug53371", test_bug53371 },
{ "test_bug31418", test_bug31418 },
{ "test_bug31669", test_bug31669 },
{ "test_bug28386", test_bug28386 },