Skip to content

ext/curl: build_mime_structure_from_hash(): hash iterator dangling after __toString-triggered rehash #25096

Open

Description

@cxxz16

Description

Category: heap-use-after-free READ; CWE-416.
Context: curl_setopt(CURLOPT_POSTFIELDS, $arr) where $arr is a nested
array (['a' => &$sub]) whose inner array is shared with refcount 1, and a
value in the inner array is an object whose __toString() enlarges the
inner array, forcing a rehash while ZEND_HASH_FOREACH iterates it.

Observed: ASan reports heap-use-after-free READ in zval_get_type
(Zend/zend_types.h:685) at build_mime_structure_from_hash
(ext/curl/interface.c:1581), reached from _php_curl_setopt; the freed
320-byte region is the inner array's bucket storage (arData).

Relevant source: ext/curl/interface.c:1568 (ZEND_HASH_FOREACH over the
POSTFIELDS array), :1581 (zval_get_tmp_string invoking __toString);
same unprotected iteration pattern also present at :2168 (slist options)
and :2399 (curl_setopt_array).

Source assessment: the iteration holds no protection against the array
being modified from inside the string conversion callback; the rehash
frees the bucket array the iterator still points into.

Expected: hold a reference/separation on the traversed array, or detect
modification during iteration, as done elsewhere in the engine
(HASH_OF / HASH_PROTECT_RECURSION style guards).

Evidence: asan/CURL-01-postfields-rehash-uaf.asan.txt.
PoC: poc/01-postfields-rehash/poc.php. Note: the inner array must have
refcount 1 (reference-nested) — a top-level array is COW-protected.

CURL-01-postfields-rehash-uaf.asan.txt

poc.php

PHP Version

Component: ext/curl in php-src master, commit b9ea38ef639f5b9bbb67d27834be9c52b922520b (cloned 2026-10-21).
PHP: 8.7.0-dev (cli), NTS.
Platform: Linux x86-64 (Ubuntu 22.04 container, Docker), libcurl 7.81.
Sanitizer: AddressSanitizer (-fsanitize=address -g -O1).
Configure: --disable-all --enable-cli --with-curl (plus phar/zlib/dom/simplexml/fileinfo/mbstring/sqlite3/openssl/soap/mysqli/pdo/pdo_pgsql/snmp/ftp in the shared image).

Recorded runtime environment:
  USE_ZEND_ALLOC=0
  ZEND_DONT_UNLOAD_MODULES=1
  ASAN_OPTIONS=abort_on_error=0:detect_leaks=0

Operating System

No response

Activity

  1. windshock commented on Nov 4, 2026

    @windshock

    Following up with independent verification data from our lab (no action requested — just measurements).

    We ran the original PoC from this report (downloaded as-is, USE_ZEND_ALLOC=0 + ASan, 3 runs each) against the current release lines and master:

    build result
    8.4.27 (release, asanext-curl) heap-use-after-free 3/3 at build_mime_structure_from_hash
    8.5.12 (release, asanext-curl) heap-use-after-free 3/3 (same site)
    master (asanext-curl, built 2026-11-04) heap-use-after-free 3/3 (same site)

    All three builds confirmed the UAF with the exact site you reported (build_mime_structure_from_hash at interface.c). This confirms the issue is still unfixed across 8.4.27, 8.5.12, and master as of 2026-11-04.

    One additional observation from minimizing the reproducer: the exact 2000 iterations are not required. The condition is that the __toString() growth pushes the inner array past its first resize (HT_MIN_SIZE 8 → the 9th insertion reallocates and frees the bucket storage), and that at least one element follows the __toString object in the inner array (if it is the last element, the foreach cache count reaches zero before the stale pointer is dereferenced — we could not trigger the UAF even with 2000 iterations in that layout). The reference may sit in any slot of the outer array. This might be useful for anyone writing a regression test (e.g. inner array of 2 elements + growth of 7 with one trailing element is sufficient).

    If a fix lands, we can re-run the original PoC against the patch branches and report results.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions