Skip to content

Commit 9cb3e5d

Browse files
committed
Bug#22136709 INFINITE RECURSION BY CALLING MY_MESSAGE FROM MYSQL_AUDIT_GENERAL_CLASS HANDLER
Problem: ======== Calling my_message from within an audit plugin during handling MYSQL_AUDIT_GENERAL_ERROR event causes the audit error event generating mechanism to fall into infinite recursion, which ends with the server crash. Fix: ==== A condition has been embedded into event generating mechanism that stops infinite recursion, when the stack size shrinks to a certain value. This fix does not prevent infinite recursion but stops it instead. The server does not crash and the ER_STACK_OVERRUN_NEED_MORE error is generated. null_audit_event_order_check_consume_ignore_count global variable was introduced that prevents clearing null_audit_event_order_check variable certain number of times. This allows to simulate infinite and non-infinite recursion during testing. Reviewed-by: ============ Georgi Kodinov <georgi.kodinov@oracle.com> Ramil Kalimullin <ramil.kalimullin@oracle.com> Evgeny Potemkin <evgeny.potemkin@oracle.com>
1 parent 446b3ee commit 9cb3e5d

File tree

7 files changed

+165
-21
lines changed

7 files changed

+165
-21
lines changed

mysql-test/r/audit_plugin_bugs.result

+27
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,30 @@
77
# Load the plugin at startup and abort on STARTUP event with custom message
88
# Search for custom abort message
99
# Startup the server
10+
#
11+
# Bug #22136709: INFINITE RECURSION BY CALLING MY_MESSAGE FROM
12+
# MYSQL_AUDIT_GENERAL_CLASS HANDLER
13+
INSTALL PLUGIN null_audit SONAME 'adt_null.so';
14+
SET @@GLOBAL.null_audit_event_order_check= "MYSQL_AUDIT_GENERAL_ERROR;;ABORT_RET";
15+
SET @@GLOBAL.null_audit_abort_message= "Abort message.";
16+
SET @@GLOBAL.null_audit_event_order_check_consume_ignore_count= 2;
17+
connect(localhost,wrong_root,,test,MASTER_PORT,MASTER_SOCKET);
18+
ERROR HY000: Abort message.
19+
SET @@GLOBAL.null_audit_event_order_check_consume_ignore_count= 10000;
20+
connect(localhost,wrong_root,,test,MASTER_PORT,MASTER_SOCKET);
21+
SET @@GLOBAL.null_audit_event_order_check= NULL;
22+
SET @@GLOBAL.null_audit_abort_message= NULL;
23+
SET @@GLOBAL.null_audit_event_order_check_consume_ignore_count= 0;
24+
# test
25+
SET @@null_audit_event_order_check= "MYSQL_AUDIT_GENERAL_ERROR;;ABORT_RET";
26+
SET @@null_audit_abort_message= "Abort message.";
27+
SET @@null_audit_event_order_check_consume_ignore_count= 10000;
28+
# Must not crash
29+
SELECT 1 FROM mysql.fictional_table;
30+
ERROR HY000: Thread stack overrun: <NUMBER> used of a <NUMBER> stack, and <NUMBER> needed. Use 'mysqld --thread_stack=#' to specify a bigger stack.
31+
# Clean up
32+
End of 5.7 tests
33+
# cleanup
34+
UNINSTALL PLUGIN null_audit;
35+
Warnings:
36+
Warning 1620 Plugin is busy and will be uninstalled on shutdown

mysql-test/t/audit_plugin_bugs.test

+58
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,61 @@ let SEARCH_PATTERN= \[ERROR\] Abort message custom;
5454
--enable_reconnect
5555
--source include/wait_until_connected_again.inc
5656
--disable_reconnect
57+
58+
--echo #
59+
--echo # Bug #22136709: INFINITE RECURSION BY CALLING MY_MESSAGE FROM
60+
--echo # MYSQL_AUDIT_GENERAL_CLASS HANDLER
61+
62+
--replace_regex /\.dll/.so/
63+
eval INSTALL PLUGIN null_audit SONAME '$AUDIT_NULL';
64+
65+
# Save the initial number of concurrent sessions
66+
--source include/count_sessions.inc
67+
68+
SET @@GLOBAL.null_audit_event_order_check= "MYSQL_AUDIT_GENERAL_ERROR;;ABORT_RET";
69+
SET @@GLOBAL.null_audit_abort_message= "Abort message.";
70+
71+
# 3 recursive my_message calls should not cause stack overrun
72+
SET @@GLOBAL.null_audit_event_order_check_consume_ignore_count= 2;
73+
--replace_result $MASTER_MYSOCK MASTER_SOCKET $MASTER_MYPORT MASTER_PORT
74+
--error ER_AUDIT_API_ABORT
75+
connect(user1_con,localhost,wrong_root,);
76+
77+
# Infinite my_message calls cause stack overrun
78+
SET @@GLOBAL.null_audit_event_order_check_consume_ignore_count= 10000;
79+
--replace_result $MASTER_MYSOCK MASTER_SOCKET $MASTER_MYPORT MASTER_PORT
80+
--disable_result_log
81+
--error ER_STACK_OVERRUN_NEED_MORE
82+
connect(user1_con,localhost,wrong_root,);
83+
--enable_result_log
84+
85+
# Clean up global variables
86+
SET @@GLOBAL.null_audit_event_order_check= NULL;
87+
SET @@GLOBAL.null_audit_abort_message= NULL;
88+
# Let's hope 10000 calls will overrun the stack frame
89+
SET @@GLOBAL.null_audit_event_order_check_consume_ignore_count= 0;
90+
91+
--echo # test
92+
connect(user1_con,localhost,root,);
93+
94+
SET @@null_audit_event_order_check= "MYSQL_AUDIT_GENERAL_ERROR;;ABORT_RET";
95+
SET @@null_audit_abort_message= "Abort message.";
96+
# Let's hope 10000 calls will overrun the stack frame
97+
SET @@null_audit_event_order_check_consume_ignore_count= 10000;
98+
99+
--echo # Must not crash
100+
--replace_regex /[0-9]+ byte[s]?/<NUMBER>/
101+
--error ER_STACK_OVERRUN_NEED_MORE
102+
SELECT 1 FROM mysql.fictional_table;
103+
104+
--echo # Clean up
105+
connection default;
106+
disconnect user1_con;
107+
108+
# Wait till we reached the initial number of concurrent sessions
109+
--source include/wait_until_count_sessions.inc
110+
111+
--echo End of 5.7 tests
112+
113+
--echo # cleanup
114+
UNINSTALL PLUGIN null_audit;

plugin/audit_null/audit_null.c

+44-17
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,12 @@ static MYSQL_THDVAR_STR(event_order_check,
127127
PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_MEMALLOC,
128128
"Event order check string", NULL, NULL, NULL);
129129

130+
static MYSQL_THDVAR_UINT(event_order_check_consume_ignore_count,
131+
PLUGIN_VAR_RQCMDARG,
132+
"Do not consume event order string specified "
133+
"number of times.",
134+
NULL, NULL, 0, 0, UINT_MAX, 1);
135+
130136
static MYSQL_THDVAR_INT(event_order_started,
131137
PLUGIN_VAR_RQCMDARG,
132138
"Plugin is in the event order check.",
@@ -316,7 +322,8 @@ static void process_event_record(MYSQL_THD thd, LEX_CSTRING event_name,
316322
}
317323
}
318324

319-
static int process_command(MYSQL_THD thd, LEX_CSTRING event_command)
325+
static int process_command(MYSQL_THD thd, LEX_CSTRING event_command,
326+
my_bool consume_event)
320327
{
321328
LEX_CSTRING abort_ret_command= { C_STRING_WITH_LEN("ABORT_RET") };
322329

@@ -334,11 +341,15 @@ static int process_command(MYSQL_THD thd, LEX_CSTRING event_command)
334341
lex_cstring_set(&order_cstr,
335342
(const char *)THDVAR(thd, event_order_check));
336343

337-
memmove((char *)order_cstr.str,
338-
(void *)status.str, status.length + 1);
344+
/* Do not replace order string yet. */
345+
if (consume_event)
346+
{
347+
memmove((char *) order_cstr.str,
348+
(void *) status.str, status.length + 1);
339349

340-
THDVAR(thd, abort_value) = 1;
341-
THDVAR(thd, abort_message) = 0;
350+
THDVAR(thd, abort_value)= 1;
351+
THDVAR(thd, abort_message)= 0;
352+
}
342353

343354
if (err_message)
344355
{
@@ -376,6 +387,7 @@ static int audit_null_notify(MYSQL_THD thd,
376387
LEX_CSTRING event_token= get_token(&order_str);
377388
LEX_CSTRING event_data= get_token(&order_str);
378389
LEX_CSTRING event_command= get_token(&order_str);
390+
my_bool consume_event= TRUE;
379391

380392
/* prone to races, oh well */
381393
number_of_calls++;
@@ -674,31 +686,45 @@ static int audit_null_notify(MYSQL_THD thd,
674686
else
675687
{
676688
LEX_CSTRING order_cstr;
689+
ulong consume= THDVAR(thd, event_order_check_consume_ignore_count);
677690
lex_cstring_set(&order_cstr,
678691
(const char *)THDVAR(thd, event_order_check));
679692

680693
THDVAR(thd, event_order_started)= 1;
681694

682-
memmove((char*)order_cstr.str, (void*)order_str,
683-
order_cstr.length - (order_str - order_cstr.str) + 1);
695+
if (consume)
696+
{
697+
/*
698+
Do not consume event this time. Just decrease value and wait until
699+
the next event is matched.
700+
*/
701+
THDVAR(thd, event_order_check_consume_ignore_count)= consume - 1;
702+
consume_event= FALSE;
703+
}
704+
else
705+
{
706+
/* Consume matched event. */
707+
memmove((char*)order_cstr.str, (void*)order_str,
708+
order_cstr.length - (order_str - order_cstr.str) + 1);
684709

685-
/* Count new length. */
686-
lex_cstring_set(&order_cstr, order_cstr.str);
710+
/* Count new length. */
711+
lex_cstring_set(&order_cstr, order_cstr.str);
687712

688-
if (order_cstr.length == 0)
689-
{
690-
LEX_CSTRING status = { C_STRING_WITH_LEN("EVENT-ORDER-OK") };
713+
if (order_cstr.length == 0)
714+
{
715+
LEX_CSTRING status = { C_STRING_WITH_LEN("EVENT-ORDER-OK") };
691716

692-
memmove((char *)order_cstr.str,
693-
(void *)status.str, status.length + 1);
717+
memmove((char *)order_cstr.str,
718+
(void *)status.str, status.length + 1);
694719

695-
/* event_order_started contains message. Do not verify it. */
696-
THDVAR(thd, event_order_started)= 0;
720+
/* event_order_started contains message. Do not verify it. */
721+
THDVAR(thd, event_order_started)= 0;
722+
}
697723
}
698724
}
699725
}
700726

701-
return process_command(thd, event_command);
727+
return process_command(thd, event_command, consume_event);
702728
}
703729

704730
/*
@@ -729,6 +755,7 @@ static struct st_mysql_sys_var* system_variables[] = {
729755
MYSQL_SYSVAR(abort_value),
730756

731757
MYSQL_SYSVAR(event_order_check),
758+
MYSQL_SYSVAR(event_order_check_consume_ignore_count),
732759
MYSQL_SYSVAR(event_order_started),
733760
MYSQL_SYSVAR(event_order_check_exact),
734761

sql/auth/sql_authentication.cc

+5-1
Original file line numberDiff line numberDiff line change
@@ -2201,7 +2201,8 @@ acl_authenticate(THD *thd, enum_server_command command)
22012201
acl_log_connect(mpvio.auth_info.user_name, mpvio.auth_info.host_or_ip,
22022202
mpvio.auth_info.authenticated_as, mpvio.db.str, thd, command);
22032203
}
2204-
if (!mpvio.can_authenticate() && res == CR_OK)
2204+
if (res == CR_OK &&
2205+
(!mpvio.can_authenticate() || thd->is_error()))
22052206
{
22062207
res= CR_ERROR;
22072208
}
@@ -2259,6 +2260,9 @@ acl_authenticate(THD *thd, enum_server_command command)
22592260
mpvio.auth_info.authenticated_as, mpvio.db.str, thd, command);
22602261
}
22612262

2263+
if (thd->is_error())
2264+
DBUG_RETURN(1);
2265+
22622266
if (is_proxy_user)
22632267
{
22642268
ACL_USER *acl_proxy_user;

sql/mysqld.cc

+2-1
Original file line numberDiff line numberDiff line change
@@ -2215,7 +2215,8 @@ void my_message_sql(uint error, const char *str, myf MyFlags)
22152215
}
22162216

22172217
#ifndef EMBEDDED_LIBRARY
2218-
mysql_audit_general(thd, MYSQL_AUDIT_GENERAL_ERROR, error, str);
2218+
if (error != ER_STACK_OVERRUN_NEED_MORE)
2219+
mysql_audit_general(thd, MYSQL_AUDIT_GENERAL_ERROR, error, str);
22192220
#endif
22202221

22212222
if (thd)

sql/sql_audit.cc

+14
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "sql_thd_internal_api.h" // create_thd / destroy_thd
2222
#include "sql_plugin.h" // my_plugin_foreach
2323
#include "sql_rewrite.h" // mysql_rewrite_query
24+
#include "sql_parse.h" // check_stack_overrun
2425

2526
/**
2627
@class Audit_error_handler
@@ -1259,6 +1260,19 @@ static int event_class_dispatch(THD *thd, mysql_event_class_t event_class,
12591260
{
12601261
plugin_ref *plugins, *plugins_last;
12611262

1263+
/*
1264+
Does not allow infinite recursive calls that crash the server.
1265+
This happens when error is reported from within a plugin that already
1266+
is receiving error event (MYSQL_AUDIT_GENERAL_ERROR). This condition
1267+
breaks the recursion, when the stack size gets close to its minimal
1268+
value.
1269+
*/
1270+
if (check_stack_overrun(thd, STACK_MIN_SIZE * 5,
1271+
reinterpret_cast<uchar *>(&event_generic)))
1272+
{
1273+
return 0;
1274+
}
1275+
12621276
/* Use the cached set of audit plugins */
12631277
plugins= thd->audit_class_plugins.begin();
12641278
plugins_last= thd->audit_class_plugins.end();

sql/srv_session_service.cc

+15-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
1+
/* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
22
33
This program is free software; you can redistribute it and/or
44
modify it under the terms of the GNU General Public License as
@@ -102,11 +102,24 @@ Srv_session* srv_session_open(srv_session_error_cb error_cb, void *plugin_ctx)
102102
}
103103
else
104104
{
105-
if (session->open())
105+
THD *current= current_thd;
106+
THD *stack_thd= session->get_thd();
107+
108+
session->get_thd()->thread_stack= reinterpret_cast<char *>(&stack_thd);
109+
session->get_thd()->store_globals();
110+
111+
bool result= session->open();
112+
113+
session->get_thd()->restore_globals();
114+
115+
if (result)
106116
{
107117
delete session;
108118
session= NULL;
109119
}
120+
121+
if (current)
122+
current->store_globals();
110123
}
111124
DBUG_RETURN(session);
112125
}

0 commit comments

Comments
 (0)