ext/curl: add socket callback options bridging to ext/sockets#22159
Open
xavierleune wants to merge 1 commit into
Open
ext/curl: add socket callback options bridging to ext/sockets#22159xavierleune wants to merge 1 commit into
xavierleune wants to merge 1 commit into
Conversation
Expose libcurl's CURLOPT_SOCKOPTFUNCTION, CURLOPT_OPENSOCKETFUNCTION and
CURLOPT_CLOSESOCKETFUNCTION, letting userland hook into socket creation,
configuration and teardown. These are useful for application security, in
particular SSRF protection (validating the resolved address before connecting)
and low-level socket hardening.
Following the existing curl_write_header / curl_prereqfunction bridge model, the
callbacks exchange ext/sockets Socket objects so they are fully usable in pure
PHP (socket_create/socket_bind, socket_set_option, socket_close):
- sockopt: fn(CurlHandle $ch, Socket $socket, int $purpose): int
returns CURL_SOCKOPT_OK / _ERROR / _ALREADY_CONNECTED
- opensocket: fn(CurlHandle $ch, int $purpose, array $address): Socket|false
$address = [family, socktype, protocol, ip, port];
returning false aborts the connection (CURL_SOCKET_BAD)
- closesocket: fn(CurlHandle $ch, Socket $socket): void
This is achieved through the C API exported by ext/sockets (socket_ce, the
php_socket struct and socket_import_file_descriptor()), so ext/curl now depends
on ext/sockets. The whole feature is guarded by HAVE_SOCKETS; without the
sockets extension curl still builds, just without these options.
Notable details:
- Descriptors owned by libcurl are detached (bsd_socket = -1) before the
temporary Socket object is released, to avoid a double close.
- The close-socket callback is disabled before curl_easy_cleanup() so libcurl
closes pooled sockets natively rather than calling into PHP during handle
destruction.
- Setting an option to null restores libcurl's native default.
New constants: CURLOPT_SOCKOPTFUNCTION, CURLOPT_OPENSOCKETFUNCTION,
CURLOPT_CLOSESOCKETFUNCTION, CURL_SOCKOPT_OK, CURL_SOCKOPT_ERROR,
CURL_SOCKOPT_ALREADY_CONNECTED, CURLSOCKTYPE_IPCXN, CURLSOCKTYPE_ACCEPT.
e34e404 to
3ed7c46
Compare
Author
|
@Girgias 👋 hi gina, do you mind having a look on this one ? |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR exposes libcurl's three socket-level callback options, which were previously unavailable in PHP:
CURLOPT_SOCKOPTFUNCTION— invoked after a socket is created but before it is connected, to tune low-level socket options.CURLOPT_OPENSOCKETFUNCTION— invoked to create the socket for a connection, after the address has been resolved but beforeconnect().CURLOPT_CLOSESOCKETFUNCTION— invoked when libcurl is done with a socket.The main motivation is application security.
CURLOPT_OPENSOCKETFUNCTIONin particular lets an application inspect the resolved IP address and refuse the connection, which is the robust way to implement SSRF protection (it happens after DNS resolution, so it also defeats DNS-rebinding to internal addresses — something hostname/URL allow-listing cannot do).CURLOPT_SOCKOPTFUNCTIONallows socket hardening (SO_BINDTODEVICE, keep-alive, packet marks, …).API
The C callbacks take/return a raw
curl_socket_tfile descriptor, which pure PHP cannot create or read. To make the options usable from plain PHP, the callbacks exchange ext/socketsSocketobjects, built on top of the C API alreadyexported by ext/sockets (
socket_ce, thephp_socketstruct andsocket_import_file_descriptor()):Example: blocking private/reserved IPs (SSRF protection)
Because
CURLOPT_OPENSOCKETFUNCTIONreceives the resolved address, it can reject any request that would reach a private or reserved range — even if the attacker supplied a public hostname that resolves to an internal IP:Implementation notes
HAVE_SOCKETS. ext/curl gains aZEND_MOD_REQUIRED("sockets")dependency (plusPHP_ADD_EXTENSION_DEP); when built without ext/sockets, curl still builds, just without these options.bsd_socket = -1) before the temporarySocketobject is released, to avoid a double close.CURLOPT_CLOSESOCKETFUNCTIONis disabled right beforecurl_easy_cleanup(), so libcurl closes pooled sockets natively instead of calling into PHP while the handle is being destroyed (which can happen during GC/shutdown). The callback still fires for sockets closed during a transfer.nullrestores libcurl's native default.CURLOPT_SOCKOPTFUNCTION,CURLOPT_OPENSOCKETFUNCTION,CURLOPT_CLOSESOCKETFUNCTION,CURL_SOCKOPT_OK,CURL_SOCKOPT_ERROR,CURL_SOCKOPT_ALREADY_CONNECTED,CURLSOCKTYPE_IPCXN,CURLSOCKTYPE_ACCEPT.Tests
Added
.phptcoverage for each option (success, abort/error paths, invalid return types/values,null,curl_copy_handle) plus a trampoline test. The fullext/curlsuite passes with no regressions.Fixes: https://bugs.php.net/bug.php?id=62906