Skip to content

Commit 2bae4e8

Browse files
committed
Merge branch 'PHP-8.0' into PHP-8.1
* PHP-8.0: Fix bug GH-8058 - mysqlnd segfault when prepare fails
2 parents 414d562 + 93a8d5c commit 2bae4e8

File tree

4 files changed

+60
-54
lines changed

4 files changed

+60
-54
lines changed

NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ PHP NEWS
1717
. Fixed bug GH-7953 (ob_clean() only does not set Content-Encoding). (cmb)
1818
. Fixed bug GH-7980 (Unexpected result for iconv_mime_decode). (cmb)
1919

20+
- MySQLnd:
21+
. Fixed bug GH-8058 (NULL pointer dereference in mysqlnd package). (Kamil Tekiela)
22+
2023
- Zlib:
2124
. Fixed bug GH-7953 (ob_clean() only does not set Content-Encoding). (cmb)
2225

ext/mysqli/tests/gh8058.phpt

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
--TEST--
2+
GH-8058 (NULL pointer dereference in mysqlnd package (#81706))
3+
--EXTENSIONS--
4+
mysqli
5+
--SKIPIF--
6+
<?php
7+
require_once 'skipifconnectfailure.inc';
8+
?>
9+
--FILE--
10+
<?php
11+
require_once "connect.inc";
12+
13+
mysqli_report(MYSQLI_REPORT_OFF);
14+
$mysqli = new my_mysqli($host, $user, $passwd, $db, $port, $socket);
15+
16+
// There should be no segfault due to NULL deref
17+
$stmt = $mysqli->prepare("select 1,2,3");
18+
$stmt->bind_result($a,$a,$a);
19+
$stmt->prepare("");
20+
$stmt->prepare("select ".str_repeat("'A',", 0x1201)."1");
21+
unset($stmt); // trigger dtor
22+
23+
// There should be no memory leak
24+
$stmt = $mysqli->prepare("select 1,2,3");
25+
$stmt->bind_result($a,$a,$a);
26+
$stmt->prepare("");
27+
$stmt->prepare("select 1");
28+
unset($stmt); // trigger dtor
29+
30+
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
31+
$stmt = $mysqli->prepare("select 1,2,3");
32+
try {
33+
// We expect an exception to be thrown
34+
$stmt->prepare("");
35+
} catch (mysqli_sql_exception $e) {
36+
var_dump($e->getMessage());
37+
}
38+
?>
39+
--EXPECT--
40+
string(15) "Query was empty"

ext/mysqli/tests/mysqli_stmt_affected_rows.phpt

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -197,18 +197,13 @@ require_once('skipifconnectfailure.inc');
197197
/* stmt_affected_rows is not really meant for SELECT! */
198198
if (mysqli_stmt_prepare($stmt, 'SELECT unknown_column FROM this_table_does_not_exist') ||
199199
mysqli_stmt_execute($stmt))
200-
printf("[041] The invalid SELECT statement is issued on purpose\n");
200+
printf("[041] Expecting SELECT statement to fail on purpose\n");
201201

202202
if (-1 !== ($tmp = mysqli_stmt_affected_rows($stmt)))
203203
printf("[042] Expecting int/-1, got %s/%s\n", gettype($tmp), $tmp);
204204

205-
if ($IS_MYSQLND) {
206-
if (false !== ($tmp = mysqli_stmt_store_result($stmt)))
207-
printf("[043] Expecting boolean/false, got %s\%s\n", gettype($tmp), $tmp);
208-
} else {
209-
if (true !== ($tmp = mysqli_stmt_store_result($stmt)))
210-
printf("[043] Libmysql does not care if the previous statement was bogus, expecting boolean/true, got %s\%s\n", gettype($tmp), $tmp);
211-
}
205+
if (true !== ($tmp = mysqli_stmt_store_result($stmt)))
206+
printf("[043] Expecting boolean/true, got %s/%s\n", gettype($tmp), $tmp);
212207

213208
if (0 !== ($tmp = mysqli_stmt_num_rows($stmt)))
214209
printf("[044] Expecting int/0, got %s/%s\n", gettype($tmp), $tmp);

ext/mysqlnd/mysqlnd_ps.c

Lines changed: 14 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -369,12 +369,10 @@ mysqlnd_stmt_prepare_read_eof(MYSQLND_STMT * s)
369369

370370
/* {{{ mysqlnd_stmt::prepare */
371371
static enum_func_status
372-
MYSQLND_METHOD(mysqlnd_stmt, prepare)(MYSQLND_STMT * const s, const char * const query, const size_t query_len)
372+
MYSQLND_METHOD(mysqlnd_stmt, prepare)(MYSQLND_STMT * s, const char * const query, const size_t query_len)
373373
{
374374
MYSQLND_STMT_DATA * stmt = s? s->data : NULL;
375375
MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL;
376-
MYSQLND_STMT * s_to_prepare = s;
377-
MYSQLND_STMT_DATA * stmt_to_prepare = stmt;
378376

379377
DBG_ENTER("mysqlnd_stmt::prepare");
380378
if (!stmt || !conn) {
@@ -390,25 +388,15 @@ MYSQLND_METHOD(mysqlnd_stmt, prepare)(MYSQLND_STMT * const s, const char * const
390388
SET_EMPTY_ERROR(conn->error_info);
391389

392390
if (stmt->state > MYSQLND_STMT_INITTED) {
393-
/* See if we have to clean the wire */
394-
if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) {
395-
/* Do implicit use_result and then flush the result */
396-
stmt->default_rset_handler = s->m->use_result;
397-
stmt->default_rset_handler(s);
398-
}
399-
/* No 'else' here please :) */
400-
if (stmt->state > MYSQLND_STMT_WAITING_USE_OR_STORE && stmt->result) {
401-
stmt->result->m.skip_result(stmt->result);
402-
}
403391
/*
404-
Create a new test statement, which we will prepare, but if anything
405-
fails, we will scrap it.
392+
Create a new prepared statement and destroy the previous one.
406393
*/
407-
s_to_prepare = conn->m->stmt_init(conn);
408-
if (!s_to_prepare) {
394+
s->m->dtor(s, TRUE);
395+
s = conn->m->stmt_init(conn);
396+
if (!s) {
409397
goto fail;
410398
}
411-
stmt_to_prepare = s_to_prepare->data;
399+
stmt = s->data;
412400
}
413401

414402
{
@@ -422,13 +410,13 @@ MYSQLND_METHOD(mysqlnd_stmt, prepare)(MYSQLND_STMT * const s, const char * const
422410
}
423411
}
424412

425-
if (FAIL == mysqlnd_stmt_read_prepare_response(s_to_prepare)) {
413+
if (FAIL == mysqlnd_stmt_read_prepare_response(s)) {
426414
goto fail;
427415
}
428416

429-
if (stmt_to_prepare->param_count) {
430-
if (FAIL == mysqlnd_stmt_skip_metadata(s_to_prepare) ||
431-
FAIL == mysqlnd_stmt_prepare_read_eof(s_to_prepare))
417+
if (stmt->param_count) {
418+
if (FAIL == mysqlnd_stmt_skip_metadata(s) ||
419+
FAIL == mysqlnd_stmt_prepare_read_eof(s))
432420
{
433421
goto fail;
434422
}
@@ -439,51 +427,31 @@ MYSQLND_METHOD(mysqlnd_stmt, prepare)(MYSQLND_STMT * const s, const char * const
439427
Beware that SHOW statements bypass the PS framework and thus they send
440428
no metadata at prepare.
441429
*/
442-
if (stmt_to_prepare->field_count) {
443-
MYSQLND_RES * result = conn->m->result_init(stmt_to_prepare->field_count);
430+
if (stmt->field_count) {
431+
MYSQLND_RES * result = conn->m->result_init(stmt->field_count);
444432
if (!result) {
445433
SET_OOM_ERROR(conn->error_info);
446434
goto fail;
447435
}
448436
/* Allocate the result now as it is needed for the reading of metadata */
449-
stmt_to_prepare->result = result;
437+
stmt->result = result;
450438

451439
result->conn = conn->m->get_reference(conn);
452440

453441
result->type = MYSQLND_RES_PS_BUF;
454442

455443
if (FAIL == result->m.read_result_metadata(result, conn) ||
456-
FAIL == mysqlnd_stmt_prepare_read_eof(s_to_prepare))
444+
FAIL == mysqlnd_stmt_prepare_read_eof(s))
457445
{
458446
goto fail;
459447
}
460448
}
461449

462-
if (stmt_to_prepare != stmt) {
463-
/* swap */
464-
size_t real_size = sizeof(MYSQLND_STMT) + mysqlnd_plugin_count() * sizeof(void *);
465-
char * tmp_swap = mnd_emalloc(real_size);
466-
memcpy(tmp_swap, s, real_size);
467-
memcpy(s, s_to_prepare, real_size);
468-
memcpy(s_to_prepare, tmp_swap, real_size);
469-
mnd_efree(tmp_swap);
470-
{
471-
MYSQLND_STMT_DATA * tmp_swap_data = stmt_to_prepare;
472-
stmt_to_prepare = stmt;
473-
stmt = tmp_swap_data;
474-
}
475-
s_to_prepare->m->dtor(s_to_prepare, TRUE);
476-
}
477450
stmt->state = MYSQLND_STMT_PREPARED;
478451
DBG_INF("PASS");
479452
DBG_RETURN(PASS);
480453

481454
fail:
482-
if (stmt_to_prepare != stmt && s_to_prepare) {
483-
s_to_prepare->m->dtor(s_to_prepare, TRUE);
484-
}
485-
stmt->state = MYSQLND_STMT_INITTED;
486-
487455
DBG_INF("FAIL");
488456
DBG_RETURN(FAIL);
489457
}

0 commit comments

Comments
 (0)