Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Port Control Cross-Table Access to 8.0
Summary:
Port our v5.6 sysvar allow_noncurrent_db_rw, which detects when a query
accesses data not in the current database.  Depending on its setting,
when a query tries to read or write to another database, it will:
ON: Allow it.
LOG: Allow it, but log that it occurred.
LOG_WARN: Allow it, log it occurred, and send an error to client.
OFF: Block it, and send an error to the client.

Reference patch: facebook@b5f54b4

Differential Revision: D6071054 (facebook@d04eb6a)

fbshipit-source-id: 690ab034efe
  • Loading branch information
Aliaksei Sandryhaila authored and inikep committed Jul 14, 2021
1 parent 9aa251a commit a149a6d
Show file tree
Hide file tree
Showing 8 changed files with 338 additions and 1 deletion.
4 changes: 4 additions & 0 deletions mysql-test/r/mysqld--help-notwin.result
Expand Up @@ -43,6 +43,9 @@ The following options may be given as the first argument:
--admin-tls-version=name
TLS version for --admin-port, permitted values are TLSv1,
TLSv1.1, TLSv1.2, TLSv1.3
--allow-noncurrent-db-rw=name
Switch to allow/deny reads and writes to a table not in
the current database.
--allow-suspicious-udfs
Allows use of UDFs consisting of only one symbol xxx()
without corresponding xxx_init() or xxx_deinit(). That
Expand Down Expand Up @@ -1474,6 +1477,7 @@ admin-ssl-crl (No default value)
admin-ssl-crlpath (No default value)
admin-ssl-key (No default value)
admin-tls-ciphersuites (No default value)
allow-noncurrent-db-rw ON
allow-suspicious-udfs FALSE
auto-increment-increment 1
auto-increment-offset 1
Expand Down
122 changes: 122 additions & 0 deletions mysql-test/suite/sys_vars/r/allow_noncurrent_db_rw_basic.result
@@ -0,0 +1,122 @@
set @start_allow_noncurrent_db_rw= @@global.allow_noncurrent_db_rw;
set global allow_noncurrent_db_rw = ALLOWED;
ERROR 42000: Variable 'allow_noncurrent_db_rw' can't be set to the value of 'ALLOWED'
Expected error ER_WRONG_VALUE_FOR_VAR
show databases;
Database
information_schema
mtr
mysql
performance_schema
sys
test
set global allow_noncurrent_db_rw = OFF;
connect (non_current_db,localhost,root,,*NO-ONE*);
select database();
database()
NULL
create table test.allow_noncurrent_db_rw_create (id int);
ERROR HY000: The MySQL server is running with the --allow_noncurrent_db_rw=OFF option so it cannot execute this statement
set global allow_noncurrent_db_rw = LOG;
connect (con1,localhost,root,,test);
connection default;
drop database if exists mysqltest_db1;
drop database if exists mysqltest_db2;
create database mysqltest_db1;
create database mysqltest_db2;
connect (con_test_db1,localhost,root,,mysqltest_db1);
create table t1 (id int);
create table t2 (id int);
insert t1 values(1);
insert t2 values(1);
connect (con_test_db2,localhost,root,,mysqltest_db2);
create table t1 (id int);
create table t2 (id int);
insert t1 values(1);
insert t2 values(1);
set allow_noncurrent_db_rw = LOG_WARN;
select * from mysqltest_db1.t1;
id
1
Warnings:
Warning 1290 The MySQL server is running with the --allow_noncurrent_db_rw=LOG_WARN option so it cannot execute this statement
set allow_noncurrent_db_rw = OFF;
select * from mysqltest_db1.t1;
ERROR HY000: The MySQL server is running with the --allow_noncurrent_db_rw=OFF option so it cannot execute this statement
insert mysqltest_db1.t1 values(2);
ERROR HY000: The MySQL server is running with the --allow_noncurrent_db_rw=OFF option so it cannot execute this statement
update mysqltest_db1.t1 set id=11 where id =1;
ERROR HY000: The MySQL server is running with the --allow_noncurrent_db_rw=OFF option so it cannot execute this statement
insert t1 values(100);
insert into mysqltest_db1.t1 SELECT * from t1 where id=100;
ERROR HY000: The MySQL server is running with the --allow_noncurrent_db_rw=OFF option so it cannot execute this statement
create table mysqltest_db1.t_create (id int) select * from t1;
ERROR HY000: The MySQL server is running with the --allow_noncurrent_db_rw=OFF option so it cannot execute this statement
delete from mysqltest_db1.t1 where id=1;
ERROR HY000: The MySQL server is running with the --allow_noncurrent_db_rw=OFF option so it cannot execute this statement
replace into mysqltest_db1.t1 SELECT * from t1 where id=100;
ERROR HY000: The MySQL server is running with the --allow_noncurrent_db_rw=OFF option so it cannot execute this statement
alter table mysqltest_db1.t1 add column (id2 int);
ERROR HY000: The MySQL server is running with the --allow_noncurrent_db_rw=OFF option so it cannot execute this statement
SELECT 1, 2, 3, (SELECT * FROM mysqltest_db1.t1 WHERE id = T.id) FROM mysqltest_db2.t2 T;
1 2 3 (SELECT * FROM mysqltest_db1.t1 WHERE id = T.id)
1 2 3 1
SELECT test1.*, test2.* FROM mysqltest_db1.t1 test1, mysqltest_db1.t2 test2 WHERE test1.id = test2.id;
id id
1 1
set allow_noncurrent_db_rw = LOG_WARN;
SELECT 1, 2, 3, (SELECT * FROM mysqltest_db1.t1 WHERE id = T.id) FROM mysqltest_db2.t2 T;
1 2 3 (SELECT * FROM mysqltest_db1.t1 WHERE id = T.id)
1 2 3 1
Warnings:
Warning 1290 The MySQL server is running with the --allow_noncurrent_db_rw=LOG_WARN option so it cannot execute this statement
SELECT test1.*, test2.* FROM mysqltest_db1.t1 test1, mysqltest_db2.t2 test2 WHERE test1.id = test2.id;
id id
1 1
Warnings:
Warning 1290 The MySQL server is running with the --allow_noncurrent_db_rw=LOG_WARN option so it cannot execute this statement
set allow_noncurrent_db_rw = OFF;
SELECT 1, 2, 3, (SELECT * FROM mysqltest_db1.t1 WHERE id = T.id) FROM mysqltest_db2.t2 T;
ERROR HY000: The MySQL server is running with the --allow_noncurrent_db_rw=OFF option so it cannot execute this statement
SELECT test1.*, test2.* FROM mysqltest_db1.t1 test1, mysqltest_db2.t2 test2 WHERE test1.id = test2.id;
ERROR HY000: The MySQL server is running with the --allow_noncurrent_db_rw=OFF option so it cannot execute this statement
desc performance_schema.global_variables;
Field Type Null Key Default Extra
VARIABLE_NAME varchar(64) NO PRI NULL
VARIABLE_VALUE varchar(1024) YES NULL
desc information_schema.character_sets;
Field Type Null Key Default Extra
CHARACTER_SET_NAME varchar(64) NO NULL
DEFAULT_COLLATE_NAME varchar(64) NO NULL
DESCRIPTION varchar(2048) NO NULL
MAXLEN int unsigned NO NULL
desc sys.schema_table_statistics;
Field Type Null Key Default Extra
table_schema varchar(64) YES NULL
table_name varchar(64) YES NULL
total_latency varchar(11) YES NULL
rows_fetched bigint unsigned NO NULL
fetch_latency varchar(11) YES NULL
rows_inserted bigint unsigned NO NULL
insert_latency varchar(11) YES NULL
rows_updated bigint unsigned NO NULL
update_latency varchar(11) YES NULL
rows_deleted bigint unsigned NO NULL
delete_latency varchar(11) YES NULL
io_read_requests decimal(42,0) YES NULL
io_read varchar(11) YES NULL
io_read_latency varchar(11) YES NULL
io_write_requests decimal(42,0) YES NULL
io_write varchar(11) YES NULL
io_write_latency varchar(11) YES NULL
io_misc_requests decimal(42,0) YES NULL
io_misc_latency varchar(11) YES NULL
set allow_noncurrent_db_rw = ON;
rename table mysqltest_db1.t1 to mysqltest_db2.t2_1;
rename table mysqltest_db2.t2_1 to mysqltest_db1.t1;
select * from mysqltest_db1.t1;
id
1
drop database mysqltest_db1;
drop database mysqltest_db2;
set global allow_noncurrent_db_rw= @start_allow_noncurrent_db_rw;
133 changes: 133 additions & 0 deletions mysql-test/suite/sys_vars/t/allow_noncurrent_db_rw_basic.test
@@ -0,0 +1,133 @@
############## mysql-test\t\allow_noncurrent_db_rw_basic.test ##################
# #
# Variable Name: allow_noncurrent_db_rw #
# Scope: GLOBAL & SESSION #
# Access Type: Dynamic #
# Data Type: Numeric #
# Default Value: 0 #
# Range: 0,1,2 & 3 #
# #
# #
# Creation Date: 2012-11-06 #
# Author: Mayank Agarwal #
# #
# Description: Allow/deny read/write to table not in the current database #
# #
# Reference: http://dev.mysql.com/doc/refman/5.1/en/ #
# server-system-variables.html #
# #
################################################################################

--source include/load_sysvars.inc

################################################################################
# START OF allow_noncurrent_db_rw TEST #
################################################################################

set @start_allow_noncurrent_db_rw= @@global.allow_noncurrent_db_rw;

--Error ER_WRONG_VALUE_FOR_VAR
set global allow_noncurrent_db_rw = ALLOWED;
--ECHO Expected error ER_WRONG_VALUE_FOR_VAR

show databases;

set global allow_noncurrent_db_rw = OFF;
--echo connect (non_current_db,localhost,root,,*NO-ONE*);
connect (non_current_db,localhost,root,,*NO-ONE*);
select database();
--error ER_OPTION_PREVENTS_STATEMENT
create table test.allow_noncurrent_db_rw_create (id int);

set global allow_noncurrent_db_rw = LOG;

--echo connect (con1,localhost,root,,test);
connect (con1,localhost,root,,test);

--echo connection default;
connection default;

--disable_warnings
drop database if exists mysqltest_db1;
drop database if exists mysqltest_db2;
create database mysqltest_db1;
create database mysqltest_db2;
--enable_warnings

--echo connect (con_test_db1,localhost,root,,mysqltest_db1);
connect (con_test_db1,localhost,root,,mysqltest_db1);
create table t1 (id int);
create table t2 (id int);
insert t1 values(1);
insert t2 values(1);

--echo connect (con_test_db2,localhost,root,,mysqltest_db2);
connect (con_test_db2,localhost,root,,mysqltest_db2);
create table t1 (id int);
create table t2 (id int);
insert t1 values(1);
insert t2 values(1);

connection con_test_db2;

set allow_noncurrent_db_rw = LOG_WARN;
select * from mysqltest_db1.t1;

set allow_noncurrent_db_rw = OFF;
--error ER_OPTION_PREVENTS_STATEMENT
select * from mysqltest_db1.t1;

--error ER_OPTION_PREVENTS_STATEMENT
insert mysqltest_db1.t1 values(2);

--error ER_OPTION_PREVENTS_STATEMENT
update mysqltest_db1.t1 set id=11 where id =1;

insert t1 values(100);

--error ER_OPTION_PREVENTS_STATEMENT
insert into mysqltest_db1.t1 SELECT * from t1 where id=100;

--error ER_OPTION_PREVENTS_STATEMENT
create table mysqltest_db1.t_create (id int) select * from t1;

--error ER_OPTION_PREVENTS_STATEMENT
delete from mysqltest_db1.t1 where id=1;

--error ER_OPTION_PREVENTS_STATEMENT
replace into mysqltest_db1.t1 SELECT * from t1 where id=100;

--error ER_OPTION_PREVENTS_STATEMENT
alter table mysqltest_db1.t1 add column (id2 int);

connection con_test_db1;
SELECT 1, 2, 3, (SELECT * FROM mysqltest_db1.t1 WHERE id = T.id) FROM mysqltest_db2.t2 T;
SELECT test1.*, test2.* FROM mysqltest_db1.t1 test1, mysqltest_db1.t2 test2 WHERE test1.id = test2.id;

set allow_noncurrent_db_rw = LOG_WARN;
SELECT 1, 2, 3, (SELECT * FROM mysqltest_db1.t1 WHERE id = T.id) FROM mysqltest_db2.t2 T;
SELECT test1.*, test2.* FROM mysqltest_db1.t1 test1, mysqltest_db2.t2 test2 WHERE test1.id = test2.id;

set allow_noncurrent_db_rw = OFF;
--error ER_OPTION_PREVENTS_STATEMENT
SELECT 1, 2, 3, (SELECT * FROM mysqltest_db1.t1 WHERE id = T.id) FROM mysqltest_db2.t2 T;
--error ER_OPTION_PREVENTS_STATEMENT
SELECT test1.*, test2.* FROM mysqltest_db1.t1 test1, mysqltest_db2.t2 test2 WHERE test1.id = test2.id;
# these schemas are filtered and should pass when the variable is set to OFF
desc performance_schema.global_variables;
desc information_schema.character_sets;
desc sys.schema_table_statistics;

set allow_noncurrent_db_rw = ON;
rename table mysqltest_db1.t1 to mysqltest_db2.t2_1;
rename table mysqltest_db2.t2_1 to mysqltest_db1.t1;
select * from mysqltest_db1.t1;

connection default;
drop database mysqltest_db1;
drop database mysqltest_db2;
set global allow_noncurrent_db_rw= @start_allow_noncurrent_db_rw;

################################################################################
# END OF allow_noncurrent_db_rw TEST #
################################################################################
5 changes: 4 additions & 1 deletion sql/psi_memory_key.cc
Expand Up @@ -145,6 +145,7 @@ PSI_memory_key key_memory_user_var_entry;
PSI_memory_key key_memory_user_var_entry_value;
PSI_memory_key key_memory_sp_cache;
PSI_memory_key key_memory_write_set_extraction;
PSI_memory_key key_memory_custom_log_message;

#ifdef HAVE_PSI_INTERFACE

Expand Down Expand Up @@ -354,7 +355,9 @@ static PSI_memory_info all_server_memory[] = {
{&key_memory_log_sink_pfs, "log_sink_pfs", PSI_FLAG_ONLY_GLOBAL_STAT, 0,
PSI_DOCUMENT_ME},
{&key_memory_histograms, "histograms", 0, 0, PSI_DOCUMENT_ME},
{&key_memory_hash_join, "hash_join", 0, 0, PSI_DOCUMENT_ME}};
{&key_memory_hash_join, "hash_join", 0, 0, PSI_DOCUMENT_ME},
{&key_memory_custom_log_message, "custom_log_message", 0, 0,
PSI_DOCUMENT_ME}};

void register_server_memory_keys() {
const char *category = "sql";
Expand Down
1 change: 1 addition & 0 deletions sql/psi_memory_key.h
Expand Up @@ -169,5 +169,6 @@ extern PSI_memory_key key_memory_user_var_entry;
extern PSI_memory_key key_memory_user_var_entry_value;
extern PSI_memory_key key_memory_sp_cache;
extern PSI_memory_key key_memory_write_set_extraction;
extern PSI_memory_key key_memory_custom_log_message;

#endif // PSI_MEMORY_KEY_INCLUDED
63 changes: 63 additions & 0 deletions sql/sql_parse.cc
Expand Up @@ -1386,6 +1386,36 @@ static bool deny_updates_if_read_only_option(THD *thd, TABLE_LIST *all_tables) {
return false;
}

/*
This is the function to perform the check for variable
"allow_noncurrent_db_rw". It will assume the command is
a query. And check whether this query command invovles
any table that is not in current datbase.
@returns
0 Nothing to do.
1 Log the query.
2 Log the query with warning.
3 Disallow the query.
*/

static int process_noncurrent_db_rw(THD *thd, TABLE_LIST *all_tables) {
DBUG_ENTER("process_noncurrent_db_rw");
if (!thd->variables.allow_noncurrent_db_rw)
DBUG_RETURN(0); /* Allow cross db read and write. */
for (TABLE_LIST *table = all_tables; table; table = table->next_global) {
bool skip_table =
table->is_view_or_derived() || table->is_view() ||
table->schema_table || !strcmp(table->db, "mysql") ||
!strcmp(table->db, "performance_schema") || !strcmp(table->db, "") ||
!strcmp(table->db, "information_schema") || !strcmp(table->db, "sys");
if (skip_table) continue;
if ((!thd->db().str && table->db) || strcmp(thd->db().str, table->db))
DBUG_RETURN((int)thd->variables.allow_noncurrent_db_rw);
}
DBUG_RETURN(0);
}

/**
Check if a statement should be restarted in another storage engine,
and restart the statement if needed.
Expand Down Expand Up @@ -2946,6 +2976,39 @@ int mysql_execute_command(THD *thd, bool first_level) {
err_readonly(thd);
return -1;
}

ret = process_noncurrent_db_rw(thd, all_tables);
if (ret > 0) /* For all options other than ON */
{
/* Log the query */
const char *crosss_db_log_prefix = "CROSS_SHARD_QUERY: ";
size_t prefix_len = strlen(crosss_db_log_prefix);
size_t log_len = prefix_len + thd->query().length;
char *cross_db_query_log = (char *)my_malloc(
key_memory_custom_log_message, log_len + 1, MYF(MY_WME));
memcpy(cross_db_query_log, crosss_db_log_prefix, prefix_len);
memcpy(cross_db_query_log + prefix_len, thd->query().str,
thd->query().length);
cross_db_query_log[log_len] = 0;
query_logger.slow_log_write(thd, cross_db_query_log, log_len,
&(thd->status_var));
my_free(cross_db_query_log);
}
if (ret == 2) /* For LOG_WARN */
{
/* Warning message to user */
push_warning_printf(thd, Sql_condition::SL_WARNING,
ER_OPTION_PREVENTS_STATEMENT,
ER_THD(thd, ER_OPTION_PREVENTS_STATEMENT),
"--allow_noncurrent_db_rw=LOG_WARN", "");
}
if (ret == 3) /* For OFF */
{
/* Error message to user */
my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0),
"--allow_noncurrent_db_rw=OFF", "");
return -1;
}
} /* endif unlikely slave */

thd->status_var.com_stat[lex->sql_command]++;
Expand Down
9 changes: 9 additions & 0 deletions sql/sys_vars.cc
Expand Up @@ -3687,6 +3687,15 @@ static Sys_var_enum Sys_thread_handling(
READ_ONLY GLOBAL_VAR(Connection_handler_manager::thread_handling),
CMD_LINE(REQUIRED_ARG), thread_handling_names, DEFAULT(0));

static const char *allow_noncurrent_db_rw_levels[] = {"ON", "LOG", "LOG_WARN",
"OFF", 0};
static Sys_var_enum Sys_allow_noncurrent_db_rw(
"allow_noncurrent_db_rw",
"Switch to allow/deny reads and writes to a table not in the "
"current database.",
SESSION_VAR(allow_noncurrent_db_rw), CMD_LINE(REQUIRED_ARG),
allow_noncurrent_db_rw_levels, DEFAULT(0));

static Sys_var_charptr Sys_secure_file_priv(
"secure_file_priv",
"Limit LOAD DATA, SELECT ... OUTFILE, and LOAD_FILE() to files "
Expand Down

0 comments on commit a149a6d

Please sign in to comment.