Skip to content

Commit 987fc9a

Browse files
committed
Bug#33057164 PSI_THREAD_INFO not updated while executing PREPARED STATEMENTS
Bug 104121 PSI_THREAD_INFO not updated while executing PREPARED STATEMENTS Problem: When executing a PREPARED statement, table performance_schema.threads does not populate the PROCESSLIST_INFO column. This prevents to properly observe the execution of prepared statements. This is also a regression, introduced in: - MySQL 5.6.46 - MySQL 5.7.28 - MySQL 8.0.18 Root cause: The fix for: Bug 20712046 SHOW PROCESSLIST AND PERFORMANCE_SCHEMA TABLES DO NOT MASK PASSWORD FROM QUERY removed a call to PSI_THREAD_CALL(set_thread_info) located in THD::set_query() PSI_THREAD_CALL(set_thread_info) was called from different places in the same fix, to make sure a properly sanitized query text is displayed if needed, but the code path for prepared statements was overlooked. Fix: The fix is to call PSI_THREAD_CALL(set_thread_info) in the missing code path, when executing a prepared statement. The call uses either the executed query, or a sanitized version of it if it contains sensitive data. Additional fix: The logic around rewrite_query_if_needed() was revised. Trying to anticipate if a query will be displayed or not, to possibly save a rewrite, is too fragile, and too dangerous. Queries that contain sensitive data are always rewritten, regardless of whether the query will actually be printed somewhere or not. Change-Id: Ief9c80a24f539f0889371766843d2f7b05812ad8
1 parent 6edf72e commit 987fc9a

File tree

7 files changed

+734
-40
lines changed

7 files changed

+734
-40
lines changed

include/mysql/psi/mysql_thread.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,18 @@ static inline void mysql_thread_set_secondary_engine(bool secondary
187187
#endif
188188
}
189189

190+
/**
191+
Set the INFO attribute in the thread instrumentation.
192+
@param str query string
193+
@param len query length
194+
*/
195+
static inline void mysql_thread_set_info(const char *str [[maybe_unused]],
196+
int len [[maybe_unused]]) {
197+
#ifdef HAVE_PSI_THREAD_INTERFACE
198+
PSI_THREAD_CALL(set_thread_info)(str, len);
199+
#endif
200+
}
201+
190202
/** @} (end of group psi_api_thread) */
191203

192204
#endif
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
2+
#
3+
# Checks to perform after each query.
4+
#
5+
# Parameters:
6+
# - $con1_thread_id
7+
# - $con1_connection_id
8+
#
9+
10+
--disable_query_log
11+
eval SELECT "thread" as mark, PROCESSLIST_INFO
12+
FROM performance_schema.threads
13+
WHERE THREAD_ID = $con1_thread_id;
14+
15+
eval SELECT "processlist" as mark, INFO
16+
FROM performance_schema.processlist
17+
WHERE ID = $con1_connection_id;
18+
19+
eval SELECT "I_S.PROCESSLIST" as mark, INFO
20+
FROM INFORMATION_SCHEMA.PROCESSLIST
21+
WHERE ID = $con1_connection_id;
22+
23+
eval SELECT "current" as mark, EVENT_NAME, SQL_TEXT, DIGEST_TEXT
24+
FROM performance_schema.events_statements_current
25+
WHERE THREAD_ID = $con1_thread_id;
26+
27+
eval SELECT "history" as mark, EVENT_NAME, SQL_TEXT, DIGEST_TEXT
28+
FROM performance_schema.events_statements_history
29+
WHERE THREAD_ID = $con1_thread_id
30+
ORDER BY EVENT_ID;
31+
32+
eval SELECT "history_long" as mark, EVENT_NAME, SQL_TEXT, DIGEST_TEXT
33+
FROM performance_schema.events_statements_history_long
34+
WHERE THREAD_ID = $con1_thread_id
35+
ORDER BY EVENT_ID;
36+
--enable_query_log

mysql-test/suite/perfschema/r/rewritten_queries.result

Lines changed: 466 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
2+
# Test requires: sp-protocol/ps-protocol/view-protocol/cursor-protocol disabled
3+
--source include/no_protocol.inc
4+
5+
6+
--echo
7+
--echo # Connection 1
8+
--echo
9+
connect(con1, localhost, root,,);
10+
--disable_query_log
11+
let $con1_thread_id= `SELECT ps_current_thread_id()`;
12+
let $con1_connection_id= `SELECT connection_id()`;
13+
--enable_query_log
14+
15+
--connection default
16+
CREATE TABLE test.t1(a int, b varchar(80));
17+
INSERT INTO test.t1 VALUES (1, "a"), (2, "b"), (3, "c");
18+
SELECT * FROM test.t1;
19+
20+
BEGIN;
21+
SELECT * FROM test.t1 FOR UPDATE;
22+
23+
--echo
24+
--echo # Regular query
25+
--echo
26+
--connection con1
27+
SELECT "This is not secret" as normal_query;
28+
--connection default
29+
--source ../include/rewritten_queries.inc
30+
31+
--echo
32+
--echo # Regular query (in flight)
33+
--echo
34+
--connection con1
35+
--send INSERT INTO test.t1 VALUES (4, "This is not secret");
36+
--connection default
37+
--source ../include/rewritten_queries.inc
38+
39+
COMMIT;
40+
--connection con1
41+
--reap
42+
--connection default
43+
--source ../include/rewritten_queries.inc
44+
45+
--echo
46+
--echo # Sensitive query
47+
--echo
48+
--connection con1
49+
CREATE USER alice IDENTIFIED BY 'thisisapassword';
50+
--connection default
51+
--source ../include/rewritten_queries.inc
52+
53+
54+
--echo
55+
--echo # Regular prepared statement (prepare)
56+
--echo
57+
--connection con1
58+
PREPARE stmt FROM "SELECT 'This is not a secret either' as prepared_query";
59+
--connection default
60+
--source ../include/rewritten_queries.inc
61+
62+
--echo
63+
--echo # Regular prepared statement (execute)
64+
--echo
65+
--connection con1
66+
EXECUTE stmt;
67+
--connection default
68+
--source ../include/rewritten_queries.inc
69+
70+
--echo
71+
--echo # Regular prepared statement (deallocate)
72+
--echo
73+
--connection con1
74+
DEALLOCATE PREPARE stmt;
75+
--connection default
76+
--source ../include/rewritten_queries.inc
77+
78+
BEGIN;
79+
SELECT * FROM test.t1 FOR UPDATE;
80+
81+
--echo
82+
--echo # Regular prepared statement (prepare)
83+
--echo
84+
--connection con1
85+
PREPARE stmt FROM "INSERT INTO test.t1 VALUES (5, 'This is not secret either')";
86+
--connection default
87+
--source ../include/rewritten_queries.inc
88+
89+
--echo
90+
--echo # Regular prepared statement (execute, in flight)
91+
--echo
92+
--connection con1
93+
--send EXECUTE stmt;
94+
--connection default
95+
--source ../include/rewritten_queries.inc
96+
97+
COMMIT;
98+
--connection con1
99+
--reap
100+
--connection default
101+
--source ../include/rewritten_queries.inc
102+
103+
--echo
104+
--echo # Regular prepared statement (deallocate)
105+
--echo
106+
--connection con1
107+
DEALLOCATE PREPARE stmt;
108+
--connection default
109+
--source ../include/rewritten_queries.inc
110+
111+
--echo
112+
--echo # Sensitive prepared statement (prepare)
113+
--echo
114+
--connection con1
115+
PREPARE stmt FROM "CREATE USER bob IDENTIFIED BY 'thisisapassword'";
116+
--connection default
117+
--source ../include/rewritten_queries.inc
118+
119+
--echo
120+
--echo # Sensitive prepared statement (execute)
121+
--echo
122+
--connection con1
123+
EXECUTE stmt;
124+
--connection default
125+
--source ../include/rewritten_queries.inc
126+
127+
--echo
128+
--echo # Sensitive prepared statement (deallocate)
129+
--echo
130+
--connection con1
131+
DEALLOCATE PREPARE stmt;
132+
--connection default
133+
--source ../include/rewritten_queries.inc
134+
135+
--connection default
136+
--disconnect con1
137+
138+
DROP USER alice;
139+
DROP USER bob;
140+
DROP TABLE test.t1;

sql/sql_prepare.cc

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -324,33 +324,26 @@ class Protocol_local final : public Protocol {
324324
******************************************************************************/
325325

326326
/**
327-
Rewrite the current query (to obfuscate passwords etc.) if needed
328-
(i.e. only if we'll be writing the query to any of our logs).
327+
Rewrite the current query (to obfuscate passwords etc.).
329328
330329
Side-effect: thd->rewritten_query() may be populated with a rewritten
331330
query. If the query is not of a rewritable type,
332331
thd->rewritten_query() will be empty.
333332
334333
@param thd thread handle
335334
*/
336-
static inline void rewrite_query_if_needed(THD *thd) {
337-
bool general =
338-
(opt_general_log && !(opt_general_log_raw || thd->slave_thread));
339-
340-
if ((thd->sp_runtime_ctx == nullptr) &&
341-
(general || opt_slow_log || opt_bin_log)) {
342-
/*
343-
thd->m_rewritten_query may already contain "PREPARE stmt FROM ..."
344-
at this point, so we reset it here so mysql_rewrite_query()
345-
won't complain.
346-
*/
347-
thd->reset_rewritten_query();
348-
/*
349-
Now replace the "PREPARE ..." with the obfuscated version of the
350-
actual query were prepare.
351-
*/
352-
mysql_rewrite_query(thd);
353-
}
335+
static inline void rewrite_query(THD *thd) {
336+
/*
337+
thd->m_rewritten_query may already contain "PREPARE stmt FROM ..."
338+
at this point, so we reset it here so mysql_rewrite_query()
339+
won't complain.
340+
*/
341+
thd->reset_rewritten_query();
342+
/*
343+
Now replace the "PREPARE ..." with the obfuscated version of the
344+
actual query were prepare.
345+
*/
346+
mysql_rewrite_query(thd);
354347
}
355348

356349
/**
@@ -2273,11 +2266,11 @@ bool Execute_sql_statement::execute_server_code(THD *thd) {
22732266
thd->m_statement_psi = nullptr;
22742267

22752268
/*
2276-
Rewrite first (if needed); execution might replace passwords
2269+
Rewrite first, execution might replace passwords
22772270
with hashes in situ without flagging it, and then we'd make
22782271
a hash of that hash.
22792272
*/
2280-
rewrite_query_if_needed(thd);
2273+
rewrite_query(thd);
22812274
log_execute_line(thd);
22822275

22832276
error = mysql_execute_command(thd);
@@ -2635,18 +2628,23 @@ bool Prepared_statement::prepare(THD *thd, const char *query_str,
26352628

26362629
lex_end(m_lex);
26372630

2638-
rewrite_query_if_needed(thd);
2631+
rewrite_query(thd);
2632+
2633+
const char *display_query_string;
2634+
int display_query_length;
26392635

26402636
if (thd->rewritten_query().length()) {
2641-
thd->set_query_for_display(thd->rewritten_query().ptr(),
2642-
thd->rewritten_query().length());
2643-
MYSQL_SET_PS_TEXT(m_prepared_stmt, thd->rewritten_query().ptr(),
2644-
thd->rewritten_query().length());
2637+
display_query_string = thd->rewritten_query().ptr();
2638+
display_query_length = thd->rewritten_query().length();
26452639
} else {
2646-
thd->set_query_for_display(thd->query().str, thd->query().length);
2647-
MYSQL_SET_PS_TEXT(m_prepared_stmt, thd->query().str, thd->query().length);
2640+
display_query_string = thd->query().str;
2641+
display_query_length = thd->query().length;
26482642
}
26492643

2644+
thd->set_query_for_display(display_query_string, display_query_length);
2645+
MYSQL_SET_PS_TEXT(m_prepared_stmt, display_query_string,
2646+
display_query_length);
2647+
26502648
cleanup_stmt(thd);
26512649
stmt_backup.restore_thd(thd, this);
26522650
thd->stmt_arena = old_stmt_arena;
@@ -3636,13 +3634,26 @@ bool Prepared_statement::execute(THD *thd, String *expanded_query,
36363634
- Any passwords in the "Execute" line should be substituted with
36373635
their hashes, or a notice.
36383636
3639-
Rewrite first (if needed); execution might replace passwords
3637+
Rewrite first, execution might replace passwords
36403638
with hashes in situ without flagging it, and then we'd make
36413639
a hash of that hash.
36423640
*/
3643-
rewrite_query_if_needed(thd);
3641+
rewrite_query(thd);
36443642
log_execute_line(thd);
36453643

3644+
const char *display_query_string;
3645+
int display_query_length;
3646+
3647+
if (thd->rewritten_query().length()) {
3648+
display_query_string = thd->rewritten_query().ptr();
3649+
display_query_length = thd->rewritten_query().length();
3650+
} else {
3651+
display_query_string = thd->query().str;
3652+
display_query_length = thd->query().length;
3653+
}
3654+
3655+
mysql_thread_set_info(display_query_string, display_query_length);
3656+
36463657
thd->binlog_need_explicit_defaults_ts =
36473658
m_lex->binlog_need_explicit_defaults_ts;
36483659
resource_group_switched = mgr_ptr->switch_resource_group_if_needed(

sql/sql_rewrite.cc

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ bool rewrite_query(THD *thd, Consumer_type type, const Rewrite_params *params,
325325
326326
The query aimed to be rewritten in the usual log files
327327
(i.e. General, slow query and audit log) uses default value of
328-
type which is Consumer_type::LOG
328+
type which is Consumer_type::TEXTLOG
329329
330330
Side-effects:
331331
@@ -339,18 +339,18 @@ bool rewrite_query(THD *thd, Consumer_type type, const Rewrite_params *params,
339339
340340
@param thd The THD to rewrite for.
341341
@param type Purpose of rewriting the query
342-
Consumer_type::LOG
342+
Consumer_type::TEXTLOG
343343
To rewrite the query either for general, slow query
344344
and audit log.
345345
Consumer_type::BINLOG
346346
To rewrite the query for binlogs.
347-
Consumer_type::CONSOLE
347+
Consumer_type::STDOUT
348348
To rewrite the query for standard output.
349349
@param params Wrapper object of parameters in case needed by a SQL
350350
rewriter.
351351
*/
352-
void mysql_rewrite_query(THD *thd, Consumer_type type /*= Consumer_type::LOG */,
353-
const Rewrite_params *params /*= nullptr*/) {
352+
void mysql_rewrite_query(THD *thd, Consumer_type type,
353+
const Rewrite_params *params) {
354354
String rlb;
355355

356356
DBUG_TRACE;
@@ -373,12 +373,12 @@ void mysql_rewrite_query(THD *thd, Consumer_type type /*= Consumer_type::LOG */,
373373
@param rlb Buffer to return rewritten query in (if any) if
374374
do_ps_instrument is false.
375375
@param type Purpose of rewriting the query
376-
Consumer_type::LOG
376+
Consumer_type::TEXTLOG
377377
To rewrite the query either for general, slow query
378378
and audit log.
379379
Consumer_type::BINLOG
380380
To rewrite the query for binlogs.
381-
Consumer_type::CONSOLE
381+
Consumer_type::STDOUT
382382
To rewrite the query for standard output.
383383
@param params Wrapper object of parameters in case needed by a SQL
384384
rewriter.
@@ -392,8 +392,18 @@ void mysql_rewrite_acl_query(THD *thd, String &rlb, Consumer_type type,
392392
if (rewrite_query(thd, type, params, rlb) && (rlb.length() > 0) &&
393393
do_ps_instrument) {
394394
thd->swap_rewritten_query(rlb);
395-
thd->set_query_for_display(thd->rewritten_query().ptr(),
396-
thd->rewritten_query().length());
395+
396+
/*
397+
Queries rewritten for Consumer_type::BINLOG may contain
398+
sensitive informations, encoded in the binlog event.
399+
Do not print these.
400+
A subsequent call, using Consumer_type::TEXTLOG,
401+
will display a proper sanitized query.
402+
*/
403+
if (type != Consumer_type::BINLOG) {
404+
thd->set_query_for_display(thd->rewritten_query().ptr(),
405+
thd->rewritten_query().length());
406+
}
397407
/*
398408
rlb now contains the previous rewritten query.
399409
We clear it here both to save memory and to prevent possible confusion.

0 commit comments

Comments
 (0)