Skip to content

Bug: Memory leak of the last response headers after a nested HTTP request - #24201

Merged
Girgias merged 2 commits into
php:PHP-8.4from
true-async:http-last-response-headers-leak
Oct 14, 2026
Merged

Girgias merged 2 commits into
php:PHP-8.4from
true-async:http-last-response-headers-leak

Conversation

@EdmondDantes

@EdmondDantes EdmondDantes commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

php_stream_url_wrap_http() releases BG(last_http_headers) before the request and copies the new headers over it after the request. A request made from the notification callback of a running one stores its own headers there in between, and the outer copy overwrote that array without releasing it.

Reproduction (debug build, no extensions beyond pcntl and posix for the test server):

$ctx = stream_context_create([], ['notification' => function ($code) use (&$nested, $uri) {
    if ($code === STREAM_NOTIFY_MIME_TYPE_IS && !$nested) {
        $nested = true;
        file_get_contents($uri);
    }
}]);
file_get_contents($uri, false, $ctx);

Expected: no leak. Actual: === Total 4 memory leaks detected === (the nested request's header array).

The fix releases the stored array once, after the request, instead of before it. After the call http_get_last_response_headers() returns the outer request's headers, as before. During the request, a notification callback now sees the previous request's headers instead of null.

Test: ext/standard/tests/http/http_response_header_nested_request.phpt (fails on PHP-8.4 debug without the fix with the leak report, passes with it). The leak report comes from debug builds; a release build reports the leak only under ASAN or valgrind.

php_stream_url_wrap_http() releases BG(last_http_headers) before the request
and copies the new headers over it after. A request made while the first one
runs, from its notification callback, stores its own headers there in between,
and the copy overwrote that array without releasing it.

@ndossche ndossche left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks.
This is a bit of a stupid edge case...
It also makes me wonder if the lines 1275-1276 should be moved to else branch of the check at line 1282; so we don't have to dtor the headers twice...

@EdmondDantes

Copy link
Copy Markdown
Contributor Author

It also makes me wonder if the lines 1275-1276 should be moved to else branch of the check at line 1282; so we don't have to dtor the headers twice...

Like this?

php_stream *php_stream_url_wrap_http(
    php_stream_wrapper *wrapper,
    const char *path,
    const char *mode,
    int options,
    zend_string **opened_path,
    php_stream_context *context STREAMS_DC)
{
    php_stream *stream;
    zval headers;

    ZVAL_UNDEF(&headers);

    stream = php_stream_url_wrap_http_ex(
        wrapper, path, mode, options, opened_path, context,
        PHP_URL_REDIRECT_MAX, HTTP_WRAPPER_HEADER_INIT, &headers STREAMS_CC);

    if (!Z_ISUNDEF(headers)) {
        /* A request made from a notification callback may have stored its own headers meanwhile. */
        zval_ptr_dtor(&BG(last_http_headers));
        ZVAL_COPY(&BG(last_http_headers), &headers);

        if (FAILURE == zend_set_local_var_str(
                "http_response_header", sizeof("http_response_header")-1, &headers, 0)) {
            zval_ptr_dtor(&headers);
        }
    } else {
        zval_ptr_dtor(&BG(last_http_headers));
        ZVAL_UNDEF(&BG(last_http_headers));
    }

    return stream;
}

@ndossche

Copy link
Copy Markdown
Member

Yes indeed

The release before the request is dropped: a single release after it covers
both the previous request's headers and those stored by a nested one.
@Girgias
Girgias merged commit 0ff02e8 into php:PHP-8.4 Oct 14, 2026
17 of 18 checks passed
@EdmondDantes
EdmondDantes deleted the http-last-response-headers-leak branch October 14, 2026 17:39
Girgias added a commit that referenced this pull request Oct 14, 2026
* PHP-8.6:
  Update NEWS
  ext/standard/tests: work around Windows quoting with tempfiles.
  .github/scripts/windows: pass an extension_dir to the test runner.
  {ext/standard,sapi/cli}/tests: pass TEST_PHP_EXTRA_ARGS to php
  ext/standard/tests: pass TEST_PHP_EXTRA_ARGS to some proc_open() tests
  sapi/cli/tests/023.phpt: support TEST_PHP_EXTRA_ARGS
  std: Fix memory leak of the last response headers after a nested HTTP request (#24201)
  Fiber: hold the object a callable resolved to in the creating frame (#24134)
Girgias added a commit to Girgias/php-src that referenced this pull request Oct 14, 2026
* PHP-8.4:
  Update NEWS
  ext/standard/tests: work around Windows quoting with tempfiles.
  .github/scripts/windows: pass an extension_dir to the test runner.
  {ext/standard,sapi/cli}/tests: pass TEST_PHP_EXTRA_ARGS to php
  ext/standard/tests: pass TEST_PHP_EXTRA_ARGS to some proc_open() tests
  sapi/cli/tests/023.phpt: support TEST_PHP_EXTRA_ARGS
  std: Fix memory leak of the last response headers after a nested HTTP request (php#24201)
  Fiber: hold the object a callable resolved to in the creating frame (php#24134)
Girgias added a commit to Girgias/php-src that referenced this pull request Oct 14, 2026
* PHP-8.5:
  Update NEWS
  ext/standard/tests: work around Windows quoting with tempfiles.
  .github/scripts/windows: pass an extension_dir to the test runner.
  {ext/standard,sapi/cli}/tests: pass TEST_PHP_EXTRA_ARGS to php
  ext/standard/tests: pass TEST_PHP_EXTRA_ARGS to some proc_open() tests
  sapi/cli/tests/023.phpt: support TEST_PHP_EXTRA_ARGS
  std: Fix memory leak of the last response headers after a nested HTTP request (php#24201)
  Fiber: hold the object a callable resolved to in the creating frame (php#24134)
arnaud-lb added a commit to heathdutton/php-src that referenced this pull request Oct 15, 2026
* up/PHP-8.4: (3595 commits)
  Fix Windows quoting in proc_open tests (php#24343)
  Pin macOS CI to OpenSSL 3 (php#24332)
  Zend: zend_call_stack_get_netbsd_pthread() pthread attribute leak.
  Revert "ext/standard/tests: work around Windows quoting with tempfiles."
  Update NEWS
  ext/standard/tests: work around Windows quoting with tempfiles.
  .github/scripts/windows: pass an extension_dir to the test runner.
  {ext/standard,sapi/cli}/tests: pass TEST_PHP_EXTRA_ARGS to php
  ext/standard/tests: pass TEST_PHP_EXTRA_ARGS to some proc_open() tests
  sapi/cli/tests/023.phpt: support TEST_PHP_EXTRA_ARGS
  std: Fix memory leak of the last response headers after a nested HTTP request (php#24201)
  Fiber: hold the object a callable resolved to in the creating frame (php#24134)
  Fix v142 ARM64 hash and floating point results (php#24317)
  Use the default archive names for Windows ARM64 builds (php#24313)
  ext/gd: Drop useless and doubtful MSVC specific code (libgd/libgd@f1480ab) (php#24316)
  ext/session: do not reuse session state a save handler tore down
  Fix phpGH-24293: Segfault with an empty password in MariaDB PAM (php#24294)
  Fix phpGH-24289: finfo::file() reports the wrong argument number
  Fix phpGH-24260: querySelectorAll() returns duplicate elements for selector lists
  Fix escape analysis for objects implementing ArrayAccess
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants