Bug#18790730 - CROSS-DATABASE FOREIGN KEY WITHOUT PERMISSIONS

CHECK.

Analysis:
----------
Issue here is, while creating or altering the InnoDB table,
if the foreign key defined on the table references a parent
table on which the user has no access privileges then the
table is created without reporting any error. 

Currently the privilege level REFERENCES_ACL is unused
and is not used for access evaluation while creating the
table with a foreign key constraint or adding the foreign
key constraint to a table. But when no privileges are granted
to user then also access evaluation on parent table is ignored.

Fix:
---------
For DMLs, irrelevant of the fact, support does not want any
changes to avoid permission checks on every operation.

So, as a fix, added a function "check_fk_parent_table_access" 
to check whether any of the SELECT_ACL, INSERT_ACL, UDPATE_ACL,
DELETE_ACL or REFERENCE_ACL privileges are granted for user
at table level. If none of them is granted then error is reported.
This function is called during the table creation and alter 
operation.
This commit is contained in:
Praveenkumar Hulakund 2014-09-10 10:50:17 +05:30
parent c8d49a8dab
commit 0b28d7e048
5 changed files with 136 additions and 2 deletions

View File

@ -868,6 +868,7 @@ struct handlerton
#define HTON_TEMPORARY_NOT_SUPPORTED (1 << 6) //Having temporary tables not supported
#define HTON_SUPPORT_LOG_TABLES (1 << 7) //Engine supports log tables
#define HTON_NO_PARTITION (1 << 8) //You can not partition these tables
#define HTON_SUPPORTS_FOREIGN_KEYS (1 << 9) //Foreign key constraint supported.
class Ha_trx_info;

View File

@ -5251,6 +5251,121 @@ bool check_global_access(THD *thd, ulong want_access)
#endif
}
/**
Checks foreign key's parent table access.
@param thd [in] Thread handler
@param create_info [in] Create information (like MAX_ROWS, ENGINE or
temporary table flag)
@param alter_info [in] Initial list of columns and indexes for the
table to be created
@retval
false ok.
@retval
true error or access denied. Error is sent to client in this case.
*/
bool check_fk_parent_table_access(THD *thd,
HA_CREATE_INFO *create_info,
Alter_info *alter_info)
{
Key *key;
List_iterator<Key> key_iterator(alter_info->key_list);
handlerton *db_type= create_info->db_type ? create_info->db_type :
ha_default_handlerton(thd);
// Return if engine does not support Foreign key Constraint.
if (!ha_check_storage_engine_flag(db_type, HTON_SUPPORTS_FOREIGN_KEYS))
return false;
while ((key= key_iterator++))
{
if (key->type == Key::FOREIGN_KEY)
{
TABLE_LIST parent_table;
bool is_qualified_table_name;
Foreign_key *fk_key= (Foreign_key *)key;
LEX_STRING db_name;
LEX_STRING table_name= { fk_key->ref_table->table.str,
fk_key->ref_table->table.length };
const ulong privileges= (SELECT_ACL | INSERT_ACL | UPDATE_ACL |
DELETE_ACL | REFERENCES_ACL);
// Check if tablename is valid or not.
DBUG_ASSERT(table_name.str != NULL);
if (check_table_name(table_name.str, table_name.length, false))
{
my_error(ER_WRONG_TABLE_NAME, MYF(0), table_name.str);
return true;
}
if (fk_key->ref_table->db.str)
{
is_qualified_table_name= true;
db_name.str= (char *) thd->memdup(fk_key->ref_table->db.str,
fk_key->ref_table->db.length+1);
db_name.length= fk_key->ref_table->db.length;
// Check if database name is valid or not.
if (fk_key->ref_table->db.str && check_db_name(&db_name))
{
my_error(ER_WRONG_DB_NAME, MYF(0), db_name.str);
return true;
}
}
else if (thd->lex->copy_db_to(&db_name.str, &db_name.length))
return true;
else
is_qualified_table_name= false;
// if lower_case_table_names is set then convert tablename to lower case.
if (lower_case_table_names)
{
table_name.str= (char *) thd->memdup(fk_key->ref_table->table.str,
fk_key->ref_table->table.length+1);
table_name.length= my_casedn_str(files_charset_info, table_name.str);
}
parent_table.init_one_table(db_name.str, db_name.length,
table_name.str, table_name.length,
table_name.str, TL_IGNORE);
/*
Check if user has any of the "privileges" at table level on
"parent_table".
Having privilege on any of the parent_table column is not
enough so checking whether user has any of the "privileges"
at table level only here.
*/
if (check_some_access(thd, privileges, &parent_table) ||
parent_table.grant.want_privilege)
{
if (is_qualified_table_name)
{
const size_t qualified_table_name_len= NAME_LEN + 1 + NAME_LEN + 1;
char *qualified_table_name= (char *) thd->alloc(qualified_table_name_len);
my_snprintf(qualified_table_name, qualified_table_name_len, "%s.%s",
db_name.str, table_name.str);
table_name.str= qualified_table_name;
}
my_error(ER_TABLEACCESS_DENIED_ERROR, MYF(0),
"REFERENCES",
thd->security_ctx->priv_user,
thd->security_ctx->host_or_ip,
table_name.str);
return true;
}
}
}
return false;
}
/****************************************************************************
Check stack size; Send error if there isn't enough stack to continue
****************************************************************************/
@ -7072,8 +7187,11 @@ bool create_table_precheck(THD *thd, TABLE_LIST *tables,
if (check_table_access(thd, SELECT_ACL, tables, FALSE, UINT_MAX, FALSE))
goto err;
}
error= FALSE;
if (check_fk_parent_table_access(thd, &lex->create_info, &lex->alter_info))
goto err;
error= FALSE;
err:
DBUG_RETURN(error);
}

View File

@ -45,6 +45,9 @@ bool delete_precheck(THD *thd, TABLE_LIST *tables);
bool insert_precheck(THD *thd, TABLE_LIST *tables);
bool create_table_precheck(THD *thd, TABLE_LIST *tables,
TABLE_LIST *create_table);
bool check_fk_parent_table_access(THD *thd,
HA_CREATE_INFO *create_info,
Alter_info *alter_info);
bool parse_sql(THD *thd,
Parser_state *parser_state,

View File

@ -6004,6 +6004,18 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
goto err;
}
/*
If foreign key is added then check permission to access parent table.
In function "check_fk_parent_table_access", create_info->db_type is used
to identify whether engine supports FK constraint or not. Since
create_info->db_type is set here, check to parent table access is delayed
till this point for the alter operation.
*/
if ((alter_info->flags & ALTER_FOREIGN_KEY) &&
check_fk_parent_table_access(thd, create_info, alter_info))
goto err;
/*
If this is an ALTER TABLE and no explicit row type specified reuse
the table's row type.

View File

@ -2251,7 +2251,7 @@ innobase_init(
innobase_hton->start_consistent_snapshot=innobase_start_trx_and_assign_read_view;
innobase_hton->flush_logs=innobase_flush_logs;
innobase_hton->show_status=innobase_show_status;
innobase_hton->flags=HTON_NO_FLAGS;
innobase_hton->flags=HTON_SUPPORTS_FOREIGN_KEYS;
innobase_hton->release_temporary_latches=innobase_release_temporary_latches;
innobase_hton->alter_table_flags = innobase_alter_table_flags;