Skip to content

Crash when TLS authentication fails with verify_peer #21613

@bwoebi

Description

@bwoebi

Description

The following code:

<?php

// Start a local "proxy" that answers CONNECT with 200 then closes immediately,
// causing the SSL handshake to fail on the client side.
$server = stream_socket_server("tcp://127.0.0.1:0", $errno, $errstr);
if (!$server) {
    die("Could not start proxy server: $errstr\n");
}
$proxyAddr = stream_socket_get_name($server, false);

$pid = pcntl_fork();
if ($pid === -1) {
    die("pcntl_fork failed\n");
}

if ($pid === 0) {
    // Child: minimal HTTP CONNECT proxy
    $conn = stream_socket_accept($server, 5);
    if ($conn) {
        // Drain the CONNECT request headers
        while (($line = fgets($conn)) !== false) {
            if (rtrim($line) === '') {
                break;
            }
        }
        // Acknowledge the tunnel — then immediately close.
        // The client will attempt the TLS handshake and fail.
        fwrite($conn, "HTTP/1.1 200 Connection established\r\n\r\n");
        fclose($conn);
    }
    exit(0);
}

// Parent: HTTPS request through the proxy.
// 'verify_peer' => true ensures reset_ssl_peer_name is set (peer_name not pre-set).
// No cafile needed; the TLS handshake itself will fail because the proxy closed.
$ctx = stream_context_create([
    'ssl' => [
        'verify_peer'      => true,
        'verify_peer_name' => true,
    ],
    'http' => [
        'proxy' => "tcp://$proxyAddr",
    ],
]);

// Suppress the expected PHP warning; we only care about the crash.
@file_get_contents("https://127.0.0.1/", false, $ctx);

pcntl_waitpid($pid, $status);
echo "Done\n";

Resulted in this output:

     ==80357== Invalid read of size 8                                                                                       
     ==80357==    at 0x5980384: php_stream_url_wrap_http_ex (/usr/local/src/php/ext/standard/http_fopen_wrapper.c:580)       
     ==80357==    by 0x597F3AB: php_stream_url_wrap_http (/usr/local/src/php/ext/standard/http_fopen_wrapper.c:1204)       
     ==80357==    by 0xC5474B3: dd_stream_opener (handlers_httpstreams.c:112)             
     ==80357==    by 0xC546B7F: dd_stream_opener_https (handlers_httpstreams.c:130)
     ==80357==    by 0x5B5A657: _php_stream_open_wrapper_ex (/usr/local/src/php/main/streams/streams.c:2270)
     ==80357==    by 0x594B46F: zif_file_get_contents (/usr/local/src/php/ext/standard/file.c:409)
     ==80357==    by 0x57646DB: zif_phar_file_get_contents (/usr/local/src/php/ext/phar/func_interceptors.c:230)
     ==80357==    by 0x5D029FB: ZEND_DO_ICALL_SPEC_OBSERVER_HANDLER (zend_vm_execute.h:1487)
     ==80357==    by 0x5C7B50F: execute_ex (zend_vm_execute.h:116172)
     ==80357==    by 0x5C7B927: zend_execute (zend_vm_execute.h:121884)                                                                    
     ==80357==    by 0x5E09387: zend_execute_script (/usr/local/src/php/Zend/zend.c:1977)                                  
     ==80357==    by 0x5B23AE3: php_execute_script_ex (/usr/local/src/php/main/main.c:2641)
     ==80357==  Address 0x90 is not stack'd, malloc'd or (recently) free'd

The fix should be trivially putting the whole of

if (stream) {
char header_line[HTTP_HEADER_BLOCK_SIZE];
/* get response header */
while (php_stream_gets(stream, header_line, HTTP_HEADER_BLOCK_SIZE-1) != NULL) {
if (header_line[0] == '\n' ||
header_line[0] == '\r' ||
header_line[0] == '\0') {
break;
}
}
}
/* enable SSL transport layer */
if (stream) {
if (php_stream_xport_crypto_setup(stream, STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL) < 0 ||
php_stream_xport_crypto_enable(stream, 1) < 0) {
php_stream_wrapper_log_error(wrapper, options, "Cannot connect to HTTPS server through proxy");
php_stream_close(stream);
stream = NULL;
}
}
if (reset_ssl_peer_name) {
php_stream_context_unset_option(PHP_STREAM_CONTEXT(stream), "ssl", "peer_name");
}
into the same if (stream) branch.

PHP Version

PHP 8.4+

Introduced with 42f6c15.

Operating System

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions