diff options
-rw-r--r-- | mysql-test/suite/rpl/r/rpl_filter_revoke_missing_user.result | 39 | ||||
-rw-r--r-- | mysql-test/suite/rpl/t/rpl_filter_revoke_missing_user.test | 92 | ||||
-rw-r--r-- | sql/log_event.cc | 7 | ||||
-rw-r--r-- | sql/sql_acl.cc | 3 | ||||
-rw-r--r-- | sql/sql_class.h | 2 |
5 files changed, 143 insertions, 0 deletions
diff --git a/mysql-test/suite/rpl/r/rpl_filter_revoke_missing_user.result b/mysql-test/suite/rpl/r/rpl_filter_revoke_missing_user.result new file mode 100644 index 00000000000..efd73d88964 --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_filter_revoke_missing_user.result @@ -0,0 +1,39 @@ +include/master-slave.inc +[connection master] +# +# Set replica to ignore system tables +connection slave; +include/stop_slave.inc +SET @@GLOBAL.replicate_wild_ignore_table="mysql.%"; +include/start_slave.inc +# +# Trying to execute REVOKE ALL PRIVILEGES on a non-existent user and +# DROP USER on a list of users where not all users exist should error +# and be written into the binary log +connection master; +REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'nonexistentuser'@'%'; +ERROR HY000: Can't revoke all privileges for one or more of the requested users +CREATE USER 'testuser'@'localhost' IDENTIFIED by ''; +DROP USER 'testuser'@'localhost', 'nonexistentuser'@'%'; +ERROR HY000: Operation DROP USER failed for 'nonexistentuser'@'%' +# +# Ensure the events exist in the primary's binary log +FLUSH BINARY LOGS; +# MYSQL_BINLOG MYSQLD_DATADIR/binlog_file > MYSQL_TMP_DIR/mysqlbinlog_out.sql +# There should be three Query events: REVOKE, CREATE USER, and DROP USER +FOUND 3 /Query/ in mysqlbinlog_out.sql +FOUND 1 /REVOKE ALL PRIVILEGES/ in mysqlbinlog_out.sql +FOUND 1 /CREATE USER/ in mysqlbinlog_out.sql +FOUND 1 /DROP USER/ in mysqlbinlog_out.sql +# +# Ensure that the replica receives the event without error +connection slave; +Last_SQL_Error = +Last_SQL_Errno = 0 +# +# Clean up +connection slave; +include/stop_slave.inc +SET @@GLOBAL.replicate_wild_ignore_table=""; +include/start_slave.inc +include/rpl_end.inc diff --git a/mysql-test/suite/rpl/t/rpl_filter_revoke_missing_user.test b/mysql-test/suite/rpl/t/rpl_filter_revoke_missing_user.test new file mode 100644 index 00000000000..ca2c18d36e1 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_filter_revoke_missing_user.test @@ -0,0 +1,92 @@ +# +# Purpose: +# This test ensures that a binlogged Query_log_event which failed on the +# primary server does not break replication if it is ignored by Grant_tables +# on the replica. The bug reported by MDEV-28530 shows this with +# REVOKE ALL PRIVILEGES.. using a non-existent user. The primary will binlog +# the REVOKE command with an error code, and the replica will think the command +# executed with success because the replication filter will ignore the command +# while accessing the Grant_tables classes. When the replica performs an error +# check, it sees the difference between the error codes, and replication +# breaks. +# +# Methodology: +# Using a replica configured with replicate_wild_ignore_table="schema.%", +# on the primary, execute REVOKE ALL PRVILEGES using a non-existent user and +# DROP USER using a list of users where not all users exist, and ensure that +# the replica acknowledges and ignores the events without erroring. +# +# References: +# MDEV-28530: Revoking privileges from a non-existing user on a master breaks +# replication on the slave in the presence of replication filters +# + +source include/master-slave.inc; +source include/have_binlog_format_statement.inc; + +--echo # +--echo # Set replica to ignore system tables +connection slave; +let $old_filter= query_get_value(SHOW SLAVE STATUS, Replicate_Wild_Ignore_Table, 1); +source include/stop_slave.inc; +SET @@GLOBAL.replicate_wild_ignore_table="mysql.%"; +source include/start_slave.inc; + + +--echo # +--echo # Trying to execute REVOKE ALL PRIVILEGES on a non-existent user and +--echo # DROP USER on a list of users where not all users exist should error +--echo # and be written into the binary log +--connection master + +--error 1269 +REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'nonexistentuser'@'%'; + +CREATE USER 'testuser'@'localhost' IDENTIFIED by ''; +--error 1396 +DROP USER 'testuser'@'localhost', 'nonexistentuser'@'%'; +--save_master_pos + + +--echo # +--echo # Ensure the events exist in the primary's binary log +--let $MYSQLD_DATADIR= `select @@datadir` +--let $binlog_file=query_get_value(SHOW MASTER STATUS, File, 1) +FLUSH BINARY LOGS; +--echo # MYSQL_BINLOG MYSQLD_DATADIR/binlog_file > MYSQL_TMP_DIR/mysqlbinlog_out.sql +--exec $MYSQL_BINLOG $MYSQLD_DATADIR/$binlog_file > $MYSQL_TMP_DIR/mysqlbinlog_out.sql + +--echo # There should be three Query events: REVOKE, CREATE USER, and DROP USER +--let SEARCH_FILE= $MYSQL_TMP_DIR/mysqlbinlog_out.sql + +--let SEARCH_PATTERN= Query +--source include/search_pattern_in_file.inc + +--let SEARCH_PATTERN= REVOKE ALL PRIVILEGES +--source include/search_pattern_in_file.inc + +--let SEARCH_PATTERN= CREATE USER +--source include/search_pattern_in_file.inc + +--let SEARCH_PATTERN= DROP USER +--source include/search_pattern_in_file.inc + + +--echo # +--echo # Ensure that the replica receives the event without error +connection slave; +--sync_with_master +let $error= query_get_value(SHOW SLAVE STATUS, Last_SQL_Error, 1); +--echo Last_SQL_Error = $error +let $errno= query_get_value(SHOW SLAVE STATUS, Last_SQL_Errno, 1); +--echo Last_SQL_Errno = $errno + + +--echo # +--echo # Clean up +--connection slave +source include/stop_slave.inc; +--eval SET @@GLOBAL.replicate_wild_ignore_table="$old_filter" +source include/start_slave.inc; + +--source include/rpl_end.inc diff --git a/sql/log_event.cc b/sql/log_event.cc index 3639b7a5d00..a5a31d57ed2 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -5703,6 +5703,13 @@ int Query_log_event::do_apply_event(rpl_group_info *rgi, thd->update_server_status(); log_slow_statement(thd); thd->lex->restore_set_statement_var(); + + /* + When THD::slave_expected_error gets reset inside execution stack + that is the case of to be ignored event. In this case the expected + error must change to the reset value as well. + */ + expected_error= thd->slave_expected_error; } thd->variables.option_bits&= ~OPTION_MASTER_SQL_ERROR; diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 930c107cde2..eae180837ae 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -1300,7 +1300,10 @@ class Grant_tables Rpl_filter *rpl_filter= thd->system_thread_info.rpl_sql_info->rpl_filter; if (rpl_filter->is_on() && !rpl_filter->tables_ok(0, &first_table_in_list->tl)) + { + thd->slave_expected_error= 0; DBUG_RETURN(1); + } } DBUG_RETURN(0); } diff --git a/sql/sql_class.h b/sql/sql_class.h index 05b8f7eb443..d3e2b72b12d 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -3209,6 +3209,8 @@ public: /* In case of a slave, set to the error code the master got when executing the query. 0 if no error on the master. + The stored into variable master error code may get reset inside + execution stack when the event turns out to be ignored. */ int slave_expected_error; enum_sql_command last_sql_command; // Last sql_command exceuted in mysql_execute_command() |