MDEV-37214: CREATE OR REPLACE UESR must not drop user if auth plugin is not loaded#5191
MDEV-37214: CREATE OR REPLACE UESR must not drop user if auth plugin is not loaded#5191nadaelsayed11 wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Code Review
This pull request addresses a bug (MDEV-37214) where executing CREATE OR REPLACE USER with an invalid authentication plugin could drop an existing user without writing to the binary log, leading to master/replica divergence. The changes introduce a pre-validation check for authentication plugins in sql/sql_acl.cc and add corresponding test cases. The review feedback identifies a critical issue: if a single statement contains multiple users (some valid and some invalid), the valid users will still be dropped before the statement fails, resulting in partial execution and replica divergence. The reviewer recommends pre-validating all users in a separate loop before performing any destructive operations and adding a test case to cover this multi-user scenario.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| /* | ||
| MDEV-37214: pre-validate auth plugins before the destructive DROP so | ||
| that a missing plugin does not silently drop the user without a binlog | ||
| event, causing master/replica divergence. | ||
| */ | ||
| bool plugin_error= false; | ||
| for (USER_AUTH *auth= user_name->auth; auth; auth= auth->next) | ||
| { | ||
| if (auth->plugin.length && | ||
| !plugin_is_ready(&auth->plugin, MYSQL_AUTHENTICATION_PLUGIN)) | ||
| { | ||
| my_error(ER_PLUGIN_IS_NOT_LOADED, MYF(0), auth->plugin.str); | ||
| append_user(thd, &wrong_users, user_name); | ||
| result= true; | ||
| plugin_error= true; | ||
| break; | ||
| } | ||
| } | ||
| if (plugin_error) | ||
| continue; |
There was a problem hiding this comment.
There is a potential master/replica divergence issue when CREATE OR REPLACE USER is called with multiple users, where some have valid plugins and others have invalid plugins.
The Issue
The current validation is performed inside the main loop that iterates over each user in the list. If a query like the following is executed:
CREATE OR REPLACE USER u1 IDENTIFIED VIA valid_plugin, u2 IDENTIFIED VIA invalid_plugin;u1is processed first. Since its plugin is valid, it is dropped and recreated on the master.u2is processed next. Since its plugin is invalid, the loop setsresult = trueand continues.- The function eventually returns
true(failure), so the statement fails and is not written to the binary log.
As a result, u1 is modified on the master but not on the replica, leading to master/replica divergence.
Recommended Solution
To prevent partial execution and master/replica divergence, we should pre-validate the authentication plugins for all users in the list in a separate loop at the very beginning of mysql_create_user, before any destructive operations (like dropping users) are performed. If any user in the list has an invalid plugin, we should fail the entire statement immediately.
| # MDEV-37214: CREATE OR REPLACE USER with invalid plugin must not drop | ||
| # the existing user and must not leave master/replica in diverged state. | ||
| CREATE USER u3@localhost; | ||
| --error ER_PLUGIN_IS_NOT_LOADED | ||
| CREATE OR REPLACE USER u3@localhost IDENTIFIED VIA no_such_plugin; | ||
| SELECT user FROM mysql.user WHERE user='u3'; | ||
| DROP USER u3@localhost; |
There was a problem hiding this comment.
To prevent future regressions and verify that multi-user statements do not suffer from partial execution or master/replica divergence, please add a test case covering the scenario where a valid user and an invalid user are specified in the same CREATE OR REPLACE USER statement.
For example:
CREATE USER u1@localhost IDENTIFIED BY 'old_password';
CREATE USER u2@localhost;
--error ER_PLUGIN_IS_NOT_LOADED
CREATE OR REPLACE USER u1@localhost IDENTIFIED BY 'new_password', u2@localhost IDENTIFIED VIA no_such_plugin;
--echo # Verify that u1 was NOT modified because the statement failed
--echo # (its password should still be 'old_password')
--echo # ... query mysql.user or global_priv to verify ...
DROP USER u1@localhost, u2@localhost;There was a problem hiding this comment.
Can you please describe what is this testing exactly? I do not see replication related testing done in this file.
Please consider adding a test that either checks the full scenario:
- do the command on the master
- verify that the binlog has the right contents on the master
- observe that the slave didn't stop with an error when replicating it.
- Conditionally include multiple accounts to the command as is suggested.
|
The new per-account missing-plugin skip preserves the account on the primary, but if a later account in the same Fix: Prevalidate explicit auth plugins for the whole statement before any account mutation/binlog decision, or binlog only the account mutations that were actually applied; add a multi-account binlog/RPL regression for this case. |
gkodinov
left a comment
There was a problem hiding this comment.
Thank you for your contribution. This is a preliminary review.
Please add a commit message to your commit in addition to the changes requested below.
| # MDEV-37214: CREATE OR REPLACE USER with invalid plugin must not drop | ||
| # the existing user and must not leave master/replica in diverged state. | ||
| CREATE USER u3@localhost; | ||
| --error ER_PLUGIN_IS_NOT_LOADED | ||
| CREATE OR REPLACE USER u3@localhost IDENTIFIED VIA no_such_plugin; | ||
| SELECT user FROM mysql.user WHERE user='u3'; | ||
| DROP USER u3@localhost; |
There was a problem hiding this comment.
Can you please describe what is this testing exactly? I do not see replication related testing done in this file.
Please consider adding a test that either checks the full scenario:
- do the command on the master
- verify that the binlog has the right contents on the master
- observe that the slave didn't stop with an error when replicating it.
- Conditionally include multiple accounts to the command as is suggested.
| that a missing plugin does not silently drop the user without a binlog | ||
| event, causing master/replica divergence. | ||
| */ | ||
| bool plugin_error= false; |
There was a problem hiding this comment.
While this is a valiant effort, I doubt this could be the only reason why a CREATE OR REPLACE USER can fail.
It could probably fail for a number of other reasons, like .e.g. SQL_MODE flag NO_AUTO_CREATE_USER etc.
And, it can fail due to a transient condition: e.g. the plugin could become unavailable after it being checked by your code above.
As Brandon mentioned this is a complex problem.
First of all I'd like to see an explanation on why there's a doubling of the binlog entry in this case. If there still is doubling, that is.
Secondly, you need to reach an agreement with the other MariaDB developers on what would the proper fix be in this case.
FWIW, in MySQL, a similar issue class was solved (atomic ACL) by:
- running a check pass to ensure the statement is not in some kind of violation (what you've tried to do here)
- run all table updates on an ACID compliant storage engine (InnoDB) in explicit transactional mode and making sure there's a single commit or rollback at the end.
- Making sure that in-memory structures are also reset post- rollback while holding a high level ACL lock to ensure atomicity.
A different approach could be taken here. This is why it's best to discuss it with the reviewer(s) first, as I've suggested.
I would at least make a utility function called "pre-check" and stuff all the checks you're doing here into it.
I'd also explore the other ACL statements. Maybe ALTER has a similar issue?
validated the plugin in "CREATE OR REPLACE UESR" before dropping the user.