php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #73210 Segfault with stmt read only cursor and get_result due to double closing
Submitted: 2016-09-30 11:28 UTC Modified: 2020-12-18 09:33 UTC
Votes:3
Avg. Score:3.7 ± 0.9
Reproduced:3 of 3 (100.0%)
Same Version:2 (66.7%)
Same OS:1 (33.3%)
From: richard dot fussenegger at trivago dot com Assigned: nikic (profile)
Status: Closed Package: MySQLi related
PHP Version: Irrelevant OS: Irrelevant
Private report: No CVE-ID: None
Welcome back! If you're the original bug submitter, here's where you can edit the bug or add additional notes.
If you forgot your password, you can retrieve your password here.
Password:
Status:
Package:
Bug Type:
Summary:
From: richard dot fussenegger at trivago dot com
New email:
PHP Version: OS:

 

 [2016-09-30 11:28 UTC] richard dot fussenegger at trivago dot com
Description:
------------
Getting the result of an executed prepared statement that uses a read only cursor results in a segfault because the second close call tries to free the internal result on null. It does not matter which is the first or second close call since stmt gives result a pointer to the result, the one that calls it first is the one that frees it and the other one accesses null.

The access happens in mysqlnd_res::free_result_internal after the if (result->conn) condition where result->conn->m is being called. The m might already point to nowhere because the previous close call already freed it.

Patch will be directly provided as GitHub PR.

Test script:
---------------
<?php

mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli('localhost', 'root', 'keines');

$stmt = $mysqli->prepare('SELECT 1 UNION SELECT 2 UNION SELECT 3');
$stmt->attr_set(MYSQLI_STMT_ATTR_CURSOR_TYPE, MYSQLI_CURSOR_TYPE_READ_ONLY);
$stmt->execute();
$result = $stmt->get_result();

// call order does not matter {{{
$result->close();
$stmt->close();
// }}}

$mysqli->close();


Expected result:
----------------
Successful and graceful shutdown of PHP.

Actual result:
--------------
Segfault

Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2018-05-04 21:26 UTC] cmb@php.net
> Patch will be directly provided as GitHub PR.

FTR: <https://github.com/php/php-src/pull/2146> didn't really
solve the issue.
 [2019-07-31 20:55 UTC] tekiela246 at gmail dot com
I have a similar problem, but I am not sure if it is the same one. I also get a SEGFAULT when I execute this code:

$mysqli = new mysqli($host, $user, $pass, $db);
$stmtQuery = $mysqli->prepare("SELECT ?, 'name'");
$stmtQuery->bind_result($id, $name);
$stmtQuery->attr_set(MYSQLI_STMT_ATTR_CURSOR_TYPE, MYSQLI_CURSOR_TYPE_READ_ONLY);
$stmtQuery->bind_param('i', $i);
$stmtQuery->execute();
$stmtQuery->get_result()->fetch_assoc();

---------
I could not get the proper backtrace. I only have this if it helps:
php7ts!mysqlnd_pfc_free+400 
php7ts!mysqlnd_protocol_payload_decoder_factory_free+548 
php7ts!mysqlnd_result_buffered_c_init+2c23 
php7ts!mysqlnd_result_buffered_c_init+3c2b 
php_mysqli+1133 
php7ts!php_json_parse+2f8b 
php7ts!mysqlnd_protocol_payload_decoder_factory_free+439a 
php7ts!mysqlnd_protocol_payload_decoder_factory_free+43f7 
php7ts!mysqlnd_result_buffered_c_init+2274 
php7ts!mysqlnd_result_buffered_c_init+22b1 
php7ts!mysqlnd_pfc_free+2d84 
php7ts!ecalloc+b9 
php_mysqli!mysqli_objects_new+5a 
php_mysqli!mysqli_objects_new+2167 
php_mysqli!get_module+7591 
php7ts!execute_ex+78 
php7ts!zend_execute+124 
php7ts!zend_execute+152 
php7ts!zend_execute_scripts+96 
php7ts!zend_set_timeout+90
 [2019-08-01 09:09 UTC] nikic@php.net
-Status: Open +Status: Verified
 [2019-08-01 09:14 UTC] nikic@php.net
Confirming original segfault on 7.2 and valgrind errors on newer versions.
 [2019-08-01 09:33 UTC] nikic@php.net
We're freeing the result twice, once directly via free_result and again indirectly via stmt_close.

We could store a back-reference to the stmt in the result and NULL it when the result if freed, but that would only solve the problem for the case where free_result is called before stmt_close. For the reverse case we'd need access to the PHP resource storing the result, which we don't have.

Possibly the result needs to be refcounted?
 [2019-08-01 12:43 UTC] cmb@php.net
