Skip to content

PHP 8.6.0RC3: crash in pecl pdo_oci when doing execute()+fetch_all() twice using the same prepared statement #24372

Open

Description

@rainerjung

Description

Platform: Linux x64 (Alma 9 and 10)
PHP-Version: PHP 8.6.0RC3 but not (!!) happening for PHP 8.6.0RC2
pdo_oci: version 1.2.0 plus small patch from php/pecl-database-pdo_oci#41

PHP script showing the crash:

<?php

$dsn = 'oci:dbname=(DESCRIPTION=(ADDRESS=(HOST=myhost)(PROTOCOL=tcp)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=mydb)))';
$user = 'myuser';
$pass = 'mypass';

$db = PDO::connect($dsn, $user, $pass, null);

if (!$db) {
    die("Could not create PDO object (DSN=$dsn, user=$user)\n");
}

// Next two lines only needed for first use of this script
$db->exec('CREATE TABLE testtable(id CHAR(1) NOT NULL PRIMARY KEY, val CHAR(1))');
$db->exec("INSERT INTO testtable VALUES('A', 'B')");

$stmt = $db->prepare('SELECT id, val from testtable');

echo "Before first execution...\n";
$stmt->execute();
echo "Before first fetch...\n";
var_dump($stmt->fetchAll());

echo "Before second execution...\n";
$stmt->execute();
echo "Before second fetch...\n";
var_dump($stmt->fetchAll());

?>

Execution with "php -d extension=pdo -d extension=pdo_oci myscript.php"

RC2 succeeds:

Before first fetch...
array(1) {
  [0]=>
  array(4) {
    ["ID"]=>
    string(1) "A"
    [0]=>
    string(1) "A"
    ["VAL"]=>
    string(1) "B"
    [1]=>
    string(1) "B"
  }
}
Before second execution...
Before second fetch...
array(1) {
  [0]=>
  array(4) {
    ["ID"]=>
    string(1) "A"
    [0]=>
    string(1) "A"
    ["VAL"]=>
    string(1) "B"
    [1]=>
    string(1) "B"
  }
}

RC3 fails with segfault crash:

Before first execution...
Before first fetch...
array(1) {
  [0]=>
  array(4) {
    ["ID"]=>
    string(1) "A"
    [0]=>
    string(1) "A"
    ["VAL"]=>
    string(1) "B"
    [1]=>
    string(1) "B"
  }
}
Before second execution...
Before second fetch...
Segmentation fault (core dumped)


During the fetch a column gets a string of length 1 but with string pointer NULL, so creating a ZSTR_CHAR from it crashes when trying to dereference the first character in this string.

Stack:

#0  0x00007f8e348a8a74 in __pthread_kill_implementation () from /lib64/libc.so.6
#1  0x00007f8e3484f37e in raise () from /lib64/libc.so.6
#2  0x00007f8e331665c4 in skgesigOSCrash () from /path/to/oracle-instantclient/12.1.0.2.0.linux.x86_64/libclntsh.so.12.1
#3  0x00007f8e3343dde6 in kpeDbgSignalHandler () from /path/to/oracle-instantclient/12.1.0.2.0.linux.x86_64/libclntsh.so.12.1
#4  0x00007f8e331667b5 in skgesig_sigactionHandler () from /path/to/oracle-instantclient/12.1.0.2.0.linux.x86_64/libclntsh.so.12.1
#5  <signal handler called>
#6  ZSTR_CHAR (c=<error reading variable: Cannot access memory at address 0x0>)
    at /path/to/include/php/Zend/zend_string.h:119
#7  zend_string_init_fast (str=0x0, len=1) at /path/to/include/php/Zend/zend_string.h:226
#8  oci_stmt_get_col (stmt=0x7f8e34694000, colno=0, result=0x7ffd35b48580, type=0x0)
    at /path/to/pecl-database-pdo_oci-1.2.0/oci_statement.c:810
#9  0x00007f8e34595554 in fetch_value (stmt=stmt@entry=0x7f8e34694000, dest=dest@entry=0x7ffd35b48580, colno=colno@entry=0, type_override=type_override@entry=0x0)
    at /path/to/php86/ext/pdo/pdo_stmt.c:535
#10 0x00007f8e34597f6d in do_fetch (stmt=stmt@entry=0x7f8e34694000, return_value=return_value@entry=0x7ffd35b48620, how=PDO_FETCH_BOTH, ori=ori@entry=PDO_FETCH_ORI_NEXT, 
    offset=offset@entry=0, group_key=group_key@entry=0x0) at /path/to/php86/ext/pdo/pdo_stmt.c:883
#11 0x00007f8e34598e53 in zim_PDOStatement_fetchAll (execute_data=<optimized out>, return_value=0x7f8e346141d0)
    at /path/to/php86/ext/pdo/pdo_stmt.c:1322
#12 0x00007f8e350d9139 in ZEND_DO_FCALL_SPEC_RETVAL_USED_HANDLER () at /path/to/php86/Zend/zend_vm_execute.h:2152
#13 execute_ex (ex=0x7f8e34694000) at /path/to/php86/Zend/zend_vm_execute.h:110852
#14 0x00007f8e350ce875 in zend_execute (op_array=op_array@entry=0x7f8e34693300, return_value=return_value@entry=0x0)
    at /path/to/php86/Zend/zend_vm_execute.h:116021
#15 0x00007f8e3513adf0 in zend_execute_script (type=type@entry=8, retval=retval@entry=0x0, file_handle=file_handle@entry=0x7ffd35b4ac10)
    at /path/to/php86/Zend/zend.c:1980
#16 0x00007f8e34fcb9c6 in php_execute_script_ex (primary_file=<optimized out>, retval=retval@entry=0x0)
    at /path/to/php86/main/main.c:2605
#17 0x00007f8e34fcbcc7 in php_execute_script (primary_file=<optimized out>) at /path/to/php86/main/main.c:2645
#18 0x00000000006026ab in do_cli (argc=argc@entry=6, argv=argv@entry=0x182e6300) at /path/to/php86/sapi/cli/php_cli.c:943
#19 0x0000000000602cde in do_php_cli (argc=6, argv=0x182e6300) at /path/to/php86/sapi/cli/php_cli.c:1356
#20 0x00007f8e34838348 in __libc_start_call_main () from /lib64/libc.so.6
#21 0x00007f8e3483840b in __libc_start_main_impl () from /lib64/libc.so.6
#22 0x0000000000600b95 in _start ()

Stack with variable info:

#6  ZSTR_CHAR (c=<error reading variable: Cannot access memory at address 0x0>)
    at /path/to/include/php/Zend/zend_string.h:119
No locals.
#7  zend_string_init_fast (str=0x0, len=1) at /path/to/include/php/Zend/zend_string.h:226
No locals.
#8  oci_stmt_get_col (stmt=0x7f8e34694000, colno=0, result=0x7ffd35b48580, type=0x0)
    at /path/to/pecl-database-pdo_oci-1.2.0/oci_statement.c:810
        __z = 0x7ffd35b48580
        __s = <optimized out>
        S = 0x7f8e346810a0
        C = 0x7f8e3468c080
#9  0x00007f8e34595554 in fetch_value (stmt=stmt@entry=0x7f8e34694000, dest=dest@entry=0x7ffd35b48580, colno=colno@entry=0, type_override=type_override@entry=0x0)
    at /path/to/ext/pdo/pdo_stmt.c:535
        pdo_param_str = PDO_PARAM_NULL
#10 0x00007f8e34597f6d in do_fetch (stmt=stmt@entry=0x7f8e34694000, return_value=return_value@entry=0x7ffd35b48620, how=PDO_FETCH_BOTH, ori=ori@entry=PDO_FETCH_ORI_NEXT, 
    offset=offset@entry=0, group_key=group_key@entry=0x0) at /path/to/ext/pdo/pdo_stmt.c:883
        val = {value = {lval = 140248741350496, dval = 6.9292084973754746e-310, counted = 0x7f8e34688c60, str = 0x7f8e34688c60, arr = 0x7f8e34688c60, obj = 0x7f8e34688c60, 
            res = 0x7f8e34688c60, ref = 0x7f8e34688c60, ast = 0x7f8e34688c60, zv = 0x7f8e34688c60, ptr = 0x7f8e34688c60, ce = 0x7f8e34688c60, func = 0x7f8e34688c60, ww = {
              w1 = 879266912, w2 = 32654}}, u1 = {type_info = 1, v = {type = 1 '\001', type_flags = 0 '\000', u = {extra = 0}}}, u2 = {next = 32654, cache_slot = 32654, 
            opline_num = 32654, lineno = 32654, num_args = 32654, fe_pos = 32654, fe_iter_idx = 32654, guard = 32654, constant_flags = 32654, extra = 32654}}
        column_name = <optimized out>
        flags = 0
        ce = <optimized out>
        ctor_arguments = <optimized out>
        column_index_to_fetch = 0
        fetch_function_params = <optimized out>
        fetch_function_param_num = 0
#11 0x00007f8e34598e53 in zim_PDOStatement_fetchAll (execute_data=<optimized out>, return_value=0x7f8e346141d0)
    at /path/to/ext/pdo/pdo_stmt.c:1322
        how = 4
        arg2 = <optimized out>
        old_ce = <optimized out>
        old_ctor_args = <optimized out>
        ctor_args = <optimized out>
        stmt = 0x7f8e34694000
        fetch_mode = <optimized out>
        flags = 0
        data = {value = {lval = 140248741151448, dval = 6.9292084875411968e-310, counted = 0x7f8e346582d8, str = 0x7f8e346582d8, arr = 0x7f8e346582d8, obj = 0x7f8e346582d8, 
            res = 0x7f8e346582d8, ref = 0x7f8e346582d8, ast = 0x7f8e346582d8, zv = 0x7f8e346582d8, ptr = 0x7f8e346582d8, ce = 0x7f8e346582d8, func = 0x7f8e346582d8, ww = {
              w1 = 879067864, w2 = 32654}}, u1 = {type_info = 775, v = {type = 7 '\a', type_flags = 3 '\003', u = {extra = 0}}}, u2 = {next = 32654, cache_slot = 32654, 
            opline_num = 32654, lineno = 32654, num_args = 32654, fe_pos = 32654, fe_iter_idx = 32654, guard = 32654, constant_flags = 32654, extra = 32654}}
        group_key = {value = {lval = 0, dval = 0, counted = 0x0, str = 0x0, arr = 0x0, obj = 0x0, res = 0x0, ref = 0x0, ast = 0x0, zv = 0x0, ptr = 0x0, ce = 0x0, func = 0x0, 
            ww = {w1 = 0, w2 = 0}}, u1 = {type_info = 407588640, v = {type = 32 ' ', type_flags = 79 'O', u = {extra = 6219}}}, u2 = {next = 0, cache_slot = 0, 
            opline_num = 0, lineno = 0, num_args = 0, fe_pos = 0, fe_iter_idx = 0, guard = 0, constant_flags = 0, extra = 0}}
#12 0x00007f8e350d9139 in ZEND_DO_FCALL_SPEC_RETVAL_USED_HANDLER () at /path/to/Zend/zend_vm_execute.h:2152
        call = 0x7f8e34614250
        fbc = 0x184b4f20
        ret = <optimized out>
        retval = <optimized out>
        no_discard = <optimized out>
        call = <optimized out>
        fbc = <optimized out>
        ret = <optimized out>
        retval = <optimized out>
        no_discard = <optimized out>
#13 execute_ex (ex=0x7f8e34694000) at /path/to/Zend/zend_vm_execute.h:110852

Here's more info about the data in oci_stmt_get_col():

(gdb) print *stmt
$3 = {methods = 0x7f8e3480cce0 <oci_stmt_methods>, driver_data = 0x7f8e346810a0, error_code = "00000", executed = 1, in_fetch = 1, supports_placeholders = 1, 
  in_param_event = 0, reserved = 0, bound_params = 0x0, bound_param_map = 0x0, bound_columns = 0x0, columns = 0x7f8e34602450, column_count = 2, 
  default_fetch_type = PDO_FETCH_BOTH, fetch = {column = 0, cls = {ctor_args = 0x0, ce = 0x0}, func = {fcc = {function_handler = 0x0, calling_scope = 0x0, called_scope = 0x0, 
        object = 0x0, closure = 0x0}}, into = 0x0}, lazy_object_ref = 0x0, dbh = 0x7f8e34659140, database_object_handle = 0x7f8e346023c8, row_count = 0, 
  query_string = 0x7f8e34658380, active_query_string = 0x0, named_rewrite_template = 0x0, std = {gc = {refcount = 2, u = {type_info = 3221227528}}, handle = 2, 
    extra_flags = 0, ce = 0x184b1f90, handlers = 0x7f8e345a6860 <pdo_dbstmt_object_handlers>, properties = 0x0, properties_table = {{value = {lval = 140248741151616, 
          dval = 6.9292084875494971e-310, counted = 0x7f8e34658380, str = 0x7f8e34658380, arr = 0x7f8e34658380, obj = 0x7f8e34658380, res = 0x7f8e34658380, 
          ref = 0x7f8e34658380, ast = 0x7f8e34658380, zv = 0x7f8e34658380, ptr = 0x7f8e34658380, ce = 0x7f8e34658380, func = 0x7f8e34658380, ww = {w1 = 879068032, 
            w2 = 32654}}, u1 = {type_info = 6, v = {type = 6 '\006', type_flags = 0 '\000', u = {extra = 0}}}, u2 = {next = 0, cache_slot = 0, opline_num = 0, lineno = 0, 
          num_args = 0, fe_pos = 0, fe_iter_idx = 0, guard = 0, constant_flags = 0, extra = 0}}}}}

