Bug #120480 Crash in set_and_validate_user_attributes during relay log recovery
Submitted: 14 May 20:59 Modified: 14 May 21:00
Reporter: Fariha Shaikh (OCA) Email Updates:
Status: Open Impact on me:
None 
Category:MySQL Server: Replication Severity:S3 (Non-critical)
Version:8.0.40 OS:Any
Assigned to: CPU Architecture:Any
Tags: crash, relay-log-recovery, replication, segfault

[14 May 20:59] Fariha Shaikh
Description:
A MySQL replica crashes with a segfault in `set_and_validate_user_attributes()` during relay log recovery when the relay log contains an `ALTER USER` or `GRANT` statement for a user that does not exist on the replica.

The root cause is a startup ordering issue in `mysqld.cc`. `ReplicaInitializer` (which triggers relay log recovery and can apply relay log events) executes before `update_authentication_policy()` populates the global `authentication_policy_list` vector. 

If relay log recovery applies an `ALTER USER` or `GRANT` for a non-existent user, the code path in `set_and_validate_user_attributes()` (`sql/auth/sql_user.cc`) reaches:

```
if (authentication_policy_list[0].compare("*") == 0)

```

Since `authentication_policy_list` is still empty at this point, `operator[](0)` is an out-of-bounds access. On release builds this manifests as a segfault in `std::string::compare(const char*)`. On debug builds it triggers a vector bounds-check assertion.

The crash is deterministic on restart — the same relay log event causes the crash every time, putting the replica into a restart loop that cannot self-recover.

How to repeat:
Requires a source-replica GTID replication setup. The key is to have a pending relay log event (`ALTER USER` or `GRANT`) for a user that does not exist on the replica, then restart the replica with `relay_log_recovery=ON`.

```
-- Setup: source (server-id=1, port=3306) and replica (server-id=2, port=3307)
-- with GTID replication. Replica uses relay_log_recovery=ON.

-- 1. On source: create a user and let it replicate to the replica
CREATE USER 'testuser'@'%' IDENTIFIED WITH mysql_native_password BY 'pass';
-- Wait for replication to sync

-- 2. On replica: stop replication and drop the user locally
STOP REPLICA;
DROP USER 'testuser'@'%';

-- 3. On source: execute ALTER USER (this goes into the binlog)
ALTER USER 'testuser'@'%' ACCOUNT LOCK;
-- (A GRANT statement also triggers the same crash)

-- 4. Kill the replica process (simulate crash)
-- kill -9 <replica_mysqld_pid>

-- 5. Restart the replica with --relay-log-recovery=ON
-- Relay log recovery applies the ALTER USER statement.
-- Since 'testuser' does not exist on the replica, the code enters
-- the "user does not exist" path in set_and_validate_user_attributes()
-- and accesses authentication_policy_list[0] before it has been populated.
-- Result: SEGFAULT (release) or assertion failure (debug)
```

Note: Because relay log recovery and `authentication_policy_list` initialization happen close together during startup, a timing-dependent race exists. To reliably reproduce, add a delay after `ReplicaInitializer` in `mysqld.cc`:
```
DBUG_EXECUTE_IF("delay_after_replica_init", { sleep(10); });
```

Then start the replica with `--debug='+d,delay_after_replica_init'`.

Stack trace (release build):
```
#0 std::__cxx11::basic_string<...>::compare(char const*) const
#1 set_and_validate_user_attributes(...) at sql/auth/sql_user.cc:1563
#2 mysql_alter_user(...) at sql/auth/sql_user.cc:3807
#3 mysql_execute_command(...) at sql/sql_parse.cc
#4 dispatch_sql_command(...) at sql/sql_parse.cc
#5 Query_log_event::do_apply_event(...) at sql/log_event.cc
#6 handle_slave_sql at sql/rpl_replica.cc
```

Suggested fix:
Move `update_authentication_policy()` to execute before `ReplicaInitializer` in the server startup sequence in `mysqld.cc`. 

Alternatively, add a bounds check before accessing `authentication_policy_list[0]` in `set_and_validate_user_attributes()`, falling back to `default_auth_plugin_name` when the list is empty.
[14 May 21:00] Fariha Shaikh
I am also taking a look at the fix for this, will update here with a PR link once it's ready (soon), thank you