Skip to content

Commit b5481de

Browse files
kamil-tekielanikic
authored andcommitted
Fix bug #72413: Segfault with get_result and PS cursors
We cannot simply switch to use_result here, because the fetch_row methods in get_result mode and in use_result/store_result mode are different: In one case it accepts a statement, in the other a return value zval. Thus, doing a switch to use_result results in a segfault when trying to fetch a row. Actually supporting get_result with cursors would require adding cursor support in mysqlnd_result, not just mysqlnd_ps. That would be a significant amount of effort and, given the age of the issue, does not appear to be particularly likely to happen soon. As such, we simply generate an error when using get_result() with cursors, which is much better than causing a segfault. Instead, parameter binding needs to be used.
1 parent 0044a81 commit b5481de

File tree

3 files changed

+78
-23
lines changed

3 files changed

+78
-23
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ PHP NEWS
3131
timeout). (Kamil Tekiela, Nikita)
3232
. Fixed bug #76525 (mysqli::commit does not throw if MYSQLI_REPORT_ERROR
3333
enabled and mysqlnd used). (Kamil Tekiela)
34+
. Fixed bug #72413 (mysqlnd segfault (fetch_row second parameter
35+
typemismatch)). (Kamil Tekiela)
3436

3537
- ODBC:
3638
. Fixed bug #44618 (Fetching may rely on uninitialized data). (cmb)

ext/mysqli/tests/mysqli_stmt_get_result.phpt

Lines changed: 67 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -124,32 +124,77 @@ if (!function_exists('mysqli_stmt_get_result'))
124124
if (true !== ($tmp = mysqli_stmt_bind_result($stmt, $id, $label)))
125125
printf("[035] Expecting boolean/true, got %s/%s\n", gettype($tmp), var_export($tmp, 1));
126126

127-
if (!is_object($tmp = $result = mysqli_stmt_get_result($stmt)))
128-
printf("[036] Expecting array, got %s/%s, [%d] %s\n",
129-
gettype($tmp), var_export($tmp, 1),
130-
mysqli_stmt_errno($stmt), mysqli_stmt_error($stmt));
131-
132-
if (false !== ($tmp = mysqli_stmt_fetch($stmt)))
133-
printf("[037] Expecting boolean/false, got %s/%s, [%d] %s\n",
134-
gettype($tmp), $tmp, mysqli_stmt_errno($stmt), mysqli_stmt_error($stmt));
135-
136-
printf("[038] [%d] [%s]\n", mysqli_stmt_errno($stmt), mysqli_stmt_error($stmt));
137-
printf("[039] [%d] [%s]\n", mysqli_errno($link), mysqli_error($link));
138-
while ($row = mysqli_fetch_assoc($result)) {
139-
var_dump($row);
140-
}
141-
mysqli_free_result($result);
127+
// get_result cannot be used in PS cursor mode
128+
if (!$stmt = mysqli_stmt_init($link))
129+
printf("[030] [%d] %s\n", mysqli_errno($link), mysqli_error($link));
130+
131+
if (!mysqli_stmt_prepare($stmt, "SELECT id, label FROM test ORDER BY id LIMIT 2"))
132+
printf("[031] [%d] %s\n", mysqli_stmt_errno($stmt), mysqli_stmt_error($stmt));
133+
134+
if (!mysqli_stmt_attr_set($stmt, MYSQLI_STMT_ATTR_CURSOR_TYPE, MYSQLI_CURSOR_TYPE_READ_ONLY))
135+
printf("[032] [%d] %s\n", mysqli_stmt_errno($stmt), mysqli_stmt_error($stmt));
136+
137+
if (!mysqli_stmt_execute($stmt))
138+
printf("[033] [%d] %s\n", mysqli_stmt_errno($stmt), mysqli_stmt_error($stmt));
139+
140+
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
141+
try {
142+
$res = mysqli_stmt_get_result($stmt);
143+
// we expect no segfault if we try to fetch a row because get_result should throw an error or return false
144+
mysqli_fetch_assoc($res);
145+
} catch (\mysqli_sql_exception $e) {
146+
echo $e->getMessage() . "\n";
147+
}
148+
149+
try {
150+
$res = $stmt->get_result();
151+
// we expect no segfault if we try to fetch a row because get_result should throw an error or return false
152+
$res->fetch_assoc();
153+
} catch (\mysqli_sql_exception $e) {
154+
echo $e->getMessage() . "\n";
155+
}
156+
mysqli_report(MYSQLI_REPORT_OFF);
157+
158+
if (!$stmt = mysqli_stmt_init($link))
159+
printf("[034] [%d] %s\n", mysqli_errno($link), mysqli_error($link));
160+
161+
if (!mysqli_stmt_prepare($stmt, "SELECT id, label FROM test ORDER BY id LIMIT 2"))
162+
printf("[035] [%d] %s\n", mysqli_stmt_errno($stmt), mysqli_stmt_error($stmt));
163+
164+
if (!mysqli_stmt_execute($stmt))
165+
printf("[036] [%d] %s\n", mysqli_stmt_errno($stmt), mysqli_stmt_error($stmt));
166+
167+
$id = NULL;
168+
$label = NULL;
169+
if (true !== ($tmp = mysqli_stmt_bind_result($stmt, $id, $label)))
170+
printf("[037] Expecting boolean/true, got %s/%s\n", gettype($tmp), var_export($tmp, 1));
171+
172+
if (!is_object($tmp = $result = mysqli_stmt_get_result($stmt)))
173+
printf("[038] Expecting array, got %s/%s, [%d] %s\n",
174+
gettype($tmp), var_export($tmp, 1),
175+
mysqli_stmt_errno($stmt), mysqli_stmt_error($stmt));
176+
177+
if (false !== ($tmp = mysqli_stmt_fetch($stmt)))
178+
printf("[039] Expecting boolean/false, got %s/%s, [%d] %s\n",
179+
gettype($tmp), $tmp, mysqli_stmt_errno($stmt), mysqli_stmt_error($stmt));
180+
181+
printf("[040] [%d] [%s]\n", mysqli_stmt_errno($stmt), mysqli_stmt_error($stmt));
182+
printf("[041] [%d] [%s]\n", mysqli_errno($link), mysqli_error($link));
183+
while ($row = mysqli_fetch_assoc($result)) {
184+
var_dump($row);
185+
}
186+
mysqli_free_result($result);
142187

143188
if (!mysqli_kill($link, mysqli_thread_id($link)))
144-
printf("[040] [%d] %s\n", mysqli_errno($link), mysqli_error($link));
189+
printf("[042] [%d] %s\n", mysqli_errno($link), mysqli_error($link));
145190

146191
if (false !== ($tmp = mysqli_stmt_get_result($stmt)))
147-
printf("[041] Expecting false, got %s/%s\n", gettype($tmp), var_export($tmp, 1));
192+
printf("[043] Expecting false, got %s/%s\n", gettype($tmp), var_export($tmp, 1));
148193

149194
mysqli_stmt_close($stmt);
150195

151196
if (false !== ($tmp = mysqli_stmt_fetch($stmt)))
152-
printf("[042] Expecting false, got %s/%s\n", gettype($tmp), var_export($tmp, 1));
197+
printf("[044] Expecting false, got %s/%s\n", gettype($tmp), var_export($tmp, 1));
153198

154199
mysqli_close($link);
155200

@@ -168,8 +213,10 @@ Warning: mysqli_stmt_fetch(): invalid object or resource mysqli_stmt
168213

169214
Warning: mysqli_stmt_get_result(): invalid object or resource mysqli_stmt
170215
in %s on line %d
171-
[038] [2014] [Commands out of sync; you can't run this command now]
172-
[039] [0] []
216+
mysqli_stmt_get_result() cannot be used with cursors
217+
get_result() cannot be used with cursors
218+
[040] [2014] [Commands out of sync; you can't run this command now]
219+
[041] [0] []
173220
array(2) {
174221
["id"]=>
175222
int(1)

ext/mysqlnd/mysqlnd_ps.c

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,13 +153,19 @@ MYSQLND_METHOD(mysqlnd_stmt, get_result)(MYSQLND_STMT * const s)
153153
}
154154

155155
if (stmt->cursor_exists) {
156-
/* Silently convert buffered to unbuffered, for now */
157-
DBG_RETURN(s->m->use_result(s));
156+
/* Prepared statement cursors are not supported as of yet */
157+
char * msg;
158+
mnd_sprintf(&msg, 0, "%s() cannot be used with cursors", get_active_function_name());
159+
SET_CLIENT_ERROR(stmt->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, msg);
160+
if (msg) {
161+
mnd_sprintf_free(msg);
162+
}
163+
DBG_RETURN(NULL);
158164
}
159165

160166
/* Nothing to store for UPSERT/LOAD DATA*/
161167
if (GET_CONNECTION_STATE(&conn->state) != CONN_FETCHING_DATA || stmt->state != MYSQLND_STMT_WAITING_USE_OR_STORE) {
162-
SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
168+
SET_CLIENT_ERROR(stmt->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync);
163169
DBG_RETURN(NULL);
164170
}
165171

0 commit comments

Comments
 (0)