PHP :: Bug #76675 :: Segfault with H2 server push
php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #76675 Segfault with H2 server push
Submitted: 2018-07-28 00:12 UTC Modified: 2018-07-29 11:42 UTC
Votes:1
Avg. Score:5.0 ± 0.0
Reproduced:0 of 0 (0.0%)
From: tobias dot nyholm at gmail dot com Assigned:
Status: Open Package: cURL related
PHP Version: 7.2.8 OS: OSX
Private report: No CVE-ID: None
Have you experienced this issue?
Rate the importance of this bug to you:

 [2018-07-28 00:12 UTC] tobias dot nyholm at gmail dot com
Description:
------------
When experimenting with HTTP2 server push I found some issues. 

1) It does not work when using `CURLOPT_TIMEOUT`
2) Segmentation fault when using `CURLOPT_WRITEFUNCTION` and `CURLOPT_HEADERFUNCTION`
3) Segmentation fault when using `CURLOPT_WRITEFUNCTION` and `CURLOPT_HEADERFUNCTION` in `CURLMOPT_PUSHFUNCTION`. 

I started with a working example provided by Davey: https://github.com/dshafik/php-http2-push-example

Please note my 3 FIXME comments in the test script. 

Im on PHP 7.2.8 with cUrl 7.61.0. 

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

$urls = [];

function parseUrl($headers, $handle)
{
    global $urls;
    foreach ($headers as $header) {
        if (strpos($header, ':path:') === 0) {
            $path = substr($header, 6);
            $url = curl_getinfo($handle)['url'];
            $url = str_replace(
                parse_url($url, PHP_URL_PATH),
                $path,
                $url
            );
            $urls[$url] = $handle;
        }
    }
}

function getUrl($handle)
{
    $found = false;
    global $urls;
    foreach ($urls as $url => $h) {
        if ($handle == $h) {
            $found = $url;
        }
    }
    if (!$found) {
        $found = curl_getinfo($handle)['url'];
    }

    return $found;
}


function get_request($url)
{
    $cb = function ($parent, $pushed, $headers) {
        curl_setopt($pushed, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($pushed, CURLOPT_HEADER, true);

        //FIXME These two lines will cause segmentation fault
        //curl_setopt($pushed, CURLOPT_HEADERFUNCTION, null);
        //curl_setopt($pushed, CURLOPT_WRITEFUNCTION, null);

        parseUrl($headers, $pushed);

        return CURL_PUSH_OK;
    };

    $mh = curl_multi_init();

    curl_multi_setopt($mh, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
    curl_multi_setopt($mh, CURLMOPT_PUSHFUNCTION, $cb);

    $curl = curl_init();
    curl_reset($curl);
    curl_setopt($curl, CURLOPT_URL, $url);
    curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2);
    curl_setopt($curl, CURLOPT_FOLLOWLOCATION, false);
    curl_setopt($curl, CURLOPT_MAXREDIRS, 0);
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 1);
    curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);
    curl_setopt($curl, CURLOPT_HTTPGET, true);
    curl_setopt($curl, CURLOPT_CUSTOMREQUEST, true);

    curl_setopt($curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
    curl_setopt($curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
    curl_setopt($curl, CURLOPT_HEADER, false);
    curl_setopt($curl, CURLOPT_FAILONERROR, false);
    $originalResponseContent = '';


    //FIXME Using timeout will weirdly disable pushed responses (content will be empty)
    //curl_setopt($curl, CURLOPT_TIMEOUT, 30);

    // -----------
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, false);
    //FIXME These 2 lines will case segmentation fault
    curl_setopt($curl, CURLOPT_HEADERFUNCTION, function ($ch, $data) {
        return strlen($data);
    });
    curl_setopt($curl, CURLOPT_WRITEFUNCTION, function ($ch, $data) use (&$originalResponseContent) {
        $originalResponseContent .= $data;
        return strlen($data);
    });

    curl_multi_add_handle($mh, $curl);


    // Start fetching the responses.
    $content = null;
    $active = null;
    do {
        $mrc = curl_multi_exec($mh, $active);
    } while ($mrc == CURLM_CALL_MULTI_PERFORM);

    while ($active && $mrc == CURLM_OK) {
        curl_multi_select($mh);
        do {
            $mrc = curl_multi_exec($mh, $active);
        } while ($mrc == CURLM_CALL_MULTI_PERFORM);
        while ($info = curl_multi_info_read($mh))
        {
            if ($info['msg'] == CURLMSG_DONE) {
                $handle = $info['handle'];
                if ($handle !== null) {
                    $content = curl_multi_getcontent($handle);
                    $url = getUrl($handle);

                    // Debug
                    echo strlen($content).': '.$url."\n";

                    curl_multi_remove_handle($mh, $handle);
                    curl_close($handle);
                }
            }
        }


    }

    curl_multi_close($mh);

    return $originalResponseContent;
}

$url = 'https://http2.golang.org/serverpush';
$response = get_request($url);

echo strlen($response).': '.$url."\n\n";



Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2018-07-29 11:36 UTC] rasmus@php.net
I am able to reproduce the segfault. Interestingly it doesn't crash under Valgrind, nor is it giving me any clues about the crash, which is odd. The gdb backtrace is useful though (against 7.3):

Thread 1 "php" received signal SIGSEGV, Segmentation fault.
curl_write_header (data=0x555557c48c60 "HTTP/2 200 \r\n", size=1, nmemb=13, ctx=0x7fffec266040)
    at /home/rasmus/php-src/ext/curl/interface.c:1654
1654				GC_ADDREF(ch->res);
(gdb) bt
#0  curl_write_header (data=0x555557c48c60 "HTTP/2 200 \r\n", size=1, nmemb=13, ctx=0x7fffec266040)
    at /home/rasmus/php-src/ext/curl/interface.c:1654
#1  0x00007ffff36f22bd in ?? () from /usr/lib/x86_64-linux-gnu/libcurl-gnutls.so.4
#2  0x00007ffff36f0754 in ?? () from /usr/lib/x86_64-linux-gnu/libcurl-gnutls.so.4
#3  0x00007ffff3709a78 in ?? () from /usr/lib/x86_64-linux-gnu/libcurl-gnutls.so.4
#4  0x00007ffff3714726 in ?? () from /usr/lib/x86_64-linux-gnu/libcurl-gnutls.so.4
#5  0x00007ffff3715361 in curl_multi_perform () from /usr/lib/x86_64-linux-gnu/libcurl-gnutls.so.4
#6  0x0000555555838f4e in zif_curl_multi_exec (execute_data=<optimized out>, return_value=0x7fffec21d190)
    at /home/rasmus/php-src/ext/curl/multi.c:278
#7  0x0000555555ba64e8 in ZEND_DO_ICALL_SPEC_RETVAL_USED_HANDLER () at /home/rasmus/php-src/Zend/zend_vm_execute.h:690
#8  execute_ex (ex=0x28) at /home/rasmus/php-src/Zend/zend_vm_execute.h:54894
#9  0x0000555555baef55 in zend_execute (op_array=op_array@entry=0x7fffec27d2a0, 
    return_value=return_value@entry=0x7fffdc0d58d8) at /home/rasmus/php-src/Zend/zend_vm_execute.h:60326
#10 0x0000555555b23f65 in zend_execute_scripts (type=-333328176, type@entry=8, retval=0x7fffdc0d58d8, 
    retval@entry=0x0, file_count=file_count@entry=3) at /home/rasmus/php-src/Zend/zend.c:1564
#11 0x0000555555ac6b50 in php_execute_script (primary_file=0x7fffffffcf10) at /home/rasmus/php-src/main/main.c:2608
#12 0x0000555555bb12dd in do_cli (argc=2, argv=0x555556706330) at /home/rasmus/php-src/sapi/cli/php_cli.c:1002
#13 0x00005555556cba76 in main (argc=2, argv=0x555556706330) at /home/rasmus/php-src/sapi/cli/php_cli.c:1395
 [2018-07-29 11:42 UTC] rasmus@php.net
In that backtrace, ch looks like a valid curl handle, but ch->res is null at that point.

(gdb) p ch
$1 = (php_curl *) 0x7fffec265c80
(gdb) p *ch
$2 = {cp = 0x5555569af080, handlers = 0x7fffec25f348, res = 0x0, to_free = 0x7fffec28f000, header = {str = 0x0}, 
  err = {str = '\000' <repeats 256 times>, no = 0}, in_callback = 0 '\000', clone = 0x7fffec28a010}
 
PHP Copyright © 2001-2018 The PHP Group
All rights reserved.
Last updated: Thu Aug 16 12:01:24 2018 UTC