Skip to content

Commit

Permalink
MDEV-25197: The statement set password=password('') executed in PS mo…
Browse files Browse the repository at this point in the history
…de fails in case it is run by a user with expired password

A user connected to a server with an expired password
can't change password with the statement "SET password=..."
if this statement is run in PS mode. In mentioned use case a user
gets the error ER_MUST_CHANGE_PASSWORD on attempt to run
the statement  PREPARE stmt FOR "SET password=...";

The reason of failure to reset password by a locked user using the
statement PREPARE stmt FOR "SET password=..." is that PS-related
statements are not listed among the commands allowed for execution
by a user with expired password. However, simple adding of PS-related
statements (PREPARE FOR/EXECUTE/DEALLOCATE PREPARE ) to the list of
statements allowed for execution by a locked user is not enough
to solve problems, since it opens the opportunity for a locked user
to execute any statement in the PS mode.

To exclude this opportunity, additional checking that the statement
being prepared for execution in PS-mode is the SET statement has to be added.
This extra checking has been added by this patch into the method
Prepared_statement::prepared() that executed on preparing any statement
for execution in PS-mode.
  • Loading branch information
dmitryshulga committed Apr 13, 2021
1 parent e14b682 commit 61f84bb
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 2 deletions.
32 changes: 32 additions & 0 deletions mysql-test/main/ps.result
Expand Up @@ -5536,5 +5536,37 @@ DEALLOCATE PREPARE stmt;
DROP VIEW v1;
DROP TABLE t1;
#
# MDEV-25197: The statement set password=password('') executed in PS mode
# fails in case it is run by a user with expired password
#
CREATE USER user1@localhost PASSWORD EXPIRE;
SET @disconnect_on_expired_password_save=@@global.disconnect_on_expired_password;
SET GLOBAL disconnect_on_expired_password=OFF;
connect con1,localhost,user1;
connection con1;
# Check that no regular statement like SELECT can be prepared
# by a user with an expired password
PREPARE stmt FROM "SELECT 1";
ERROR HY000: You must SET PASSWORD before executing this statement
# Check that the DEALLOCATE PREPARE statement can be run by a user
# with an expired password
PREPARE stmt FROM "SET password=password('')";
DEALLOCATE PREPARE stmt;
# Check that the SET PASSWORD statement can be executed in PS mode by
# a user with an expired password
PREPARE stmt FROM "SET password=password('')";
EXECUTE stmt;
PREPARE stmt FROM "SELECT 1";
# Check that user's password is not expired anymore
EXECUTE stmt;
1
1
DEALLOCATE PREPARE stmt;
# Clean up
disconnect con1;
connection default;
SET GLOBAL disconnect_on_expired_password=@disconnect_on_expired_password_save;
DROP USER user1@localhost;
#
# End of 10.4 tests
#
37 changes: 37 additions & 0 deletions mysql-test/main/ps.test
Expand Up @@ -4979,6 +4979,43 @@ DEALLOCATE PREPARE stmt;
DROP VIEW v1;
DROP TABLE t1;

--echo #
--echo # MDEV-25197: The statement set password=password('') executed in PS mode
--echo # fails in case it is run by a user with expired password
--echo #
CREATE USER user1@localhost PASSWORD EXPIRE;

SET @disconnect_on_expired_password_save=@@global.disconnect_on_expired_password;
SET GLOBAL disconnect_on_expired_password=OFF;

connect(con1,localhost,user1);
connection con1;
--echo # Check that no regular statement like SELECT can be prepared
--echo # by a user with an expired password
--error ER_MUST_CHANGE_PASSWORD
PREPARE stmt FROM "SELECT 1";

--echo # Check that the DEALLOCATE PREPARE statement can be run by a user
--echo # with an expired password
PREPARE stmt FROM "SET password=password('')";
DEALLOCATE PREPARE stmt;

--echo # Check that the SET PASSWORD statement can be executed in PS mode by
--echo # a user with an expired password
PREPARE stmt FROM "SET password=password('')";
EXECUTE stmt;
PREPARE stmt FROM "SELECT 1";
--echo # Check that user's password is not expired anymore
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

--echo # Clean up
disconnect con1;
connection default;

SET GLOBAL disconnect_on_expired_password=@disconnect_on_expired_password_save;
DROP USER user1@localhost;

--echo #
--echo # End of 10.4 tests
--echo #
9 changes: 7 additions & 2 deletions sql/sql_parse.cc
Expand Up @@ -1641,7 +1641,9 @@ bool dispatch_command(enum enum_server_command command, THD *thd,
if (unlikely(thd->security_ctx->password_expired &&
command != COM_QUERY &&
command != COM_PING &&
command != COM_QUIT))
command != COM_QUIT &&
command != COM_STMT_PREPARE &&
command != COM_STMT_EXECUTE))
{
my_error(ER_MUST_CHANGE_PASSWORD, MYF(0));
goto dispatch_end;
Expand Down Expand Up @@ -3490,7 +3492,10 @@ mysql_execute_command(THD *thd)
first_table->for_insert_data);

if (thd->security_ctx->password_expired &&
lex->sql_command != SQLCOM_SET_OPTION)
lex->sql_command != SQLCOM_SET_OPTION &&
lex->sql_command != SQLCOM_PREPARE &&
lex->sql_command != SQLCOM_EXECUTE &&
lex->sql_command != SQLCOM_DEALLOCATE_PREPARE)
{
my_error(ER_MUST_CHANGE_PASSWORD, MYF(0));
DBUG_RETURN(1);
Expand Down
9 changes: 9 additions & 0 deletions sql/sql_prepare.cc
Expand Up @@ -4217,6 +4217,15 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len)
thd->is_error() ||
init_param_array(this));

if (thd->security_ctx->password_expired &&
lex->sql_command != SQLCOM_SET_OPTION)
{
thd->restore_backup_statement(this, &stmt_backup);
thd->restore_active_arena(this, &stmt_backup);
thd->stmt_arena= old_stmt_arena;
my_error(ER_MUST_CHANGE_PASSWORD, MYF(0));
DBUG_RETURN(true);
}
lex->set_trg_event_type_for_tables();

/*
Expand Down

0 comments on commit 61f84bb

Please sign in to comment.