Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#
# Verify auth callback drains before plugin deinit with threadpool
#
# restart:--thread-handling=pool-of-threads --thread-pool-size=2 --thread-pool-max-threads=4
CREATE USER tp_user@127.0.0.1 IDENTIFIED WITH caching_sha2_password BY 'tp_pwd';
SET GLOBAL debug = "+d,auth_plugin_before_callback_sync";
1
1
SET DEBUG_SYNC='now WAIT_FOR auth_plugin_before_callback_entered TIMEOUT 30';
# restart
DROP USER IF EXISTS tp_user@127.0.0.1;
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
--source include/not_windows.inc
--source include/have_debug.inc
--source include/have_debug_sync.inc

--echo #
--echo # Verify auth callback drains before plugin deinit with threadpool
--echo #

--let $restart_parameters=restart:--thread-handling=pool-of-threads --thread-pool-size=2 --thread-pool-max-threads=4
--source include/restart_mysqld.inc
--source include/have_pool_of_threads.inc

CREATE USER tp_user@127.0.0.1 IDENTIFIED WITH caching_sha2_password BY 'tp_pwd';

SET GLOBAL debug = "+d,auth_plugin_before_callback_sync";

--let $mysqld_pid_file=`SELECT @@GLOBAL.pid_file`

--let $output_file= $MYSQLTEST_VARDIR/tmp/bug_ps11143_mysql_output
--let $pid_file= $MYSQLTEST_VARDIR/tmp/bug_ps11143_mysql_pid
--let $sql_file= $MYSQLTEST_VARDIR/tmp/bug_ps11143_query.sql
--write_file $sql_file
SELECT 1;
EOF
--let $command= $MYSQL
--let $command_opt= --protocol=tcp --host=127.0.0.1 --port=$MASTER_MYPORT --user=tp_user --password=tp_pwd < $sql_file
--let $redirect_stderr= 1
--source include/start_proc_in_background.inc

SET DEBUG_SYNC='now WAIT_FOR auth_plugin_before_callback_entered TIMEOUT 30';

--source include/expect_crash.inc
exec kill -TERM `cat $mysqld_pid_file`;

--source include/wait_until_disconnected.inc
--source include/wait_proc_to_finish.inc

--let $restart_parameters=
--source include/start_mysqld.inc

DROP USER IF EXISTS tp_user@127.0.0.1;

