From b35af311414184ebfec47df2050205b3f739183a Mon Sep 17 00:00:00 2001 From: Brandon Nesterenko Date: Thu, 8 Jan 2026 13:54:00 -0700 Subject: [PATCH 1/3] MDEV-38506: (Regression) Failed GRANT on a procedure breaks replication Test case to showcase MDEV-38506: A failed GRANT on a procedure will be replicated when sql_mode does not have NO_AUTO_CREATE_USER. This is because the function mysql_routine_grant() does not check if an error occured while performing the GRANT on a procedure before binlogging, it simply always binlogs. --- mysql-test/suite/rpl/t/rpl_grant.test | 43 +++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/mysql-test/suite/rpl/t/rpl_grant.test b/mysql-test/suite/rpl/t/rpl_grant.test index 0220645380ab7..cd3ed4ec2a684 100644 --- a/mysql-test/suite/rpl/t/rpl_grant.test +++ b/mysql-test/suite/rpl/t/rpl_grant.test @@ -34,4 +34,47 @@ sync_slave_with_master; SELECT user,host FROM mysql.user WHERE user like 'dummy%'; SELECT COUNT(*) FROM mysql.user WHERE user like 'dummy%'; +--echo # +--echo # MDEV-38506: Failed GRANT on a procedure breaks replication +--echo # + +--echo # Disable NO_AUTO_CREATE_USER so grant will auto-create users +connection master; +SET @old_sql_mode= @@GLOBAL.sql_mode; +SET GLOBAL sql_mode=''; + +--echo # Create new stored procedure for GRANT EXECUTE ON PROCEDURE +CREATE PROCEDURE test.sp() SELECT 1; + +--echo # Create a new user with limited privileges to grant sp execution +CREATE USER 'test_user'@'%' IDENTIFIED BY 'somepass'; +GRANT EXECUTE ON test.* TO 'test_user'@'%' WITH GRANT OPTION; +connect (con_test_user,localhost,test_user,somepass); +let $old_binlog_gtid= `SELECT @@global.gtid_binlog_pos`; # Tag the GTID before the failed GRANT +error ER_CANT_CREATE_USER_WITH_GRANT; +GRANT EXECUTE ON PROCEDURE test.sp TO 'nonexistentuser'@''; + +--echo # Ensuring the failed GRANT is not replicated.. +source include/rpl_sync.inc; +connection master; +let $new_binlog_gtid= `SELECT @@global.gtid_binlog_pos`; +if (`SELECT strcmp('$old_binlog_gtid', '$new_binlog_gtid') != 0`) +{ + --echo # binlog_gtid_pos before GRANT: $old_binlog_gtid + --echo # binlog_gtid_pos after GRANT: $new_binlog_gtid + --die Failed GRANT was binlogged +} +connection slave; +error ER_NONEXISTING_GRANT; +SHOW GRANTS for 'nonexistentuser'@''; +--echo # ..PASS + +connection master; +disconnect con_test_user; +SET GLOBAL sql_mode= @old_sql_mode; +DROP USER test_user@'%'; +DROP PROCEDURE test.sp; +--echo # End of MDEV-38506 test case + --source include/rpl_end.inc +--echo # End of rpl_grant.test From 6ea4789c220a50a0416db0737e772a0a2f165ebf Mon Sep 17 00:00:00 2001 From: Brandon Nesterenko Date: Thu, 8 Jan 2026 13:55:07 -0700 Subject: [PATCH 2/3] MDEV-38506: (Patch) Failed GRANT on a procedure breaks replication A failed GRANT on a procedure will be replicated when sql_mode does not have NO_AUTO_CREATE_USER. This is because the function mysql_routine_grant() does not check if an error occured while performing the GRANT on a procedure before binlogging, it simply always binlogs. This patch fixes this problem by checking if an error happened previously before binlogging, and if so, then skip binlogging. Note there is still a broader issue in this area leading to replication divergence. Reported in MDEV-29848, a partially-completed GRANT statment (where some earlier GRANTS succeed and a later fails) will not binlog. Note this affects all grant types, whereas the issue addressed in this patch is limited to GRANT EXECUTE ON PROCEDURE. This patch makes GRANT EXECUTE ON PROCEDURE binlogging behavior consistent with the other grant types. A separate follow-up patch will address the broader MDEV-29848 issue. Reviewed-by: TODO Signed-off-by: Brandon Nesterenko --- mysql-test/suite/rpl/r/rpl_grant.result | 29 +++++++++++++++++++++++++ sql/sql_acl.cc | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/mysql-test/suite/rpl/r/rpl_grant.result b/mysql-test/suite/rpl/r/rpl_grant.result index 274a8505fb8a4..9fc752cef9fe8 100644 --- a/mysql-test/suite/rpl/r/rpl_grant.result +++ b/mysql-test/suite/rpl/r/rpl_grant.result @@ -38,4 +38,33 @@ User Host SELECT COUNT(*) FROM mysql.user WHERE user like 'dummy%'; COUNT(*) 0 +# +# MDEV-38506: Failed GRANT on a procedure breaks replication +# +# Disable NO_AUTO_CREATE_USER so grant will auto-create users +connection master; +SET @old_sql_mode= @@GLOBAL.sql_mode; +SET GLOBAL sql_mode=''; +# Create new stored procedure sp to be granted to new role test_role +CREATE PROCEDURE test.sp() SELECT 1; +# Create a new user with limited privileges to grant sp execution to test_role +CREATE USER 'test_user'@'%' IDENTIFIED BY 'somepass'; +GRANT EXECUTE ON test.* TO 'test_user'@'%' WITH GRANT OPTION; +connect con_test_user,localhost,test_user,somepass; +GRANT EXECUTE ON PROCEDURE test.sp TO 'nonexistentuser'@''; +ERROR 42000: You are not allowed to create a user with GRANT +# Ensuring the failed GRANT is not replicated.. +include/rpl_sync.inc +connection master; +connection slave; +SHOW GRANTS for 'nonexistentuser'@''; +ERROR 42000: There is no such grant defined for user 'nonexistentuser' on host '%' +# ..PASS +connection master; +disconnect con_test_user; +SET GLOBAL sql_mode= @old_sql_mode; +DROP USER test_user@'%'; +DROP PROCEDURE test.sp; +# End of MDEV-38506 test case include/rpl_end.inc +# End of rpl_grant.test diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 422ea6b008b36..4fdc6656b9e6d 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -7519,7 +7519,7 @@ bool mysql_routine_grant(THD *thd, TABLE_LIST *table_list, thd->mem_root= old_root; mysql_mutex_unlock(&acl_cache->lock); - if (write_to_binlog) + if (write_to_binlog && !result) { if (write_bin_log(thd, FALSE, thd->query(), thd->query_length())) result= TRUE; From 70db41df2b265772c05fed88fa1a551cf458ebc5 Mon Sep 17 00:00:00 2001 From: Brandon Nesterenko Date: Thu, 8 Jan 2026 15:26:49 -0700 Subject: [PATCH 3/3] MDEV-38506: (Post-fix) rpl.rpl_do_grant rpl_do_grant.test took advantage of MDEV-38506 so a partially-failing REVOKE EXECUTE ON PROCEDURE would still replicate. This commit disables the problematic test case with a TODO note to re-enable it once MDEV-29848 is fixed. --- mysql-test/suite/rpl/r/rpl_do_grant.result | 68 +---------- mysql-test/suite/rpl/t/rpl_do_grant.test | 126 +++++++++++---------- 2 files changed, 69 insertions(+), 125 deletions(-) diff --git a/mysql-test/suite/rpl/r/rpl_do_grant.result b/mysql-test/suite/rpl/r/rpl_do_grant.result index 0d2908809b9eb..0e3b578addad6 100644 --- a/mysql-test/suite/rpl/r/rpl_do_grant.result +++ b/mysql-test/suite/rpl/r/rpl_do_grant.result @@ -211,71 +211,9 @@ connection master; DROP TABLE t1; DROP PROCEDURE p1; connection slave; -### ii) Test case in which REVOKE partially succeeds -connection master; -include/rpl_reset.inc -connection master; -CREATE TABLE t1(c1 INT); -CREATE PROCEDURE p1() SELECT * FROM t1 | -CREATE USER 'user49119'@'localhost'; -GRANT EXECUTE ON PROCEDURE p1 TO 'user49119'@'localhost'; -############################################################## -### Showing grants for both users: root and user49119 (master) -SHOW GRANTS FOR 'user49119'@'localhost'; -Grants for user49119@localhost -GRANT USAGE ON *.* TO `user49119`@`localhost` -GRANT EXECUTE ON PROCEDURE `test`.`p1` TO `user49119`@`localhost` -SHOW GRANTS FOR CURRENT_USER; -Grants for root@localhost -GRANT ALL PRIVILEGES ON *.* TO `root`@`localhost` WITH GRANT OPTION -GRANT PROXY ON ''@'%' TO 'root'@'localhost' WITH GRANT OPTION -############################################################## -connection slave; -############################################################## -### Showing grants for both users: root and user49119 (master) -SHOW GRANTS FOR 'user49119'@'localhost'; -Grants for user49119@localhost -GRANT USAGE ON *.* TO `user49119`@`localhost` -GRANT EXECUTE ON PROCEDURE `test`.`p1` TO `user49119`@`localhost` -SHOW GRANTS FOR CURRENT_USER; -Grants for root@localhost -GRANT ALL PRIVILEGES ON *.* TO `root`@`localhost` WITH GRANT OPTION -GRANT PROXY ON ''@'%' TO 'root'@'localhost' WITH GRANT OPTION -############################################################## -connection master; -## This statement will make the revoke fail because root has no -## execute grant. However, it will still revoke the grant for -## user49119. -REVOKE EXECUTE ON PROCEDURE p1 FROM 'user49119'@'localhost', 'root'@'localhost'; -ERROR 42000: There is no such grant defined for user 'root' on host 'localhost' on routine 'p1' -############################################################## -### Showing grants for both users: root and user49119 (master) -### after revoke statement failure -SHOW GRANTS FOR 'user49119'@'localhost'; -Grants for user49119@localhost -GRANT USAGE ON *.* TO `user49119`@`localhost` -SHOW GRANTS FOR CURRENT_USER; -Grants for root@localhost -GRANT ALL PRIVILEGES ON *.* TO `root`@`localhost` WITH GRANT OPTION -GRANT PROXY ON ''@'%' TO 'root'@'localhost' WITH GRANT OPTION -############################################################## -connection slave; -############################################################# -### Showing grants for both users: root and user49119 (slave) -### after revoke statement failure (should match -SHOW GRANTS FOR 'user49119'@'localhost'; -Grants for user49119@localhost -GRANT USAGE ON *.* TO `user49119`@`localhost` -SHOW GRANTS FOR CURRENT_USER; -Grants for root@localhost -GRANT ALL PRIVILEGES ON *.* TO `root`@`localhost` WITH GRANT OPTION -GRANT PROXY ON ''@'%' TO 'root'@'localhost' WITH GRANT OPTION -############################################################## -connection master; -DROP TABLE t1; -DROP PROCEDURE p1; -DROP USER 'user49119'@'localhost'; -connection slave; +# +# TODO: Re-enable test-case after fixing MDEV-29848 +# include/rpl_reset.inc connection master; grant all on *.* to foo@"1.2.3.4"; diff --git a/mysql-test/suite/rpl/t/rpl_do_grant.test b/mysql-test/suite/rpl/t/rpl_do_grant.test index 2b757f57692d9..323ef9be9e13b 100644 --- a/mysql-test/suite/rpl/t/rpl_do_grant.test +++ b/mysql-test/suite/rpl/t/rpl_do_grant.test @@ -212,8 +212,11 @@ USE test; # when binlogging is active, the master will not hit an # assertion. # -# ii) a test case that partially succeeds on the master will also -# partially succeed on the slave. +# ii) (DISABLED) To pass, this test case took advantage of a vulnerability, +# MDEV-38506. Please uncomment this test case when MDEV-29848 is fixed. +# +# test case that partially succeeds on the master will also +# partially succeed on the slave. # # - The revoke statement that partially succeeds tries to revoke # an EXECUTE grant for two users, and only one of the user has @@ -242,64 +245,67 @@ DROP PROCEDURE p1; -- sync_slave_with_master --- echo ### ii) Test case in which REVOKE partially succeeds - --- connection master --- source include/rpl_reset.inc --- connection master - -CREATE TABLE t1(c1 INT); -DELIMITER |; -CREATE PROCEDURE p1() SELECT * FROM t1 | -DELIMITER ;| - -CREATE USER 'user49119'@'localhost'; -GRANT EXECUTE ON PROCEDURE p1 TO 'user49119'@'localhost'; - --- echo ############################################################## --- echo ### Showing grants for both users: root and user49119 (master) -SHOW GRANTS FOR 'user49119'@'localhost'; -SHOW GRANTS FOR CURRENT_USER; --- echo ############################################################## - --- sync_slave_with_master - --- echo ############################################################## --- echo ### Showing grants for both users: root and user49119 (master) -SHOW GRANTS FOR 'user49119'@'localhost'; -SHOW GRANTS FOR CURRENT_USER; --- echo ############################################################## - --- connection master - --- echo ## This statement will make the revoke fail because root has no --- echo ## execute grant. However, it will still revoke the grant for --- echo ## user49119. --- error ER_NONEXISTING_PROC_GRANT -REVOKE EXECUTE ON PROCEDURE p1 FROM 'user49119'@'localhost', 'root'@'localhost'; - --- echo ############################################################## --- echo ### Showing grants for both users: root and user49119 (master) --- echo ### after revoke statement failure -SHOW GRANTS FOR 'user49119'@'localhost'; -SHOW GRANTS FOR CURRENT_USER; --- echo ############################################################## - --- sync_slave_with_master - --- echo ############################################################# --- echo ### Showing grants for both users: root and user49119 (slave) --- echo ### after revoke statement failure (should match -SHOW GRANTS FOR 'user49119'@'localhost'; -SHOW GRANTS FOR CURRENT_USER; --- echo ############################################################## - --- connection master -DROP TABLE t1; -DROP PROCEDURE p1; -DROP USER 'user49119'@'localhost'; - --- sync_slave_with_master +--echo # +--echo # TODO: Re-enable test-case after fixing MDEV-29848 +--echo # +#-- echo ### ii) Test case in which REVOKE partially succeeds +# +#-- connection master +#-- source include/rpl_reset.inc +#-- connection master +# +#CREATE TABLE t1(c1 INT); +#DELIMITER |; +#CREATE PROCEDURE p1() SELECT * FROM t1 | +#DELIMITER ;| +# +#CREATE USER 'user49119'@'localhost'; +#GRANT EXECUTE ON PROCEDURE p1 TO 'user49119'@'localhost'; +# +#-- echo ############################################################## +#-- echo ### Showing grants for both users: root and user49119 (master) +#SHOW GRANTS FOR 'user49119'@'localhost'; +#SHOW GRANTS FOR CURRENT_USER; +#-- echo ############################################################## +# +#-- sync_slave_with_master +# +#-- echo ############################################################## +#-- echo ### Showing grants for both users: root and user49119 (master) +#SHOW GRANTS FOR 'user49119'@'localhost'; +#SHOW GRANTS FOR CURRENT_USER; +#-- echo ############################################################## +# +#-- connection master +# +#-- echo ## This statement will make the revoke fail because root has no +#-- echo ## execute grant. However, it will still revoke the grant for +#-- echo ## user49119. +#-- error ER_NONEXISTING_PROC_GRANT +#REVOKE EXECUTE ON PROCEDURE p1 FROM 'user49119'@'localhost', 'root'@'localhost'; +# +#-- echo ############################################################## +#-- echo ### Showing grants for both users: root and user49119 (master) +#-- echo ### after revoke statement failure +#SHOW GRANTS FOR 'user49119'@'localhost'; +#SHOW GRANTS FOR CURRENT_USER; +#-- echo ############################################################## +# +#-- sync_slave_with_master +# +#-- echo ############################################################# +#-- echo ### Showing grants for both users: root and user49119 (slave) +#-- echo ### after revoke statement failure (should match) +#SHOW GRANTS FOR 'user49119'@'localhost'; +#SHOW GRANTS FOR CURRENT_USER; +#-- echo ############################################################## +# +#-- connection master +#DROP TABLE t1; +#DROP PROCEDURE p1; +#DROP USER 'user49119'@'localhost'; +# +#-- sync_slave_with_master # # Bug #51987 revoke privileges logs wrong error code