Skip to content

Commit e3b4c3e

Browse files
author
Jonathan Powell
committed
Fix segfault when accessing fields/field_types on freed result
Add resultFreed check to rb_mysql_result_fetch_fields and rb_mysql_result_fetch_field_types before accessing wrapper->result. Additionally, eagerly populate fields when a result is created (for non-streaming results) to prevent the issue from occurring at all. This ensures fields are fetched before any iteration can free the result. The segfault occurs when: 1. A query returns 0 rows 2. Internal row caching iterates (0 iterations), never populating wrapper->fields 3. Result is freed after iteration completes 4. .fields is called, wrapper->fields is Qnil 5. mysql_num_fields(wrapper->result) accesses freed memory -> SEGFAULT This check already exists in other functions (e.g., rb_mysql_result_each_) but was missing in the fields accessor functions. Based on fix for #1426
1 parent c79b3c1 commit e3b4c3e

1 file changed

Lines changed: 15 additions & 0 deletions

File tree

ext/mysql2/result.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -939,6 +939,9 @@ static VALUE rb_mysql_result_fetch_fields(VALUE self) {
939939
}
940940

941941
if (wrapper->fields == Qnil) {
942+
if (wrapper->resultFreed) {
943+
rb_raise(cMysql2Error, "Result set has already been freed");
944+
}
942945
wrapper->numberOfFields = mysql_num_fields(wrapper->result);
943946
wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
944947
}
@@ -958,6 +961,9 @@ static VALUE rb_mysql_result_fetch_field_types(VALUE self) {
958961
GET_RESULT(self);
959962

960963
if (wrapper->fieldTypes == Qnil) {
964+
if (wrapper->resultFreed) {
965+
rb_raise(cMysql2Error, "Result set has already been freed");
966+
}
961967
wrapper->numberOfFields = mysql_num_fields(wrapper->result);
962968
wrapper->fieldTypes = rb_ary_new2(wrapper->numberOfFields);
963969
}
@@ -1192,6 +1198,7 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_
11921198
wrapper->lastRowProcessed = 0;
11931199
wrapper->resultFreed = 0;
11941200
wrapper->result = r;
1201+
wrapper->numberOfFields = 0;
11951202
wrapper->fields = Qnil;
11961203
wrapper->fieldTypes = Qnil;
11971204
wrapper->rows = Qnil;
@@ -1221,6 +1228,14 @@ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_
12211228
* should be processed here. */
12221229
wrapper->is_streaming = (rb_hash_aref(options, sym_stream) == Qtrue ? 1 : 0);
12231230

1231+
/* Eagerly populate fields for non-streaming results to prevent
1232+
* segfault/error when accessing .fields on 0-row results that get freed
1233+
* after iteration completes but before .fields is accessed.
1234+
* See: https://github.com/brianmario/mysql2/issues/1426 */
1235+
if (r != NULL && !wrapper->is_streaming) {
1236+
rb_mysql_result_fetch_fields(obj);
1237+
}
1238+
12241239
return obj;
12251240
}
12261241

0 commit comments

Comments
 (0)