For PHP 7.2, the segfault already happens when the result is
freed:

    php7_debug.dll!mysqlnd_mysqlnd_res_free_result_internal_pub(st_mysqlnd_res * result) Line 348 (c:\php-sdk\phpdev\vc15\x64\php-src-7.2\ext\mysqlnd\mysqlnd_result.c:348)
    php7_debug.dll!mysqlnd_mysqlnd_stmt_free_stmt_result_pub(st_mysqlnd_stmt * const s) Line 2131 (c:\php-sdk\phpdev\vc15\x64\php-src-7.2\ext\mysqlnd\mysqlnd_ps.c:2131)
    php7_debug.dll!mysqlnd_mysqlnd_stmt_free_stmt_content_pub(st_mysqlnd_stmt * const s) Line 2175 (c:\php-sdk\phpdev\vc15\x64\php-src-7.2\ext\mysqlnd\mysqlnd_ps.c:2175)
    php7_debug.dll!mysqlnd_mysqlnd_stmt_close_on_server_priv(st_mysqlnd_stmt * const s, unsigned char implicit) Line 2261 (c:\php-sdk\phpdev\vc15\x64\php-src-7.2\ext\mysqlnd\mysqlnd_ps.c:2261)
    php7_debug.dll!mysqlnd_mysqlnd_stmt_dtor_pub(st_mysqlnd_stmt * const s, unsigned char implicit) Line 2285 (c:\php-sdk\phpdev\vc15\x64\php-src-7.2\ext\mysqlnd\mysqlnd_ps.c:2285)
    php7_debug.dll!zif_mysqli_stmt_close(_zend_execute_data * execute_data, _zval_struct * return_value) Line 2064 (c:\php-sdk\phpdev\vc15\x64\php-src-7.2\ext\mysqli\mysqli_api.c:2064)
    php7_debug.dll!ZEND_DO_FCALL_SPEC_RETVAL_UNUSED_HANDLER(_zend_execute_data * execute_data) Line 908 (c:\php-sdk\phpdev\vc15\x64\php-src-7.2\Zend\zend_vm_execute.h:908)
    php7_debug.dll!execute_ex(_zend_execute_data * ex) Line 59739 (c:\php-sdk\phpdev\vc15\x64\php-src-7.2\Zend\zend_vm_execute.h:59739)
    php7_debug.dll!zend_execute(_zend_op_array * op_array, _zval_struct * return_value) Line 63777 (c:\php-sdk\phpdev\vc15\x64\php-src-7.2\Zend\zend_vm_execute.h:63777)
    php7_debug.dll!zend_execute_scripts(int type, _zval_struct * retval, int file_count, ...) Line 1499 (c:\php-sdk\phpdev\vc15\x64\php-src-7.2\Zend\zend.c:1499)
    php7_debug.dll!php_execute_script(_zend_file_handle * primary_file) Line 2599 (c:\php-sdk\phpdev\vc15\x64\php-src-7.2\main\main.c:2599)
    php.exe!do_cli(int argc, char * * argv) Line 1012 (c:\php-sdk\phpdev\vc15\x64\php-src-7.2\sapi\cli\php_cli.c:1012)
    php.exe!main(int argc, char * * argv) Line 1403 (c:\php-sdk\phpdev\vc15\x64\php-src-7.2\sapi\cli\php_cli.c:1403)
    php.exe!invoke_main() Line 79 (d:\agent\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:79)
    php.exe!__scrt_common_main_seh() Line 288 (d:\agent\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288)
    php.exe!__scrt_common_main() Line 331 (d:\agent\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:331)
    php.exe!mainCRTStartup() Line 17 (d:\agent\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_main.cpp:17)
    kernel32.dll!00007ffeba257bd4() (Unbekannte Quelle:0)
    ntdll.dll!00007ffebadece71() (Unbekannte Quelle:0)

I set a breakpoint on mysqlnd_result.c:1527[1], and did

  p result->conn // 0x0000016afac76500
  n
  p result->conn // 0x0000016afac9c180

@tekiela246, your problem doesn't seem related to this issue,
so please file a new ticket.

[1] <https://github.com/php/php-src/blob/php-7.2.21/ext/mysqlnd/mysqlnd_result.c#L1527>
 [2019-08-01 12:50 UTC] cmb@php.net
> For PHP 7.2, the segfault already happens when the result is
> freed

No, nonsense.  It's the double free described by @nikic.
 [2019-08-08 07:01 UTC] cmb@php.net
@tekiela246, your issue is actually a duplicate of bug #72413.
 [2020-12-18 09:33 UTC] nikic@php.net
-Status: Verified +Status: Closed -Assigned To: +Assigned To: nikic
 [2020-12-18 09:33 UTC] nikic@php.net
No longer crashes on 7.4 HEAD, presumably fixed by https://github.com/php/php-src/commit/bc166844e37a6e1531a18dc0916fbe508152fc6c or related changes. (Does valgrind on 7.3.)
 
PHP Copyright © 2001-2025 The PHP Group
All rights reserved.
Last updated: Wed Jan 15 07:01:29 2025 UTC