Repository navigation
[RFC] ext/curl: Add CURLOPT_OPENSOCKETFUNCTION - #25414
xavierleune wants to merge 2 commits into
Conversation
|
I'd drop the enum and use an int port, as in #25410: function (CurlHandle $handle, string $address, int $port): boolThe address already tells IPv4, IPv6 and UNIX sockets apart, and On the implementation:
I opened #25410 before seeing this PR, with you as the commit author since it reuses your code. I'm fine closing either one, as long as we agree on the simpler signature. WDYT? (UPGRADING entries for curl are usually one paragraph, see |
Lets a callback allow or refuse each socket libcurl is about to create,
after DNS resolution and before connect():
function (CurlHandle $handle, string $address, int $port): bool
$address is the literal IP address, or the path of a UNIX domain
socket, whose $port is 0, as CURLINFO_PRIMARY_PORT reports. Returning
true creates the socket as libcurl would by default; false refuses the
connection, and libcurl tries the next address, if any. Any other
return value raises a TypeError. Endpoints that cannot be described to
the callback (other address families, other socket purposes) are
refused without calling it.
RFC: https://wiki.php.net/rfc/curl_opensocketfunction
e7ef9d6 to
30997a5
Compare
The callback is now function (CurlHandle $handle, string $address, int $port): bool, as in php/php-src#25414. A UNIX socket is reported with port 0, as CURLINFO_PRIMARY_PORT does.
nicolas-grekas
left a comment
There was a problem hiding this comment.
Thanks for the update, the code now matches #25410 so I'm closing mine.
The description still shows the enum and ?int $port.
The test that false makes libcurl try the next address might be worth taking from #25410. It uses CURLOPT_RESOLVE with two IPs, so it doesn't need a server: curl_opensocketfunction_next_address.phpt
…n exception An exception thrown after the callback returned true, e.g. by a destructor, left both true and a pending exception, so the socket was still created. Also take the test that refusing an address makes libcurl try the next one from php#25410. Co-authored-by: Nicolas Grekas <nicolas.grekas@gmail.com>
e2ecad9 to
a3d8a16
Compare
|
Thanks @nicolas-grekas, I've taken the test from #25410 (and co-authored the commit). Description has been updated too ! |
Description
RFC: https://wiki.php.net/rfc/curl_opensocketfunction (under discussion)
Discussion: "SSRF protection in curl", internals@lists.php.net
Supersedes #23657.
This adds
CURLOPT_OPENSOCKETFUNCTION, with libcurl's name and value. Its callback allows or refuses each socket libcurl is about to create, after DNS resolution and beforeconnect():This is the point where an SSRF policy can be enforced reliably: on the address actually about to be contacted, for every redirect hop and every address libcurl tries, whatever spelling the URL used and whatever DNS answered.
Semantics
$handlecomes first, as in the other ext/curl callbacks.curl_getinfo($handle, CURLINFO_EFFECTIVE_URL)tells which URL the address is for, redirects included.$addressis the literal IP address, or the path of a UNIX socket. An abstract socket name keeps its leading NUL byte.$portis 0 for a UNIX socket, asCURLOPT_PREREQFUNCTIONandCURLINFO_PRIMARY_PORTreport it.true: ext/curl creates the socket withsocket(family, socktype, protocol), which is what libcurl documents as the hook's default behaviour. Everything libcurl does next is unchanged.false: the connection is refused. libcurl tries the next address of the host, if any, and the transfer fails withCURLE_COULDNT_CONNECTonce none is left.TypeErroris raised and the connection is refused.nullremoves it;curl_reset()clears it;curl_copy_handle()andclonecarry it over;CURLOPT_PREREQFUNCTIONruns before every request, reused connections included. UPGRADING documents this, along with proxies andCURLOPT_PROTOCOLS_STR.No descriptor is ever exposed to userland, and ext/curl gains no dependency on ext/sockets. The return type can later be widened, and parameters appended, without breaking existing callbacks.
Tests
curl_multi, and libcurl trying the next address after a refusal.true.file://, which opens no socket.CURLOPT_FRESH_CONNECTandCURLOPT_PREREQFUNCTION. These use a small keep-alive server,keepalive_server.inc, since the CLI server closes every connection.cloneand reset, and a callback that removes itself.Polyfill
curl_hardening implements the same API for PHP 8.3 to 8.6. It stays inert on a PHP that provides the option natively.