Re: [RFC] Vetting cURL connections with CURLOPT_OPENSOCKETFUNCTION
| From: | Nicolas Grekas | Date: | Thu, 12 Nov 2026 10:56:49 +0000 |
| Subject: | Re: [RFC] Vetting cURL connections with CURLOPT_OPENSOCKETFUNCTION | ||
| References: | 1 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-133086@lists.php.net to get a copy of this message | ||
Hi Xavier,
thanks for the RFC
Le jeu. 12 nov. 2026 à 11:45, Xavier Leune <xavier.leune+internals@gmail.com>
a écrit :
> Hi internals,
>
> Following the "SSRF protection in curl" thread, I'd like to start the
> discussion on this RFC:
> https://wiki.php.net/rfc/curl_opensocketfunction
>
> It adds CURLOPT_OPENSOCKETFUNCTION to ext/curl, with libcurl's name and
> value. The callback is invoked after DNS resolution and before connect(),
> once for each socket libcurl is about to create. It returns a bool that
> allows or refuses the connection:
>
> curl_setopt($ch, CURLOPT_OPENSOCKETFUNCTION,
> function (CurlHandle $handle, CurlAddressFamily $family, string
> $address, ?int $port): bool {
> // Only publicly routable addresses.
> return $family !== CurlAddressFamily::Unix
> && filter_var($address, FILTER_VALIDATE_IP,
> FILTER_FLAG_GLOBAL_RANGE) !== false;
> });
>
> This is the point where an SSRF policy can be enforced reliably. It
> applies to the address actually about to be contacted, on every redirect
> hop and for every address libcurl tries, whatever spelling the URL used and
> whatever DNS answered.
>
> Compared to the earlier proposals:
>
> - It returns a bool, as Sjoerd suggested in the thread. No socket is
> exposed to userland, and ext/curl gains no dependency on ext/sockets.
> - It keeps libcurl's option name, rather than a PHP-specific one.
> - The handle comes first, as in the other ext/curl callbacks. A new
> CurlAddressFamily enum (IpV4, IpV6, Unix) tells a policy whether $address
> is an IP or a socket path. $port is null for UNIX sockets.
> - The return type can later be widened, and parameters appended, without
> breaking existing callbacks.
>
> The implementation is in
> https://github.com/php/php-src/pull/25414, which
> supersedes GH-23657.
>
> A polyfill, curl_hardening (https://github.com/xavierleune/curl-hardening),
> implements the same API for PHP 8.3 to 8.6. It stays inert on a PHP that
> provides the option natively.
>
> As discussed with Levi, streams are left to a separate RFC (see Future
> Scope).
>
> Thanks to everyone who took part in the previous thread and reviewed the
> PRs. Feedback is welcome.
>
> Regards,
> Xavier
>
As I put on the PR, I'd drop the enum and use an int port:
function (CurlHandle $handle, string $address, int $port): bool
The address already tells IPv4, IPv6 and UNIX sockets apart, and
filter_var() rejects a socket path anyway. The enum can even mislead:
::ffff:127.0.0.1 is reported as IpV6.
For UNIX sockets, port 0 is what CURLOPT_PREREQFUNCTION and
CURLINFO_PRIMARY_PORT already report/. Using null would be inconsistent and
thus surprizing.
Cheers,
Nicolas