Skip to content

Commit 2af8e67

Browse files
committed
Merge pull request #697 from sodabrew/stmt_close_crash
Avoid crashing when Statement#close is called before a Result is garbage collected
2 parents 4ec5e84 + ed5613d commit 2af8e67

File tree

4 files changed

+32
-26
lines changed

4 files changed

+32
-26
lines changed

ext/mysql2/client.c

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ static VALUE rb_raise_mysql2_error(mysql_client_wrapper *wrapper) {
133133

134134
static void *nogvl_init(void *ptr) {
135135
MYSQL *client;
136-
mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr;
136+
mysql_client_wrapper *wrapper = ptr;
137137

138138
/* may initialize embedded server and read /etc/services off disk */
139139
client = mysql_init(wrapper->client);
@@ -224,7 +224,7 @@ static void *nogvl_close(void *ptr) {
224224

225225
/* this is called during GC */
226226
static void rb_mysql_client_free(void *ptr) {
227-
mysql_client_wrapper *wrapper = (mysql_client_wrapper *)ptr;
227+
mysql_client_wrapper *wrapper = ptr;
228228
decr_mysql2_client(wrapper);
229229
}
230230

@@ -437,10 +437,9 @@ static void *nogvl_read_query_result(void *ptr) {
437437
}
438438

439439
static void *nogvl_do_result(void *ptr, char use_result) {
440-
mysql_client_wrapper *wrapper;
440+
mysql_client_wrapper *wrapper = ptr;
441441
MYSQL_RES *result;
442442

443-
wrapper = (mysql_client_wrapper *)ptr;
444443
if (use_result) {
445444
result = mysql_use_result(wrapper->client);
446445
} else {
@@ -533,14 +532,13 @@ static VALUE disconnect_and_raise(VALUE self, VALUE error) {
533532
}
534533

535534
static VALUE do_query(void *args) {
536-
struct async_query_args *async_args;
535+
struct async_query_args *async_args = args;
537536
struct timeval tv;
538-
struct timeval* tvp;
537+
struct timeval *tvp;
539538
long int sec;
540539
int retval;
541540
VALUE read_timeout;
542541

543-
async_args = (struct async_query_args *)args;
544542
read_timeout = rb_iv_get(async_args->self, "@read_timeout");
545543

546544
tvp = NULL;
@@ -578,11 +576,9 @@ static VALUE do_query(void *args) {
578576
}
579577
#else
580578
static VALUE finish_and_mark_inactive(void *args) {
581-
VALUE self;
579+
VALUE self = args;
582580
MYSQL_RES *result;
583581

584-
self = (VALUE)args;
585-
586582
GET_CLIENT(self);
587583

588584
if (!NIL_P(wrapper->active_thread)) {

ext/mysql2/result.c

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ static rb_encoding *binaryEncoding;
4848
#define MYSQL2_MIN_TIME 62171150401ULL
4949
#endif
5050

51-
#define GET_RESULT(obj) \
51+
#define GET_RESULT(self) \
5252
mysql2_result_wrapper *wrapper; \
5353
Data_Get_Struct(self, mysql2_result_wrapper, wrapper);
5454

@@ -91,16 +91,18 @@ static void rb_mysql_result_free_result(mysql2_result_wrapper * wrapper) {
9191

9292
if (wrapper->resultFreed != 1) {
9393
if (wrapper->stmt_wrapper) {
94-
mysql_stmt_free_result(wrapper->stmt_wrapper->stmt);
95-
96-
/* MySQL BUG? If the statement handle was previously used, and so
97-
* mysql_stmt_bind_result was called, and if that result set and bind buffers were freed,
98-
* MySQL still thinks the result set buffer is available and will prefetch the
99-
* first result in mysql_stmt_execute. This will corrupt or crash the program.
100-
* By setting bind_result_done back to 0, we make MySQL think that a result set
101-
* has never been bound to this statement handle before to prevent the prefetch.
102-
*/
103-
wrapper->stmt_wrapper->stmt->bind_result_done = 0;
94+
if (!wrapper->stmt_wrapper->closed) {
95+
mysql_stmt_free_result(wrapper->stmt_wrapper->stmt);
96+
97+
/* MySQL BUG? If the statement handle was previously used, and so
98+
* mysql_stmt_bind_result was called, and if that result set and bind buffers were freed,
99+
* MySQL still thinks the result set buffer is available and will prefetch the
100+
* first result in mysql_stmt_execute. This will corrupt or crash the program.
101+
* By setting bind_result_done back to 0, we make MySQL think that a result set
102+
* has never been bound to this statement handle before to prevent the prefetch.
103+
*/
104+
wrapper->stmt_wrapper->stmt->bind_result_done = 0;
105+
}
104106

105107
if (wrapper->result_buffers) {
106108
unsigned int i;
@@ -855,6 +857,10 @@ static VALUE rb_mysql_result_each(int argc, VALUE * argv, VALUE self) {
855857

856858
GET_RESULT(self);
857859

860+
if (wrapper->stmt_wrapper && wrapper->stmt_wrapper->closed) {
861+
rb_raise(cMysql2Error, "Statement handle already closed");
862+
}
863+
858864
defaults = rb_iv_get(self, "@query_options");
859865
Check_Type(defaults, T_HASH);
860866
if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) {

ext/mysql2/statement.c

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,19 @@ static VALUE intern_usec, intern_sec, intern_min, intern_hour, intern_day, inter
88
#define GET_STATEMENT(self) \
99
mysql_stmt_wrapper *stmt_wrapper; \
1010
Data_Get_Struct(self, mysql_stmt_wrapper, stmt_wrapper); \
11-
if (!stmt_wrapper->stmt) { rb_raise(cMysql2Error, "Invalid statement handle"); }
11+
if (!stmt_wrapper->stmt) { rb_raise(cMysql2Error, "Invalid statement handle"); } \
12+
if (stmt_wrapper->closed) { rb_raise(cMysql2Error, "Statement handle already closed"); }
1213

1314

1415
static void rb_mysql_stmt_mark(void * ptr) {
15-
mysql_stmt_wrapper* stmt_wrapper = (mysql_stmt_wrapper *)ptr;
16+
mysql_stmt_wrapper *stmt_wrapper = ptr;
1617
if (!stmt_wrapper) return;
1718

1819
rb_gc_mark(stmt_wrapper->client);
1920
}
2021

2122
static void *nogvl_stmt_close(void * ptr) {
22-
mysql_stmt_wrapper *stmt_wrapper = (mysql_stmt_wrapper *)ptr;
23+
mysql_stmt_wrapper *stmt_wrapper = ptr;
2324
if (stmt_wrapper->stmt) {
2425
mysql_stmt_close(stmt_wrapper->stmt);
2526
stmt_wrapper->stmt = NULL;
@@ -28,7 +29,7 @@ static void *nogvl_stmt_close(void * ptr) {
2829
}
2930

3031
static void rb_mysql_stmt_free(void * ptr) {
31-
mysql_stmt_wrapper* stmt_wrapper = (mysql_stmt_wrapper *)ptr;
32+
mysql_stmt_wrapper *stmt_wrapper = ptr;
3233
decr_mysql2_stmt(stmt_wrapper);
3334
}
3435

@@ -93,7 +94,7 @@ static void *nogvl_prepare_statement(void *ptr) {
9394
}
9495

9596
VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
96-
mysql_stmt_wrapper* stmt_wrapper;
97+
mysql_stmt_wrapper *stmt_wrapper;
9798
VALUE rb_stmt;
9899
#ifdef HAVE_RUBY_ENCODING_H
99100
rb_encoding *conn_enc;
@@ -105,6 +106,7 @@ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
105106
{
106107
stmt_wrapper->client = rb_client;
107108
stmt_wrapper->refcount = 1;
109+
stmt_wrapper->closed = 0;
108110
stmt_wrapper->stmt = NULL;
109111
}
110112

@@ -461,6 +463,7 @@ static VALUE rb_mysql_stmt_affected_rows(VALUE self) {
461463
*/
462464
static VALUE rb_mysql_stmt_close(VALUE self) {
463465
GET_STATEMENT(self);
466+
stmt_wrapper->closed = 1;
464467
rb_thread_call_without_gvl(nogvl_stmt_close, stmt_wrapper, RUBY_UBF_IO, 0);
465468
return Qnil;
466469
}

ext/mysql2/statement.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ typedef struct {
77
VALUE client;
88
MYSQL_STMT *stmt;
99
int refcount;
10+
int closed;
1011
} mysql_stmt_wrapper;
1112

1213
void init_mysql2_statement(void);

0 commit comments

Comments
 (0)