(gdb) print *S
$1 = {H = 0x7f8e34682060, stmt = 0x1858a060, err = 0x185888a0, last_err = 0, stmt_type = 1, exec_type = 0, cols = 0x7f8e3468c080, einfo = {
    file = 0x7f8e3480a710 "/path/to/pecl-database-pdo_oci-1.2.0/oci_statement.c", line = 197, errcode = 0, errmsg = 0x0}, 
  have_blobs = 0}

(gdb) print *C
$5 = {def = 0x0, fetched_len = 1, retcode = 0, indicator = 0, data = 0x0, datalen = 0, dtype = 0}

Here's more info about the data in do_fetch():

(gdb) print how
$14 = PDO_FETCH_BOTH
(gdb) print ori
$15 = PDO_FETCH_ORI_NEXT
(gdb) print offset
$16 = 0
(gdb) print group_key
$17 = (zval *) 0x0

PHP Version

PHP 8.6.0RC3 (cli) (built: Oct 14 2026 18:13:21) (NTS)
Copyright © The PHP Group and Contributors
Zend Engine v4.6.0RC3, Copyright © Zend by Perforce
    with Zend OPcache v8.6.0RC3, Copyright ©, by Zend by Perforce

Operating System

AlmaLinux 10 (also happens on Alma 9)

Activity

  1. rainerjung commented on Oct 16, 2026

    @rainerjung
    Author
  2. ndossche commented on Oct 16, 2026

    @ndossche
    Member

    I still have a build from that ext laying around from fixing some compatibility issues last year, I'm willing to take a look.

  3. self-assigned this
    on Oct 16, 2026
  4. ndossche commented on Oct 16, 2026

    @ndossche
    Member

    This is caused by 11690db, and that was merged into 8.4 so it affects stable versions too.
    cc @iliaal @SakiTakamachi

  5. removed their assignment
    on Oct 16, 2026
  6. self-assigned this
    on Oct 16, 2026
  7. iliaal commented on Oct 16, 2026

    @iliaal
    Member

    Interesting, let me take a look, I wonder why it affected OCI

  8. ndossche commented on Oct 16, 2026

    @ndossche
    Member

    Because it has no cursor_closer handler whereas the builtin drivers do. In general you can't assume drivers have that handler.

  9. iliaal commented on Oct 16, 2026

    @iliaal
    Member

    Not quite, I don't think closer is the trigger: executed is cleared on both branches, and oci_stmt_execute() treats !executed as a first execution and reallocates its columns while core keeps the old descriptions. pdo_ibm has no closer either and doesn't crash; it hits the same first-execution path and leaks its column descriptors instead.

  10. iliaal commented on Oct 16, 2026

    @iliaal
    Member

    As far as I can tell, oci is the only driver that crashes (pdo_ibm leaks instead), but that is not a reason not to fix it.

  11. iliaal commented on Oct 16, 2026

    @iliaal
    Member

    Looks like there is even a separate (unrelated, older crash), calling closeCursor() and then execute() crashes pdo_oci

  12. rainerjung commented on Oct 16, 2026

    @rainerjung
    Author

    Thanks a lot for your investigation.
    I can confirm, that the two fixes - one in PDO and one in pdo_oci - fixes the issue for me. My test case runs fine. Also the unit tests for pdo_oci, pdo_mysql, pdo_pgsql and pdo_sqlite do not show any new failures.
    Tested with PHP 8.6.0RC3 / pdo_oci 1.2.0 plus those patches and #41 for pdo_oci.
    I had to slightly adjust the patch for 8.6 though.

  13. iliaal commented on Oct 16, 2026

    @iliaal
    Member

    Yeah, it needs tweaking as it is upmerged, thanks for validating it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions