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: 2019-08-08 07:01 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:
Status: Verified Package: MySQLi related
PHP Version: Irrelevant OS: Irrelevant
Private report: No CVE-ID: None
Have you experienced this issue?
Rate the importance of this bug to you:

 [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

Add a Patch

Pull Requests

Add a Pull Request

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.
 
PHP Copyright © 2001-2019 The PHP Group
All rights reserved.
Last updated: Thu Nov 14 13:01:32 2019 UTC