Skip to content

Commit 11e5717

Browse files
committed
Bug#30600321 MAX_QUERIES_PER_HOUR 0 ONLY REMOVES THE LIMIT ON 1 MYSQLD
This patch addresses two issues. First, all ALTER USER statements were distributed as snapshots, merely because they might contain a plaintext password, when in fact many of them could have used the preferred method of statement distribution. To fix this, for the SET PASSWORD and ALTER USER statements that do in fact contain passwords, add the query rewrite parameters (which were generated for binlogging) to the Acl_change_notification and then re-use them to rewrite the statement for schema distribution. Secondly, "SHOW CREATE USER" does not include resource limits that are set to zero. When applying a snapshot in Ndb_stored_grants, add an ALTER USER statement to explicitly set all resource limits to zero before applying the statement obtained from SHOW CREATE USER. The first patch alone fixes the bug for most common cases, and improves efficiency for distributing ALTER USER. The second patch alone fixes the bug for all cases, but makes snapshots less efficient. The test case is added to test ndb.stored_grants_error_handling Change-Id: I8517ce79906ee1be16eeddf0f48fa1d8128117d6
1 parent d15d414 commit 11e5717

File tree

9 files changed

+133
-32
lines changed

9 files changed

+133
-32
lines changed

mysql-test/suite/ndb/r/stored_grants_error_handling.result

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,17 @@ CREATE USER "mcmd"@"localhost";
22
GRANT ALL PRIVILEGES ON *.* to "mcmd"@"localhost";
33
CALL mtr.add_suppression("NDB: Error 626, Tuple did not exist");
44
GRANT ALL PRIVILEGES ON *.* to "mcmd"@"localhost";
5+
Expect 33
6+
max_questions
7+
33
8+
Expect 0
9+
max_questions
10+
0
11+
Expect 55
12+
max_questions
13+
55
14+
Expect 0
15+
max_questions
16+
0
517
DROP USER mcmd@localhost;
18+
DROP USER lu1@a;

mysql-test/suite/ndb/t/stored_grants_error_handling.test

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
--source include/have_ndb.inc
22
--source suite/ndb/include/ndb_find_tools.inc
33

4+
#
5+
# Open connections
6+
#
7+
connect(mysqld2,127.0.0.1,root,,test,$MASTER_MYPORT1);
8+
9+
#
410
# Bug#30556487 MYSQLD HANG IN NDB_STORED_GRANTS CODE ON CREATE USER
11+
#
512
CREATE USER "mcmd"@"localhost";
613
GRANT ALL PRIVILEGES ON *.* to "mcmd"@"localhost";
714

@@ -13,6 +20,60 @@ GRANT ALL PRIVILEGES ON *.* to "mcmd"@"localhost";
1320
CALL mtr.add_suppression("NDB: Error 626, Tuple did not exist");
1421
GRANT ALL PRIVILEGES ON *.* to "mcmd"@"localhost";
1522

23+
#
24+
# Bug#30600321 MAX_QUERIES_PER_HOUR 0 ONLY REMOVES THE LIMIT ON 1 MYSQLD
25+
#
26+
--enable_result_log
27+
--disable_query_log
28+
29+
# Bug#30600321 Test (1): Alter resource limit of stored user
30+
ALTER USER "mcmd"@"localhost" WITH MAX_QUERIES_PER_HOUR 33;
31+
32+
connection mysqld2;
33+
echo Expect 33;
34+
SELECT max_questions from mysql.user where user = 'mcmd';
35+
36+
connection default;
37+
ALTER USER "mcmd"@"localhost" WITH MAX_QUERIES_PER_HOUR 0;
38+
39+
connection mysqld2;
40+
let $max= `SELECT max_questions from mysql.user where user = 'mcmd'`;
41+
if ($max != 0)
42+
{
43+
die Test failed -- max_questions was not zero;
44+
}
45+
46+
# Bug#30600321 Test (2): Alter both resource limit and password of stored user
47+
connection default;
48+
ALTER USER "mcmd"@"localhost" WITH MAX_QUERIES_PER_HOUR 44;
49+
ALTER USER "mcmd"@"localhost" IDENTIFIED BY "Garb_farb_earb" WITH MAX_QUERIES_PER_HOUR 0;
50+
51+
connection mysqld2;
52+
echo Expect 0;
53+
SELECT max_questions from mysql.user where user = 'mcmd';
54+
55+
# Bug#30600321 Test (3): Alter resource limit of stored and non-stored users
56+
connection default;
57+
CREATE USER "lu1"@"a";
58+
ALTER USER "lu1"@"a", "mcmd"@"localhost" WITH MAX_QUERIES_PER_HOUR 55;
59+
60+
connection mysqld2;
61+
echo Expect 55;
62+
SELECT max_questions from mysql.user where user = 'mcmd';
63+
64+
connection default;
65+
ALTER USER "lu1"@"a", "mcmd"@"localhost" WITH MAX_QUERIES_PER_HOUR 0;
66+
67+
connection mysqld2;
68+
echo Expect 0;
69+
SELECT max_questions from mysql.user where user = 'mcmd';
70+
71+
--enable_query_log
72+
73+
#
1674
# Cleanup
75+
#
76+
connection default;
1777
DROP USER mcmd@localhost;
78+
DROP USER lu1@a;
1879

sql/auth/acl_change_notification.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
2727
#include "my_sqlcommand.h" // enum_sql_command
2828
#include "sql/table.h" // LEX_USER, LEX_CSTRING, List
2929

30+
class Rewrite_params; // forward declaration
31+
3032
class Acl_change_notification {
3133
public:
3234
struct User {
@@ -39,23 +41,24 @@ class Acl_change_notification {
3941

4042
Acl_change_notification(class THD *thd, enum_sql_command op,
4143
const List<LEX_USER> *users,
44+
Rewrite_params *rewrite_params,
4245
const List<LEX_CSTRING> *dynamic_privs);
4346

4447
private:
4548
enum_sql_command operation;
4649
std::string db;
47-
std::string query;
4850
std::vector<User> user_list;
4951
std::vector<std::string> dynamic_privilege_list;
52+
Rewrite_params *rewrite_params;
5053

5154
public:
5255
enum_sql_command get_operation() const { return operation; }
5356
const std::string &get_db() const { return db; }
54-
const std::string &get_query_for_logging() const { return query; }
5557
const std::vector<User> &get_user_list() const { return user_list; }
5658
const std::vector<std::string> &get_dynamic_privilege_list() const {
5759
return dynamic_privilege_list;
5860
}
61+
Rewrite_params *get_rewrite_params() const { return rewrite_params; }
5962
};
6063

6164
#endif

sql/auth/auth_internal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ bool log_and_commit_acl_ddl(THD *thd, bool transactional_tables,
209209
bool log_to_binlog = true);
210210
void acl_notify_htons(THD *thd, enum_sql_command operation,
211211
const List<LEX_USER> *users,
212+
std::set<LEX_USER *> *rewrite_users = nullptr,
212213
const List<LEX_CSTRING> *dynamic_privs = nullptr);
213214

214215
/* sql_authorization */

sql/auth/sql_authorization.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3592,7 +3592,7 @@ bool mysql_grant(THD *thd, const char *db, List<LEX_USER> &list, ulong rights,
35923592
my_ok(thd);
35933593
/* Notify storage engines */
35943594
acl_notify_htons(thd, revoke_grant ? SQLCOM_REVOKE : SQLCOM_GRANT, &list,
3595-
granted_dynamic_privs);
3595+
nullptr, granted_dynamic_privs);
35963596
}
35973597

35983598
return error;

sql/auth/sql_user.cc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1578,11 +1578,11 @@ bool change_password(THD *thd, LEX_USER *lex_user, const char *new_password,
15781578
authentication_plugin.c_str(), is_role, nullptr, nullptr);
15791579
} /* Critical section */
15801580

1581-
/* Notify storage engines */
1581+
/* Notify storage engines (including rewrite list) */
15821582
if (!(result || commit_result)) {
15831583
List<LEX_USER> user_list;
15841584
user_list.push_back(lex_user);
1585-
acl_notify_htons(thd, SQLCOM_SET_PASSWORD, &user_list);
1585+
acl_notify_htons(thd, SQLCOM_SET_PASSWORD, &user_list, &users);
15861586
}
15871587

15881588
return result || commit_result;
@@ -2819,8 +2819,8 @@ bool mysql_alter_user(THD *thd, List<LEX_USER> &list, bool if_exists) {
28192819
reset_mqh(thd, extra_user, false);
28202820
}
28212821
}
2822-
/* Notify storage engines */
2823-
acl_notify_htons(thd, SQLCOM_ALTER_USER, &list);
2822+
/* Notify storage engines (including rewrite list) */
2823+
acl_notify_htons(thd, SQLCOM_ALTER_USER, &list, &extra_users);
28242824
}
28252825

28262826
if (result == 0) {

sql/auth/sql_user_table.cc

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -558,13 +558,10 @@ ulong get_access(TABLE *form, uint fieldnr, uint *next_field) {
558558
*/
559559
Acl_change_notification::Acl_change_notification(
560560
THD *thd, enum_sql_command op, const List<LEX_USER> *users,
561-
const List<LEX_CSTRING> *dynamic_privs)
562-
: operation(op), db(thd->db().str, thd->db().length) {
563-
if (thd->rewritten_query().length()) {
564-
query.assign(thd->rewritten_query().ptr(), thd->rewritten_query().length());
565-
} else {
566-
query.assign(thd->query().str, thd->query().length);
567-
}
561+
Rewrite_params *rewrite, const List<LEX_CSTRING> *dynamic_privs)
562+
: operation(op),
563+
db(thd->db().str, thd->db().length),
564+
rewrite_params(rewrite) {
568565
if (users) {
569566
/* Copy data out of List<LEX_USER> */
570567
user_list.reserve(users->size());
@@ -581,11 +578,12 @@ Acl_change_notification::Acl_change_notification(
581578
}
582579
}
583580

584-
void acl_notify_htons(THD *thd MY_ATTRIBUTE((unused)),
585-
enum_sql_command operation MY_ATTRIBUTE((unused)),
586-
const List<LEX_USER> *users MY_ATTRIBUTE((unused)),
587-
const List<LEX_CSTRING> *dynamic_privs
588-
MY_ATTRIBUTE((unused))) {
581+
void acl_notify_htons(
582+
THD *thd MY_ATTRIBUTE((unused)),
583+
enum_sql_command operation MY_ATTRIBUTE((unused)),
584+
const List<LEX_USER> *users MY_ATTRIBUTE((unused)),
585+
std::set<LEX_USER *> *rewrite_users MY_ATTRIBUTE((unused)),
586+
const List<LEX_CSTRING> *dynamic_privs MY_ATTRIBUTE((unused))) {
589587
DBUG_TRACE;
590588
DBUG_PRINT("enter", ("db: %s query: '%s'", thd->db().str, thd->query().str));
591589
#ifdef WITH_NDBCLUSTER_STORAGE_ENGINE
@@ -594,7 +592,9 @@ void acl_notify_htons(THD *thd MY_ATTRIBUTE((unused)),
594592
So, instantiate it and send a notification only if the Server is
595593
built with ndbcluster SE.
596594
*/
597-
Acl_change_notification notice(thd, operation, users, dynamic_privs);
595+
User_params rewrite_user_params(rewrite_users);
596+
User_params *rewrite = rewrite_users ? &rewrite_user_params : nullptr;
597+
Acl_change_notification notice(thd, operation, users, rewrite, dynamic_privs);
598598
ha_acl_notify(thd, &notice);
599599
#endif
600600
}

storage/ndb/plugin/ha_ndbcluster_binlog.cc

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#include "sql/rpl_injector.h"
4141
#include "sql/rpl_slave.h"
4242
#include "sql/sql_lex.h"
43+
#include "sql/sql_rewrite.h"
4344
#include "sql/sql_table.h" // build_table_filename
4445
#include "sql/sql_thd_internal_api.h"
4546
#include "sql/thd_raii.h"
@@ -596,8 +597,17 @@ static void ndbcluster_acl_notify(THD *thd,
596597
return;
597598
}
598599

599-
const std::string &query = notice->get_query_for_logging();
600+
/* Obtain the query in a form suitable for writing to the error log.
601+
The password is replaced with the string "<secret>".
602+
*/
603+
std::string query;
604+
if (thd->rewritten_query().length())
605+
query.assign(thd->rewritten_query().ptr(), thd->rewritten_query().length());
606+
else
607+
query.assign(thd->query().str, thd->query().length);
608+
DBUG_ASSERT(query.length());
600609
ndb_log_verbose(9, "ACL considering: %s", query.c_str());
610+
601611
std::string user_list;
602612
bool dist_use_db = false; // Prepend "use [db];" to statement
603613
bool dist_refresh = false; // All participants must refresh their caches
@@ -632,6 +642,19 @@ static void ndbcluster_acl_notify(THD *thd,
632642

633643
DBUG_ASSERT(strategy == Ndb_stored_grants::Strategy::STATEMENT);
634644
ndb_log_verbose(9, "ACL change distribution: STATEMENT");
645+
646+
/* If the notice contains rewrite_params, query is an ALTER USER or SET
647+
PASSWORD statement and must be rewritten again, as if for the binlog,
648+
replacing a plaintext password with a crytpographic hash.
649+
*/
650+
if (notice->get_rewrite_params()) {
651+
String rewritten_query;
652+
mysql_rewrite_acl_query(thd, rewritten_query, Consumer_type::BINLOG,
653+
notice->get_rewrite_params(), false);
654+
query.assign(rewritten_query.c_ptr_safe(), rewritten_query.length());
655+
DBUG_ASSERT(query.length());
656+
}
657+
635658
if (!schema_dist_client.acl_notify(
636659
dist_use_db ? notice->get_db().c_str() : nullptr, query.c_str(),
637660
query.length(), dist_refresh))

storage/ndb/plugin/ndb_stored_grants.cc

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -617,16 +617,17 @@ int ThreadContext::drop_users(ChangeNotice *notice,
617617
from SHOW CREATE USER, so its exact format is known. For idempotence, it
618618
must be rewritten as several statements. The final result is:
619619
CREATE USER IF NOT EXISTS user@host;
620-
ALTER USER user@host ... ;
621620
REVOKE ALL ON *.* FROM user@host;
622-
GRANT ... TO user@host;
623-
GRANT ... TO user@host;
624-
ALTER USER user@host DEFAULT ROLE r;
621+
ALTER USER user@host ...; [CLEAR RESOURCE LIMITS]
622+
ALTER USER user@host ...; [SET VALUES FROM SHOW CREATE USER]
625623
*/
626624
void ThreadContext::create_user(std::string &name, std::string &statement) {
627625
const std::string create_user("CREATE USER IF NOT EXISTS ");
628626
const std::string alter_user("ALTER USER ");
629627
const std::string revoke_all("REVOKE ALL ON *.* FROM ");
628+
const std::string set_resource_defaults(
629+
" WITH MAX_QUERIES_PER_HOUR 0 MAX_UPDATES_PER_HOUR 0 "
630+
" MAX_CONNECTIONS_PER_HOUR 0 MAX_USER_CONNECTIONS 0");
630631

631632
/* Run statement CREATE USER IF NOT EXISTS */
632633
if (!get_local_user(name)) {
@@ -635,6 +636,12 @@ void ThreadContext::create_user(std::string &name, std::string &statement) {
635636
run_acl_statement(create_user + name);
636637
}
637638

639+
/* Revoke any privileges the user may have had prior to this snapshot. */
640+
run_acl_statement(revoke_all + name);
641+
642+
/* Clear resource limits (this is not included in SHOW CREATE USER) */
643+
run_acl_statement(alter_user + name + set_resource_defaults);
644+
638645
/* Rewrite CREATE to ALTER */
639646
statement = statement.replace(0, 6, "ALTER");
640647

@@ -658,9 +665,6 @@ void ThreadContext::create_user(std::string &name, std::string &statement) {
658665

659666
/* Run the rest of the statement. */
660667
run_acl_statement(statement.erase(default_role_pos, role_clause_len));
661-
662-
/* Revoke any privileges the user may have had prior to this snapshot. */
663-
run_acl_statement(revoke_all + name);
664668
}
665669

666670
/* Apply the snapshot in m_current_rows,
@@ -849,10 +853,6 @@ Ndb_stored_grants::Strategy ThreadContext::handle_change(ChangeNotice *notice) {
849853
/* ALTER USER, SET PASSWORD, or GRANT or REVOKE of misc. privileges */
850854
rebuild_local_cache = false;
851855
update_list = &m_intersection;
852-
/* Distribute ALTER USER and SET PASSWORD as snapshot refreshes
853-
in order to avoid transmitting plaintext passwords. */
854-
if (operation == SQLCOM_ALTER_USER || operation == SQLCOM_SET_PASSWORD)
855-
dist_as_snapshot = true;
856856
}
857857

858858
/* drop_users() will DROP USER or REVOKE NDB_STORED_USER, as appropriate */

0 commit comments

Comments
 (0)