Skip to content

Commit 78cdbb0

Browse files
authored
Merge pull request #3602 from sysown/v2.2.1-3601
Closes #3601: Backport bugfixes to v2.2.1 - 4
2 parents d374707 + f225565 commit 78cdbb0

6 files changed

+717
-14
lines changed

lib/MySQL_Protocol.cpp

+23-12
Original file line numberDiff line numberDiff line change
@@ -2455,8 +2455,19 @@ stmt_execute_metadata_t * MySQL_Protocol::get_binds_from_pkt(void *ptr, unsigned
24552455
uint8_t null_byte=null_bitmap[i/8];
24562456
uint8_t idx=i%8;
24572457
my_bool is_null = (null_byte & ( 1 << idx )) >> idx;
2458+
if (binds[i].buffer_type == MYSQL_TYPE_NULL)
2459+
is_null = 1;
24582460
is_nulls[i]=is_null;
24592461
binds[i].is_null=&is_nulls[i];
2462+
// set length, defaults to 0
2463+
// for parameters with not fixed length, that will be assigned later
2464+
// we moved this initialization here due to #3585
2465+
binds[i].is_unsigned=0;
2466+
lengths[i]=0;
2467+
binds[i].length=&lengths[i];
2468+
// NOTE: We nullify buffers here to reflect that memory wasn't
2469+
// initalized. See #3546.
2470+
binds[i].buffer = NULL;
24602471
}
24612472
free(null_bitmap); // we are done with it
24622473

@@ -2475,10 +2486,6 @@ stmt_execute_metadata_t * MySQL_Protocol::get_binds_from_pkt(void *ptr, unsigned
24752486
binds[i].buffer_type=(enum enum_field_types)buffer_type;
24762487
p+=2;
24772488

2478-
// set length, defaults to 0
2479-
// for parameters with not fixed length, that will be assigned later
2480-
lengths[i]=0;
2481-
binds[i].length=&lengths[i];
24822489
}
24832490
}
24842491

@@ -2527,11 +2534,13 @@ stmt_execute_metadata_t * MySQL_Protocol::get_binds_from_pkt(void *ptr, unsigned
25272534
p++;
25282535
MYSQL_TIME ts;
25292536
memset(&ts,0,sizeof(MYSQL_TIME));
2530-
memcpy(&ts.neg,p,1);
2531-
memcpy(&ts.day,p+1,4);
2532-
memcpy(&ts.hour,p+5,1);
2533-
memcpy(&ts.minute,p+6,1);
2534-
memcpy(&ts.second,p+7,1);
2537+
if (l) {
2538+
memcpy(&ts.neg,p,1);
2539+
memcpy(&ts.day,p+1,4);
2540+
memcpy(&ts.hour,p+5,1);
2541+
memcpy(&ts.minute,p+6,1);
2542+
memcpy(&ts.second,p+7,1);
2543+
}
25352544
if (l>8) {
25362545
memcpy(&ts.second_part,p+8,4);
25372546
}
@@ -2549,9 +2558,11 @@ stmt_execute_metadata_t * MySQL_Protocol::get_binds_from_pkt(void *ptr, unsigned
25492558
p++;
25502559
MYSQL_TIME ts;
25512560
memset(&ts,0,sizeof(MYSQL_TIME));
2552-
memcpy(&ts.year,p,2);
2553-
memcpy(&ts.month,p+2,1);
2554-
memcpy(&ts.day,p+3,1);
2561+
if (l) {
2562+
memcpy(&ts.year,p,2);
2563+
memcpy(&ts.month,p+2,1);
2564+
memcpy(&ts.day,p+3,1);
2565+
}
25552566
if (l>4) {
25562567
memcpy(&ts.hour,p+4,1);
25572568
memcpy(&ts.minute,p+5,1);

lib/MySQL_Session.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -3951,6 +3951,9 @@ void MySQL_Session::handler_rc0_PROCESSING_STMT_EXECUTE(MySQL_Data_Stream *myds)
39513951
(buffer_type == MYSQL_TYPE_DATETIME)
39523952
) {
39533953
free(CurrentQuery.stmt_meta->binds[i].buffer);
3954+
// NOTE: This memory should be zeroed during initialization,
3955+
// but we also nullify it here for extra safety. See #3546.
3956+
CurrentQuery.stmt_meta->binds[i].buffer = NULL;
39543957
}
39553958
}
39563959
}

lib/ProxySQL_RESTAPI_Server.cpp

+6-1
Original file line numberDiff line numberDiff line change
@@ -663,7 +663,12 @@ class gen_get_endpoint : public http_resource {
663663

664664
void * restapi_server_thread(void *arg) {
665665
httpserver::webserver * ws = (httpserver::webserver *)arg;
666-
ws->start(true);
666+
try {
667+
ws->start(true);
668+
} catch (const std::exception& ex) {
669+
proxy_error("Exception starting 'RESTAPI Server': %s\n", ex.what());
670+
throw;
671+
}
667672
return NULL;
668673
}
669674

lib/mysql_connection.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -1003,7 +1003,7 @@ MDB_ASYNC_ST MySQL_Connection::handler(short event) {
10031003
}
10041004
if (!ret_mysql) {
10051005
// always increase the counter
1006-
proxy_error("Failed to mysql_real_connect() on %s:%d , FD (Conn:%d , MyDS:%d) , %d: %s.\n", parent->address, parent->port, mysql->net.fd , myds->fd, mysql_errno(mysql), mysql_error(mysql));
1006+
proxy_error("Failed to mysql_real_connect() on %u:%s:%d , FD (Conn:%d , MyDS:%d) , %d: %s.\n", parent->myhgc->hid, parent->address, parent->port, mysql->net.fd , myds->fd, mysql_errno(mysql), mysql_error(mysql));
10071007
NEXT_IMMEDIATE(ASYNC_CONNECT_FAILED);
10081008
} else {
10091009
NEXT_IMMEDIATE(ASYNC_CONNECT_SUCCESSFUL);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
/**
2+
* @file reg_test_3546-stmt_empty_params-t.cpp
3+
* @brief This test is a regression test for exercising the code path that lead
4+
* to issue #3546. It's not meant to test a specific feature, but to server as
5+
* a regression test that should flag the issue under a memory analyzer.
6+
* @details Memory corruption related to #3546 was double-free provoqued when a
7+
* prepared statement with param of types ['MYSQL_TYPE_DATE'|'MYSQL_TYPE_TIMESTAMP'|'MYSQL_TYPE_DATETIME'|'MYSQL_TYPE_TIME'],
8+
* was prepared and a later prepared with 'NULL' parameters. Because the memory
9+
* for the buffered was not zeroed neither at initialization or during the later
10+
* `free` a corruption takes place during the second execution.
11+
*/
12+
13+
#include <iostream>
14+
#include <chrono>
15+
#include <ctime>
16+
#include <cstring>
17+
#include <unistd.h>
18+
#include <time.h>
19+
#include <vector>
20+
#include <string>
21+
#include <stdio.h>
22+
23+
#include <mysql.h>
24+
#include <mysql/mysqld_error.h>
25+
26+
#include "proxysql_utils.h"
27+
#include "tap.h"
28+
#include "command_line.h"
29+
#include "utils.h"
30+
#include "errno.h"
31+
32+
/**
33+
* @brief String size of the columns created for the testing table.
34+
*/
35+
const int STRING_SIZE=32;
36+
/**
37+
* @brief Number of iterations to perform.
38+
*/
39+
const uint32_t ITERATIONS = 100;
40+
/**
41+
* @brief Id for the current writer hostgroup.
42+
*/
43+
const uint32_t WRITER_HOSTGROUP_ID = 0;
44+
45+
int prepare_stmt(
46+
MYSQL* proxysql_mysql, MYSQL_STMT* stmt, MYSQL_TIME* ts, my_bool* is_null
47+
) {
48+
int res = EXIT_SUCCESS;
49+
std::string query {
50+
"SELECT /*+ ;hostgroup=0 */ id,c1,c2 FROM test.reg_test_3546 WHERE date IN (?)"
51+
};
52+
53+
if (mysql_stmt_prepare(stmt, query.c_str(), strlen(query.c_str()))) {
54+
diag("mysql_stmt_prepare at line %d failed: %s", __LINE__ , mysql_error(proxysql_mysql));
55+
mysql_close(proxysql_mysql);
56+
res = EXIT_FAILURE;
57+
goto exit;
58+
}
59+
60+
MYSQL_BIND bind_params;
61+
62+
memset(&bind_params, 0, sizeof(MYSQL_BIND));
63+
bind_params.buffer_type= MYSQL_TYPE_DATE;
64+
bind_params.buffer= ts;
65+
bind_params.is_null= is_null;
66+
bind_params.length= 0;
67+
68+
if (mysql_stmt_bind_param(stmt, &bind_params)) {
69+
diag(
70+
"mysql_stmt_bind_result at line %d failed: %s", __LINE__ ,
71+
mysql_stmt_error(stmt)
72+
);
73+
res = EXIT_FAILURE;
74+
goto exit;
75+
}
76+
77+
exit:
78+
return res;
79+
}
80+
81+
int main(int argc, char** argv) {
82+
83+
CommandLine cl;
84+
85+
plan(ITERATIONS);
86+
87+
if (cl.getEnv()) {
88+
diag("Failed to get the required environmental variables.");
89+
return -1;
90+
}
91+
92+
MYSQL_STMT* stmt_param = nullptr;
93+
MYSQL* proxysql_mysql = mysql_init(NULL);
94+
MYSQL* proxysql_admin = mysql_init(NULL);
95+
96+
if (!mysql_real_connect(proxysql_mysql, cl.host, cl.username, cl.password, NULL, cl.port, NULL, 0)) {
97+
fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_mysql));
98+
return -1;
99+
}
100+
101+
if (!mysql_real_connect(proxysql_admin, cl.host, cl.admin_username, cl.admin_password, NULL, cl.admin_port, NULL, 0)) {
102+
fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_admin));
103+
return -1;
104+
}
105+
106+
stmt_param = mysql_stmt_init(proxysql_mysql);
107+
if (!stmt_param) {
108+
diag("mysql_stmt_init(), out of memory");
109+
goto exit;
110+
}
111+
112+
// Insert the row to be queried with the prepared statement.
113+
// *************************************************************************
114+
MYSQL_QUERY(proxysql_mysql, "CREATE DATABASE IF NOT EXISTS test");
115+
MYSQL_QUERY(proxysql_mysql, "DROP TABLE IF EXISTS test.reg_test_3546");
116+
MYSQL_QUERY(
117+
proxysql_mysql,
118+
"CREATE TABLE IF NOT EXISTS test.reg_test_3546"
119+
" (id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, `c1` BIGINT, `c2` varchar(32), `date` DATE)"
120+
);
121+
MYSQL_QUERY(proxysql_mysql, "INSERT INTO test.reg_test_3546(c1, c2, date) VALUES (100, 'abcde', '2009-01-01')");
122+
mysql_close(proxysql_mysql);
123+
124+
// *************************************************************************
125+
126+
// Initialize the connection again
127+
proxysql_mysql = mysql_init(NULL);
128+
129+
if (!mysql_real_connect(proxysql_mysql, cl.host, cl.username, cl.password, NULL, cl.port, NULL, 0)) {
130+
fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_mysql));
131+
return -1;
132+
}
133+
134+
{
135+
// Set the number of maximum connections for servers in the writer hostgroup
136+
std::string t_update_mysql_servers {
137+
"UPDATE mysql_servers SET max_connections=1 WHERE hostgroup_id=%d"
138+
};
139+
std::string update_mysql_queries {};
140+
string_format(t_update_mysql_servers, update_mysql_queries, WRITER_HOSTGROUP_ID);
141+
MYSQL_QUERY(proxysql_admin, update_mysql_queries.c_str());
142+
MYSQL_QUERY(proxysql_admin, "LOAD MYSQL SERVERS TO RUNTIME");
143+
144+
MYSQL_TIME ts;
145+
char data_param[STRING_SIZE] = {};
146+
my_bool is_null = 0;
147+
148+
if (prepare_stmt(proxysql_mysql, stmt_param, &ts, &is_null)) {
149+
diag("'prepare_stmt' at line %d failed", __LINE__);
150+
goto exit;
151+
}
152+
153+
// Prepare parameters
154+
ts.year = 2009;
155+
ts.month = 1;
156+
ts.day = 1;
157+
158+
for (uint32_t i = 0; i < ITERATIONS; i++) {
159+
if (i % 2) {
160+
is_null = 0;
161+
} else {
162+
is_null = 1;
163+
}
164+
165+
if (mysql_stmt_execute(stmt_param)) {
166+
diag(
167+
"'mysql_stmt_execute' at line %d failed: %s", __LINE__ ,
168+
mysql_stmt_error(stmt_param)
169+
);
170+
goto exit;
171+
}
172+
173+
MYSQL_BIND bind[3];
174+
memset(bind, 0, sizeof(bind));
175+
176+
int data_id = 0;
177+
int64_t data_c1 = 0;
178+
char data_c2[STRING_SIZE] { 0 };
179+
char is_null[3] { 0 };
180+
long unsigned int length[3] { 0 };
181+
char error[3] { 0 };
182+
183+
bind[0].buffer_type = MYSQL_TYPE_LONG;
184+
bind[0].buffer = (char *)&data_id;
185+
bind[0].buffer_length = sizeof(int);
186+
bind[0].is_null = &is_null[0];
187+
bind[0].length = &length[0];
188+
189+
bind[1].buffer_type = MYSQL_TYPE_LONGLONG;
190+
bind[1].buffer = (char *)&data_c1;
191+
bind[1].buffer_length = sizeof(int64_t);
192+
bind[1].is_null = &is_null[1];
193+
bind[1].length = &length[1];
194+
195+
bind[2].buffer_type = MYSQL_TYPE_STRING;
196+
bind[2].buffer = (char *)&data_c2;
197+
bind[2].buffer_length = STRING_SIZE;
198+
bind[2].is_null = &is_null[2];
199+
bind[2].length = &length[2];
200+
bind[2].error = &error[2];
201+
202+
if (mysql_stmt_bind_result(stmt_param, bind)) {
203+
diag(
204+
"mysql_stmt_bind_result at line %d failed: %s", __LINE__,
205+
mysql_stmt_error(stmt_param)
206+
);
207+
goto exit;
208+
}
209+
210+
int fetch_result = mysql_stmt_fetch(stmt_param);
211+
if (fetch_result == 1) {
212+
diag(
213+
"mysql_stmt_fetch at line %d failed: %s", __LINE__,
214+
mysql_stmt_error(stmt_param)
215+
);
216+
goto exit;
217+
}
218+
219+
if (i % 2) {
220+
bool data_match_expected =
221+
(data_id == static_cast<int64_t>(1)) &&
222+
(data_c1 == static_cast<int64_t>(100)) &&
223+
(strcmp(data_c2, "abcde") == 0);
224+
225+
ok(
226+
data_match_expected,
227+
"Prepared statement SELECT result *SHOULD* match expected -"
228+
" Exp=(id:1, c1:100, c2:'abcde'), Act=(id:%d, c1:%ld, c2:'%s')",
229+
data_id,
230+
data_c1,
231+
data_c2
232+
);
233+
} else {
234+
bool data_match_expected =
235+
(data_id == static_cast<int64_t>(0)) &&
236+
(data_c1 == static_cast<int64_t>(0)) &&
237+
(strcmp(data_c2, "") == 0);
238+
239+
ok(
240+
data_match_expected,
241+
"Prepared statement SELECT result *SHOULD* match expected -"
242+
" Exp=(id:0, c1:0, c2:''), Act=(id:%d, c1:%ld, c2:'%s')",
243+
data_id,
244+
data_c1,
245+
data_c2
246+
);
247+
}
248+
}
249+
}
250+
251+
exit:
252+
if (stmt_param) { mysql_stmt_close(stmt_param); }
253+
mysql_close(proxysql_mysql);
254+
mysql_close(proxysql_admin);
255+
256+
return exit_status();
257+
}

0 commit comments

Comments
 (0)