--remove_file $pid_file
--remove_file $output_file
--remove_file $sql_file
21 changes: 16 additions & 5 deletions sql/auth/acl_table_user.cc
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
#include "sql/auth/auth_acls.h" /* ACLs */
#include "sql/auth/auth_common.h" /* User_table_schema, ... */
#include "sql/auth/auth_internal.h" /* acl_print_ha_error */
#include "sql/auth/auth_plugin_shutdown.h"
#include "sql/auth/partial_revokes.h"
#include "sql/auth/sql_auth_cache.h" /* global_acl_memory */
#include "sql/auth/sql_authentication.h" /* Cached_authentication_plugins */
Expand Down Expand Up @@ -1656,6 +1657,13 @@ bool Acl_table_user_reader::read_plugin_info(
if (native_plugin) {
const uint password_len = password ? strlen(password) : 0;
st_mysql_auth *auth = (st_mysql_auth *)plugin_decl(native_plugin)->info;
Auth_plugin_operation_guard op_guard;
if (!op_guard) {
LogErr(WARNING_LEVEL, ER_AUTHCACHE_USER_IGNORED_INVALID_PASSWORD,
user.user ? user.user : "",
user.host.get_host() ? user.host.get_host() : "");
return true;
}
if (auth->validate_authentication_string(password, password_len) == 0) {
// auth_string takes precedence over password
if (user.credentials[PRIMARY_CRED].m_auth_string.length == 0) {
Expand Down Expand Up @@ -1704,10 +1712,11 @@ bool Acl_table_user_reader::read_plugin_info(
my_plugin_lock_by_name(nullptr, user.plugin, MYSQL_AUTHENTICATION_PLUGIN);
if (plugin) {
st_mysql_auth *auth = (st_mysql_auth *)plugin_decl(plugin)->info;
if (auth->validate_authentication_string(
const_cast<char *>(
user.credentials[PRIMARY_CRED].m_auth_string.str),
user.credentials[PRIMARY_CRED].m_auth_string.length)) {
Auth_plugin_operation_guard op_guard;
if (!op_guard || auth->validate_authentication_string(
const_cast<char *>(
user.credentials[PRIMARY_CRED].m_auth_string.str),
user.credentials[PRIMARY_CRED].m_auth_string.length)) {
LogErr(WARNING_LEVEL, ER_AUTHCACHE_USER_IGNORED_INVALID_PASSWORD,
user.user ? user.user : "",
user.host.get_host() ? user.host.get_host() : "");
Expand Down Expand Up @@ -1921,7 +1930,9 @@ bool Acl_table_user_reader::read_user_attributes(ACL_USER &user) {
if (plugin) {
st_mysql_auth *auth = (st_mysql_auth *)plugin_decl(plugin)->info;

if (auth->validate_authentication_string(
Auth_plugin_operation_guard op_guard;
if (!op_guard ||
auth->validate_authentication_string(
const_cast<char *>(
user.credentials[SECOND_CRED].m_auth_string.str),
user.credentials[SECOND_CRED].m_auth_string.length)) {
Expand Down
47 changes: 47 additions & 0 deletions sql/auth/auth_plugin_shutdown.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/* Copyright (c) 2026, Percona LLC and/or its affiliates.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2.0,
as published by the Free Software Foundation.

This program is designed to work with certain software (including
but not limited to OpenSSL) that is licensed under separate terms,
as designated in a particular file or component or in included license
documentation. The authors of MySQL hereby grant you an additional
permission to link the program and your derivative works with the
separately licensed software that they have either included with
the program or referenced in the documentation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License, version 2.0, for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */

#ifndef AUTH_PLUGIN_SHUTDOWN_INCLUDED
#define AUTH_PLUGIN_SHUTDOWN_INCLUDED

bool begin_auth_plugin_operation();
void end_auth_plugin_operation();
void start_auth_plugin_shutdown_and_wait();

class Auth_plugin_operation_guard {
public:
Auth_plugin_operation_guard()
: m_has_operation(begin_auth_plugin_operation()) {}

~Auth_plugin_operation_guard() {
if (m_has_operation) end_auth_plugin_operation();
}

bool has_operation() const { return m_has_operation; }
explicit operator bool() const { return m_has_operation; }

private:
bool m_has_operation;
};

#endif // AUTH_PLUGIN_SHUTDOWN_INCLUDED
13 changes: 10 additions & 3 deletions sql/auth/sha2_password.cc

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, remove formatting not relevant to the fix. Does this file have to be modified at all?

Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ struct SYS_VAR;

#include <openssl/ssl.h>

extern sha2_password::Caching_sha2_password *g_caching_sha2_password;

char *caching_sha2_rsa_private_key_path;
char *caching_sha2_rsa_public_key_path;
bool caching_sha2_auto_generate_rsa_keys = true;
Expand Down Expand Up @@ -924,6 +926,7 @@ static char perform_full_authentication = '\4';
static int caching_sha2_password_authenticate(MYSQL_PLUGIN_VIO *vio,
MYSQL_SERVER_AUTH_INFO *info) {
DBUG_TRACE;

uchar *pkt;
int pkt_len;
char scramble[SCRAMBLE_LENGTH + 1];
Expand Down Expand Up @@ -1126,9 +1129,9 @@ static int caching_sha2_password_authenticate(MYSQL_PLUGIN_VIO *vio,
*/

int caching_sha2_password_generate(char *outbuf, unsigned int *buflen,
const char *inbuf,
unsigned int inbuflen) {
const char *inbuf, unsigned int inbuflen) {
DBUG_TRACE;

std::string digest;
const std::string source(inbuf, inbuflen);
std::string random;
Expand Down Expand Up @@ -1186,6 +1189,7 @@ int caching_sha2_password_generate(char *outbuf, unsigned int *buflen,
static int caching_sha2_password_validate(char *const inbuf,
unsigned int buflen) {
DBUG_TRACE;

const std::string serialized_string(inbuf, buflen);
if (g_caching_sha2_password->validate_hash(serialized_string)) return 1;
return 0;
Expand Down Expand Up @@ -1243,10 +1247,12 @@ static int caching_sha2_authentication_init(MYSQL_PLUGIN plugin_ref) {

static int caching_sha2_authentication_deinit(void *arg [[maybe_unused]]) {
DBUG_TRACE;
if (g_caching_sha2_password) {

if (g_caching_sha2_password != nullptr) {
delete g_caching_sha2_password;
g_caching_sha2_password = nullptr;
}

return 0;
}

Expand Down Expand Up @@ -1391,6 +1397,7 @@ static SHOW_VAR caching_sha2_password_status_variables[] = {
static int sha2_cache_cleaner_notify(MYSQL_THD, mysql_event_class_t event_class,
const void *event) {
DBUG_TRACE;

if (event_class == MYSQL_AUDIT_AUTHENTICATION_CLASS) {
const struct mysql_event_authentication *authentication_event =
(const struct mysql_event_authentication *)event;
Expand Down
16 changes: 11 additions & 5 deletions sql/auth/sql_auth_cache.cc
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
#include "sql/auth/auth_acls.h"
#include "sql/auth/auth_common.h" // ACL_internal_schema_access
#include "sql/auth/auth_internal.h" // auth_plugin_is_built_in
#include "sql/auth/auth_plugin_shutdown.h"
#include "sql/auth/auth_utility.h"
#include "sql/auth/dynamic_privilege_table.h"
#include "sql/auth/sql_authentication.h" // g_cached_authentication_plugins
Expand Down Expand Up @@ -1973,12 +1974,17 @@ bool set_user_salt(ACL_USER *acl_user) {
MYSQL_AUTHENTICATION_PLUGIN);
if (plugin) {
st_mysql_auth *auth = (st_mysql_auth *)plugin_decl(plugin)->info;
Auth_plugin_operation_guard op_guard;

for (int i = 0; i < NUM_CREDENTIALS && !result; ++i) {
result = auth->set_salt(acl_user->credentials[i].m_auth_string.str,
acl_user->credentials[i].m_auth_string.length,
acl_user->credentials[i].m_salt,
&acl_user->credentials[i].m_salt_len);
if (!op_guard) {
result = true;
} else {
for (int i = 0; i < NUM_CREDENTIALS && !result; ++i) {
result = auth->set_salt(acl_user->credentials[i].m_auth_string.str,
acl_user->credentials[i].m_auth_string.length,
acl_user->credentials[i].m_salt,
&acl_user->credentials[i].m_salt_len);
}
}
plugin_unlock(nullptr, plugin);
}
Expand Down
70 changes: 68 additions & 2 deletions sql/auth/sql_authentication.cc
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
#include <atomic>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#include <condition_variable>
#include <mutex>
#include <string> /* std::string */
#include <utility>
#include <vector> /* std::vector */
Expand Down Expand Up @@ -77,6 +79,7 @@
#include "sql/auth/auth_acls.h"
#include "sql/auth/auth_common.h"
#include "sql/auth/auth_internal.h" // optimize_plugin_compare_by_pointer
#include "sql/auth/auth_plugin_shutdown.h"
#include "sql/auth/partial_revokes.h"
#include "sql/auth/sql_auth_cache.h" // acl_cache
#include "sql/auth/sql_security_ctx.h"
Expand Down Expand Up @@ -1286,6 +1289,52 @@ int security_level(void) {
external_roles_t g_external_roles;
Cached_authentication_plugins *g_cached_authentication_plugins = nullptr;

namespace {

class Auth_plugin_shutdown_state {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this is defined here? It is rather tightly related to Auth_plugin_operation_guard.
In fact, some details of this state should be visible only to the guard class and we need only a single instance of the shutdown status class to be used via the guard class. In order to better express intentions, avoid misuse and possibly simplify the code I'd suggest:

  1. replacing the below global g_auth_plugin_shutdown_state by a static member of Auth_plugin_operation_guard (the static functions will be to remove)
  2. making Auth_plugin_operation_guard friend of Auth_plugin_shutdown_state and constructors of the latter protected
  3. moving the class implementation to e.g. sql/auth/auth_plugin_shutdown.cc

public:
bool begin_operation() {
std::lock_guard<std::mutex> lock(m_mutex);
if (m_shutting_down) return false;
++m_active_operations;
return true;
}

void end_operation() {
std::lock_guard<std::mutex> lock(m_mutex);
assert(m_active_operations > 0);
if (--m_active_operations == 0) m_cv.notify_all();
}

void start_shutdown_and_wait() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering what happens when:

  1. A new connection starts authenticating
  2. The server starts closing
    Please note, that some authentications may take a while (e.g. IIRC LDAP takes few client - server exchanges).

If the threads are stopped first (seems to be more logical to me), how it is done, does the main wait until all connections are "gently" closed? I suppose at least there is some timeout. Then (I guess) sth like pthread_cancel() may be used and if so, probably m_active_operations never reach 0 and the server hangs. For that scenario I recommend replacing wait() with wait_for().

If the order is reverse, the hank is even more obvious.

std::unique_lock<std::mutex> lock(m_mutex);
m_shutting_down = true;
m_cv.wait(lock, [&] { return m_active_operations == 0; });
}

private:
std::mutex m_mutex;
std::condition_variable m_cv;
size_t m_active_operations = 0;
bool m_shutting_down = false;
};

Auth_plugin_shutdown_state g_auth_plugin_shutdown_state;

} // namespace

bool begin_auth_plugin_operation() {
return g_auth_plugin_shutdown_state.begin_operation();
}

void end_auth_plugin_operation() {
g_auth_plugin_shutdown_state.end_operation();
}

void start_auth_plugin_shutdown_and_wait() {
g_auth_plugin_shutdown_state.start_shutdown_and_wait();
}

bool disconnect_on_expired_password = true;

extern bool initialized;
Expand Down Expand Up @@ -3565,7 +3614,18 @@ static int do_auth_once(THD *thd, const LEX_CSTRING &auth_plugin_name,

if (plugin) {
st_mysql_auth *auth = (st_mysql_auth *)plugin_decl(plugin)->info;
res = auth->authenticate_user(mpvio, &mpvio->auth_info);
Auth_plugin_operation_guard op_guard;
if (op_guard) {
DBUG_EXECUTE_IF("auth_plugin_before_callback_sync", {
const char act[] = "now SIGNAL auth_plugin_before_callback_entered";
assert(!debug_sync_set_action(thd, STRING_WITH_LEN(act)));
my_sleep(2000000);
};);
res = auth->authenticate_user(mpvio, &mpvio->auth_info);
} else {
my_error(ER_SERVER_SHUTDOWN, MYF(0));
res = CR_ERROR;
}

if (unlock_plugin) plugin_unlock(thd, plugin);
} else {
Expand Down Expand Up @@ -3698,7 +3758,13 @@ static int do_multi_factor_auth(THD *thd, MPVIO_EXT *mpvio) {
.auth_string_length;
mpvio->status = MPVIO_EXT::START_MFA;
st_mysql_auth *auth = (st_mysql_auth *)plugin_decl(plugin)->info;
res = auth->authenticate_user(mpvio, &mpvio->auth_info);
Auth_plugin_operation_guard op_guard;
if (op_guard) {
res = auth->authenticate_user(mpvio, &mpvio->auth_info);
} else {
my_error(ER_SERVER_SHUTDOWN, MYF(0));
res = CR_ERROR;
}
if (res == CR_OK_AUTH_IN_SANDBOX_MODE) {
/*
Server allows user account to connect in case registration is
Expand Down
17 changes: 13 additions & 4 deletions sql/auth/sql_mfa.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

#include "mysql/components/services/log_builtins.h"
#include "mysql/plugin_auth.h"
#include "sql/auth/auth_plugin_shutdown.h"
#include "sql/auth/authentication_policy.h"
#include "sql/auth/sql_mfa.h"
#include "sql/derror.h" /* ER_THD */
Expand Down Expand Up @@ -643,8 +644,10 @@ bool Multi_factor_auth_info::validate_plugins_in_auth_chain(
inbuflen = gen_password.length();
set_generated_password(gen_password.c_str(), gen_password.length());
}
if (auth->generate_authentication_string(outbuf, &buflen, inbuf,
inbuflen)) {
Auth_plugin_operation_guard op_guard;
if (!op_guard || auth->generate_authentication_string(outbuf, &buflen,
inbuf, inbuflen)) {
if (!op_guard) my_error(ER_SERVER_SHUTDOWN, MYF(0));
plugin_unlock(nullptr, plugin);
return (true);
}
Expand Down Expand Up @@ -894,8 +897,11 @@ bool Multi_factor_auth_info::init_registration(THD *thd, uint nth_factor) {
/* convert auth string to base64 to be stored in mysql.user table */
char outbuf[MAX_FIELD_WIDTH] = {0};
unsigned int outbuflen = MAX_FIELD_WIDTH;
if (auth->generate_authentication_string(
Auth_plugin_operation_guard op_guard;
if (!op_guard ||
auth->generate_authentication_string(
outbuf, &outbuflen, reinterpret_cast<char *>(buf), buflen)) {
if (!op_guard) my_error(ER_SERVER_SHUTDOWN, MYF(0));
if (buf) delete[] buf;
plugin_unlock(nullptr, plugin);
return (true);
Expand Down Expand Up @@ -982,9 +988,12 @@ bool Multi_factor_auth_info::finish_registration(THD *thd, LEX_USER *user_name,
/* convert auth string to base64 to be stored in mysql.user table */
char outbuf[MAX_FIELD_WIDTH] = {0};
unsigned int outbuflen = MAX_FIELD_WIDTH;
if (auth->generate_authentication_string(
Auth_plugin_operation_guard op_guard;
if (!op_guard ||
auth->generate_authentication_string(
outbuf, &outbuflen, reinterpret_cast<char *>(challenge_response),
challenge_response_len)) {
if (!op_guard) my_error(ER_SERVER_SHUTDOWN, MYF(0));
plugin_unlock(nullptr, plugin);
return (true);
}
Expand Down
Loading
Loading