Commit 547dfc0e authored by Sergei Golubchik's avatar Sergei Golubchik

MDEV-32500 Information schema leaks table names and structure to unauthorized users

standard table KEY_COLUMN_USAGE should only show keys where
a user has some privileges on every column of the key

standard table TABLE_CONSTRAINTS should show tables where
a user has any non-SELECT privilege on the table or on any column
of the table

standard table REFERENTIAL_CONSTRAINTS is defined in terms of
TABLE_CONSTRAINTS, so the same rule applies. If the user
has no rights to see the REFERENCED_TABLE_NAME value, it should be NULL

SHOW INDEX (and STATISTICS table) is non-standard, but it seems
reasonable to use the same logic as for KEY_COLUMN_USAGE.
parent 2eee0e9b
......@@ -101,7 +101,6 @@ ERROR 42000: SELECT command denied to user 'mysqltest_u1'@'localhost' for table
** SHOW INDEX FROM t6 will succeed because there exist a privilege on a column combination on t6.
SHOW INDEX FROM t6;
Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
t6 1 i 1 s1 A NULL NULL NULL YES BTREE
** CHECK TABLE requires any privilege on any column combination and should succeed for t6:
CHECK TABLE t6;
Table Op Msg_type Msg_text
......
......@@ -262,8 +262,8 @@ set global sql_mode=default;
create user foo@localhost;
grant select on test.* to foo@localhost;
create procedure rootonly() select 1;
create sql security definer view v1d as select current_user(),user from information_schema.processlist;
create sql security invoker view v1i as select current_user(),user from information_schema.processlist;
create sql security definer view v1d as select current_user(),user from information_schema.processlist where command!='daemon';
create sql security invoker view v1i as select current_user(),user from information_schema.processlist where command!='daemon';
create sql security definer view v2d as select table_name from information_schema.tables where table_schema='mysql' and table_name like '%user%';
create sql security invoker view v2i as select table_name from information_schema.tables where table_schema='mysql' and table_name like '%user%';
create sql security definer view v3d as select schema_name from information_schema.schemata where schema_name like '%mysql%';
......@@ -341,3 +341,47 @@ drop procedure rootonly;
#
# End of 10.2 tests
#
#
# MDEV-32500 Information schema leaks table names and structure to unauthorized users
#
create database db;
create table db.t1 (x int, key(x)) engine=InnoDB;
create table db.t2 (a int, b int, c int, unique(b), check(c>b), foreign key(c) references db.t1(x)) engine=InnoDB;
create table db.t3 (d int, e int, f int, unique(e), check(f>e), foreign key(f) references db.t1(x),
foreign key(e) references db.t2(b),
foreign key(d) references db.t3(f)
) engine=InnoDB;
create user u@localhost;
grant select (a) on db.t2 to u@localhost;
grant update (d) on db.t3 to u@localhost;
connect con1,localhost,u,,db;
select table_name, column_name from information_schema.columns where table_name like 't_';
table_name column_name
t2 a
t3 d
select table_name, column_name from information_schema.key_column_usage where table_name like 't_';
table_name column_name
select table_name, unique_constraint_name, referenced_table_name from information_schema.referential_constraints where table_name like 't_';
table_name unique_constraint_name referenced_table_name
t3 x NULL
t3 b NULL
t3 f t3
select table_name, constraint_name, constraint_type from information_schema.table_constraints where table_name like 't_';
table_name constraint_name constraint_type
t3 e UNIQUE
t3 CONSTRAINT_1 CHECK
t3 t3_ibfk_1 FOREIGN KEY
t3 t3_ibfk_2 FOREIGN KEY
t3 t3_ibfk_3 FOREIGN KEY
show index in t2;
Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
show index in t3;
Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
t3 1 d 1 d A 0 NULL NULL YES BTREE
disconnect con1;
connection default;
drop user u@localhost;
drop database db;
#
# End of 10.4 tests
#
# this test mostly test privilege control (what doesn't work
# in the embedded server by default). So skip the test in embedded-server mode.
-- source include/not_embedded.inc
-- source include/have_innodb.inc
-- source include/testdb_only.inc
set local sql_mode="";
......@@ -256,8 +256,8 @@ set global sql_mode=default;
create user foo@localhost;
grant select on test.* to foo@localhost;
create procedure rootonly() select 1;
create sql security definer view v1d as select current_user(),user from information_schema.processlist;
create sql security invoker view v1i as select current_user(),user from information_schema.processlist;
create sql security definer view v1d as select current_user(),user from information_schema.processlist where command!='daemon';
create sql security invoker view v1i as select current_user(),user from information_schema.processlist where command!='daemon';
create sql security definer view v2d as select table_name from information_schema.tables where table_schema='mysql' and table_name like '%user%';
create sql security invoker view v2i as select table_name from information_schema.tables where table_schema='mysql' and table_name like '%user%';
create sql security definer view v3d as select schema_name from information_schema.schemata where schema_name like '%mysql%';
......@@ -297,3 +297,36 @@ drop procedure rootonly;
--echo #
--echo # End of 10.2 tests
--echo #
--echo #
--echo # MDEV-32500 Information schema leaks table names and structure to unauthorized users
--echo #
create database db;
create table db.t1 (x int, key(x)) engine=InnoDB;
create table db.t2 (a int, b int, c int, unique(b), check(c>b), foreign key(c) references db.t1(x)) engine=InnoDB;
create table db.t3 (d int, e int, f int, unique(e), check(f>e), foreign key(f) references db.t1(x),
foreign key(e) references db.t2(b),
foreign key(d) references db.t3(f)
) engine=InnoDB;
create user u@localhost;
grant select (a) on db.t2 to u@localhost;
grant update (d) on db.t3 to u@localhost;
--connect con1,localhost,u,,db
--sorted_result
select table_name, column_name from information_schema.columns where table_name like 't_';
select table_name, column_name from information_schema.key_column_usage where table_name like 't_';
select table_name, unique_constraint_name, referenced_table_name from information_schema.referential_constraints where table_name like 't_';
select table_name, constraint_name, constraint_type from information_schema.table_constraints where table_name like 't_';
show index in t2;
show index in t3;
--disconnect con1
--connection default
drop user u@localhost;
drop database db;
--echo #
--echo # End of 10.4 tests
--echo #
......@@ -287,7 +287,7 @@ def information_schema REFERENTIAL_CONSTRAINTS CONSTRAINT_NAME 3 NULL NO varchar
def information_schema REFERENTIAL_CONSTRAINTS CONSTRAINT_SCHEMA 2 NULL NO varchar 64 192 NULL NULL NULL utf8 utf8_general_ci varchar(64) select NEVER NULL
def information_schema REFERENTIAL_CONSTRAINTS DELETE_RULE 9 NULL NO varchar 64 192 NULL NULL NULL utf8 utf8_general_ci varchar(64) select NEVER NULL
def information_schema REFERENTIAL_CONSTRAINTS MATCH_OPTION 7 NULL NO varchar 64 192 NULL NULL NULL utf8 utf8_general_ci varchar(64) select NEVER NULL
def information_schema REFERENTIAL_CONSTRAINTS REFERENCED_TABLE_NAME 11 NULL NO varchar 64 192 NULL NULL NULL utf8 utf8_general_ci varchar(64) select NEVER NULL
def information_schema REFERENTIAL_CONSTRAINTS REFERENCED_TABLE_NAME 11 NULL YES varchar 64 192 NULL NULL NULL utf8 utf8_general_ci varchar(64) select NEVER NULL
def information_schema REFERENTIAL_CONSTRAINTS TABLE_NAME 10 NULL NO varchar 64 192 NULL NULL NULL utf8 utf8_general_ci varchar(64) select NEVER NULL
def information_schema REFERENTIAL_CONSTRAINTS UNIQUE_CONSTRAINT_CATALOG 4 NULL NO varchar 512 1536 NULL NULL NULL utf8 utf8_general_ci varchar(512) select NEVER NULL
def information_schema REFERENTIAL_CONSTRAINTS UNIQUE_CONSTRAINT_NAME 6 NULL YES varchar 64 192 NULL NULL NULL utf8 utf8_general_ci varchar(64) select NEVER NULL
......
......@@ -287,7 +287,7 @@ def information_schema REFERENTIAL_CONSTRAINTS CONSTRAINT_NAME 3 NULL NO varchar
def information_schema REFERENTIAL_CONSTRAINTS CONSTRAINT_SCHEMA 2 NULL NO varchar 64 192 NULL NULL NULL utf8 utf8_general_ci varchar(64) NEVER NULL
def information_schema REFERENTIAL_CONSTRAINTS DELETE_RULE 9 NULL NO varchar 64 192 NULL NULL NULL utf8 utf8_general_ci varchar(64) NEVER NULL
def information_schema REFERENTIAL_CONSTRAINTS MATCH_OPTION 7 NULL NO varchar 64 192 NULL NULL NULL utf8 utf8_general_ci varchar(64) NEVER NULL
def information_schema REFERENTIAL_CONSTRAINTS REFERENCED_TABLE_NAME 11 NULL NO varchar 64 192 NULL NULL NULL utf8 utf8_general_ci varchar(64) NEVER NULL
def information_schema REFERENTIAL_CONSTRAINTS REFERENCED_TABLE_NAME 11 NULL YES varchar 64 192 NULL NULL NULL utf8 utf8_general_ci varchar(64) NEVER NULL
def information_schema REFERENTIAL_CONSTRAINTS TABLE_NAME 10 NULL NO varchar 64 192 NULL NULL NULL utf8 utf8_general_ci varchar(64) NEVER NULL
def information_schema REFERENTIAL_CONSTRAINTS UNIQUE_CONSTRAINT_CATALOG 4 NULL NO varchar 512 1536 NULL NULL NULL utf8 utf8_general_ci varchar(512) NEVER NULL
def information_schema REFERENTIAL_CONSTRAINTS UNIQUE_CONSTRAINT_NAME 6 NULL YES varchar 64 192 NULL NULL NULL utf8 utf8_general_ci varchar(64) NEVER NULL
......
......@@ -250,8 +250,6 @@ ORDER BY table_schema,table_name,index_name,seq_in_index,column_name;
TABLE_CATALOG TABLE_SCHEMA TABLE_NAME NON_UNIQUE INDEX_SCHEMA INDEX_NAME SEQ_IN_INDEX COLUMN_NAME COLLATION CARDINALITY SUB_PART PACKED NULLABLE INDEX_TYPE COMMENT INDEX_COMMENT
def db_datadict t1 1 db_datadict f2_ind 1 f2 NULL 0 NULL NULL YES HASH
def db_datadict t1 0 db_datadict PRIMARY 1 f1 NULL 0 NULL NULL HASH
def db_datadict_2 t3 1 db_datadict_2 f2f1_ind 1 f2 NULL NULL NULL NULL YES HASH
def db_datadict_2 t3 1 db_datadict_2 f2f1_ind 2 f1 NULL 0 NULL NULL HASH
def db_datadict_2 t3 0 db_datadict_2 f5 1 f5 NULL 0 NULL NULL YES HASH
def db_datadict_2 t3 0 db_datadict_2 PRIMARY 1 f1 NULL 0 NULL NULL HASH
SHOW GRANTS FOR 'testuser1'@'localhost';
......@@ -282,8 +280,6 @@ SELECT * FROM information_schema.statistics
WHERE table_schema LIKE 'db_datadict%'
ORDER BY table_schema,table_name,index_name,seq_in_index,column_name;
TABLE_CATALOG TABLE_SCHEMA TABLE_NAME NON_UNIQUE INDEX_SCHEMA INDEX_NAME SEQ_IN_INDEX COLUMN_NAME COLLATION CARDINALITY SUB_PART PACKED NULLABLE INDEX_TYPE COMMENT INDEX_COMMENT
def db_datadict_2 t3 1 db_datadict_2 f2f1_ind 1 f2 NULL NULL NULL NULL YES HASH
def db_datadict_2 t3 1 db_datadict_2 f2f1_ind 2 f1 NULL 0 NULL NULL HASH
def db_datadict_2 t3 0 db_datadict_2 f5 1 f5 NULL 0 NULL NULL YES HASH
def db_datadict_2 t3 0 db_datadict_2 PRIMARY 1 f1 NULL 0 NULL NULL HASH
SHOW GRANTS FOR 'testuser1'@'localhost';
......
......@@ -104,11 +104,11 @@ CREATE TABLE db_datadict.t2 (f1 BIGINT, f2 BIGINT, f3 BIGINT, f4 BIGINT,
f5 BIGINT, f6 BIGINT, PRIMARY KEY (f1,f2))
ENGINE = <some_engine_type>;
CREATE USER 'testuser1'@'localhost';
GRANT SELECT(f5) ON db_datadict.t1 TO 'testuser1'@'localhost';
GRANT SELECT(f5), UPDATE(f6) ON db_datadict.t1 TO 'testuser1'@'localhost';
SHOW GRANTS FOR 'testuser1'@'localhost';
Grants for testuser1@localhost
GRANT USAGE ON *.* TO `testuser1`@`localhost`
GRANT SELECT (`f5`) ON `db_datadict`.`t1` TO `testuser1`@`localhost`
GRANT SELECT (`f5`), UPDATE (`f6`) ON `db_datadict`.`t1` TO `testuser1`@`localhost`
SELECT * FROM information_schema.table_constraints
WHERE table_schema = 'db_datadict'
ORDER BY table_schema,table_name, constraint_name;
......@@ -132,7 +132,7 @@ connect testuser1, localhost, testuser1, , db_datadict;
SHOW GRANTS FOR 'testuser1'@'localhost';
Grants for testuser1@localhost
GRANT USAGE ON *.* TO `testuser1`@`localhost`
GRANT SELECT (`f5`) ON `db_datadict`.`t1` TO `testuser1`@`localhost`
GRANT SELECT (`f5`), UPDATE (`f6`) ON `db_datadict`.`t1` TO `testuser1`@`localhost`
SELECT * FROM information_schema.table_constraints
WHERE table_schema = 'db_datadict'
ORDER BY table_schema,table_name, constraint_name;
......@@ -142,11 +142,6 @@ def db_datadict my_idx2 db_datadict t1 UNIQUE
def db_datadict PRIMARY db_datadict t1 PRIMARY KEY
SHOW INDEXES FROM db_datadict.t1;
Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
t1 0 PRIMARY 1 f1 ### ### ### ### ### ### ###
t1 0 PRIMARY 2 f2 ### ### ### ### ### ### ###
t1 0 my_idx1 1 f6 ### ### ### ### ### ### ###
t1 0 my_idx1 2 f1 ### ### ### ### ### ### ###
t1 0 my_idx2 1 f3 ### ### ### ### ### ### ###
SHOW INDEXES FROM db_datadict.t2;
ERROR 42000: SELECT command denied to user 'testuser1'@'localhost' for table `db_datadict`.`t2`
connection default;
......
......@@ -99,7 +99,7 @@ CREATE TABLE db_datadict.t2 (f1 BIGINT, f2 BIGINT, f3 BIGINT, f4 BIGINT,
ENGINE = $engine_type;
CREATE USER 'testuser1'@'localhost';
GRANT SELECT(f5) ON db_datadict.t1 TO 'testuser1'@'localhost';
GRANT SELECT(f5), UPDATE(f6) ON db_datadict.t1 TO 'testuser1'@'localhost';
SHOW GRANTS FOR 'testuser1'@'localhost';
let $my_select = SELECT * FROM information_schema.table_constraints
......
......@@ -7011,6 +7011,7 @@ static bool check_show_access(THD *thd, TABLE_LIST *table)
FALSE, FALSE))
return TRUE; /* Access denied */
thd->col_access= dst_table->grant.privilege; // for sql_show.cc
/*
Check_grant will grant access if there is any column privileges on
all of the tables thanks to the fourth parameter (bool show_table).
......
......@@ -6782,6 +6782,21 @@ static int get_schema_stat_record(THD *thd, TABLE_LIST *tables,
HA_STATUS_CONST | HA_STATUS_TIME);
set_statistics_for_table(thd, show_table);
}
#ifndef NO_EMBEDDED_ACCESS_CHECKS
bool need_column_checks= false;
/* we know that the table or at least some of the columns have
necessary privileges, but the caller didn't pass down the GRANT_INFO
object, so we have to rediscover everything again :( */
if (!(thd->col_access & TABLE_ACLS))
{
check_grant(thd, SELECT_ACL, tables, 0, 1, 1);
if (!(tables->grant.privilege & TABLE_ACLS))
need_column_checks= true;
}
#endif
for (uint i=0 ; i < show_table->s->keys ; i++,key_info++)
{
if ((key_info->flags & HA_INVISIBLE_KEY) &&
......@@ -6790,6 +6805,26 @@ static int get_schema_stat_record(THD *thd, TABLE_LIST *tables,
KEY_PART_INFO *key_part= key_info->key_part;
LEX_CSTRING *str;
LEX_CSTRING unknown= {STRING_WITH_LEN("?unknown field?") };
#ifndef NO_EMBEDDED_ACCESS_CHECKS
if (need_column_checks)
{
uint j;
for (j=0 ; j < key_info->user_defined_key_parts ; j++,key_part++)
{
uint access= get_column_grant(thd, &tables->grant, db_name->str,
table_name->str,
key_part->field->field_name.str);
if (!access)
break;
}
if (j != key_info->user_defined_key_parts)
continue;
key_part= key_info->key_part;
}
#endif
for (uint j=0 ; j < key_info->user_defined_key_parts ; j++,key_part++)
{
if (key_part->field->invisible >= INVISIBLE_SYSTEM &&
......@@ -7102,6 +7137,21 @@ static int get_schema_constraints_record(THD *thd, TABLE_LIST *tables,
}
else if (!tables->view)
{
#ifndef NO_EMBEDDED_ACCESS_CHECKS
/* need any non-SELECT privilege on the table or any of its columns */
const ulong need= TABLE_ACLS & ~SELECT_ACL;
if (!(thd->col_access & need))
{
/* we know that the table or at least some of the columns have
necessary privileges, but the caller didn't pass down the GRANT_INFO
object, so we have to rediscover everything again :( */
check_grant(thd, SELECT_ACL, tables, 0, 1, 1);
if (!(tables->grant.all_privilege() & need))
DBUG_RETURN(0);
}
#endif
List<FOREIGN_KEY_INFO> f_key_list;
TABLE *show_table= tables->table;
KEY *key_info=show_table->s->key_info;
......@@ -7299,12 +7349,46 @@ static int get_schema_key_column_usage_record(THD *thd, TABLE_LIST *tables,
uint primary_key= show_table->s->primary_key;
show_table->file->info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK |
HA_STATUS_TIME);
#ifndef NO_EMBEDDED_ACCESS_CHECKS
bool need_column_checks= false;
/* we know that the table or at least some of the columns have
necessary privileges, but the caller didn't pass down the GRANT_INFO
object, so we have to rediscover everything again :( */
if (!(thd->col_access & TABLE_ACLS))
{
check_grant(thd, SELECT_ACL, tables, 0, 1, 1);
if (!(tables->grant.privilege & TABLE_ACLS))
need_column_checks= true;
}
#endif
for (uint i=0 ; i < show_table->s->keys ; i++, key_info++)
{
if (i != primary_key && !(key_info->flags & HA_NOSAME))
continue;
uint f_idx= 0;
KEY_PART_INFO *key_part= key_info->key_part;
#ifndef NO_EMBEDDED_ACCESS_CHECKS
if (need_column_checks)
{
uint j;
for (j=0 ; j < key_info->user_defined_key_parts ; j++,key_part++)
{
uint access= get_column_grant(thd, &tables->grant, db_name->str,
table_name->str,
key_part->field->field_name.str);
if (!access)
break;
}
if (j != key_info->user_defined_key_parts)
continue;
key_part= key_info->key_part;
}
#endif
for (uint j=0 ; j < key_info->user_defined_key_parts ; j++,key_part++)
{
f_idx++;
......@@ -7329,6 +7413,23 @@ static int get_schema_key_column_usage_record(THD *thd, TABLE_LIST *tables,
List_iterator_fast<LEX_CSTRING> it(f_key_info->foreign_fields),
it1(f_key_info->referenced_fields);
uint f_idx= 0;
#ifndef NO_EMBEDDED_ACCESS_CHECKS
if (need_column_checks)
{
while ((r_info= it1++))
{
uint access= get_column_grant(thd, &tables->grant, db_name->str,
table_name->str, r_info->str);
if (!access)
break;
}
if (!it1.at_end())
continue;
it1.rewind();
}
#endif
while ((f_info= it++))
{
r_info= it1++;
......@@ -8179,6 +8280,21 @@ get_referential_constraints_record(THD *thd, TABLE_LIST *tables,
show_table->file->info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK |
HA_STATUS_TIME);
#ifndef NO_EMBEDDED_ACCESS_CHECKS
/* need any non-SELECT privilege on the table or any of its columns */
const ulong need= TABLE_ACLS & ~SELECT_ACL;
if (!(thd->col_access & need))
{
/* we know that the table or at least some of the columns have
necessary privileges, but the caller didn't pass down the GRANT_INFO
object, so we have to rediscover everything again :( */
check_grant(thd, SELECT_ACL, tables, 0, 1, 1);
if (!(tables->grant.all_privilege() & need))
DBUG_RETURN(0);
}
#endif
show_table->file->get_foreign_key_list(thd, &f_key_list);
FOREIGN_KEY_INFO *f_key_info;
List_iterator_fast<FOREIGN_KEY_INFO> it(f_key_list);
......@@ -8193,8 +8309,28 @@ get_referential_constraints_record(THD *thd, TABLE_LIST *tables,
table->field[3]->store(STRING_WITH_LEN("def"), cs);
table->field[4]->store(f_key_info->referenced_db->str,
f_key_info->referenced_db->length, cs);
table->field[10]->store(f_key_info->referenced_table->str,
f_key_info->referenced_table->length, cs);
bool show_ref_table= true;
#ifndef NO_EMBEDDED_ACCESS_CHECKS
/* need any non-SELECT privilege on the table or any of its columns */
if (!(thd->col_access & need))
{
TABLE_LIST table_acl_check;
bzero((char*) &table_acl_check, sizeof(table_acl_check));
table_acl_check.db= *f_key_info->referenced_db;
table_acl_check.table_name= *f_key_info->referenced_table;
table_acl_check.grant.privilege= thd->col_access;
check_grant(thd, SELECT_ACL, &table_acl_check, 0, 1, 1);
if (!(table_acl_check.grant.all_privilege() & need))
show_ref_table= false;
}
#endif
if (show_ref_table)
{
table->field[10]->set_notnull();
table->field[10]->store(f_key_info->referenced_table->str,
f_key_info->referenced_table->length, cs);
}
if (f_key_info->referenced_key_name)
{
table->field[5]->store(f_key_info->referenced_key_name->str,
......@@ -9898,8 +10034,8 @@ ST_FIELD_INFO referential_constraints_fields_info[]=
{"UPDATE_RULE", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FULL_TABLE},
{"DELETE_RULE", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FULL_TABLE},
{"TABLE_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0, OPEN_FULL_TABLE},
{"REFERENCED_TABLE_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0, 0, 0,
OPEN_FULL_TABLE},
{"REFERENCED_TABLE_NAME", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0,
MY_I_S_MAYBE_NULL, 0, OPEN_FULL_TABLE},
{0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment