Skip to content

Commit ac37a97

Browse files
Sjorddevnexen
authored andcommitted
ext/curl: error when curl read func returns unexpected long
Raise a value error when the callback registered with CURLOPT_READFUNCTION returns an unexpected long. The function registered with CURLOPT_READFUNCTION should return a string. PHP then writes that string to a buffer and returns the length, so that curl can read that many bytes from the buffer. The function can also return CURL_READFUNC_ABORT and CURL_READFUNC_PAUSE, so it also supports returning longs. However, when it returns a long other than these two constants, it is interpreted as a length. PHP does not update the buffer, but does instruct curl it can read that many bytes from the buffer. It reads whatever uninitialized data that is in the buffer and sends it over the line to the server. This seems bad, so validate the return value of the read function and raise an error. Returning 0 is a bit of an edge case. It is not documented but does results in correct behavior (i.e. end-of-file). So we accept that, but don't advertise it as valid in the error message. Related to #10270 Close GH-22757
1 parent c48fc1e commit ac37a97

7 files changed

Lines changed: 75 additions & 2 deletions

File tree

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ PHP NEWS
66
. Changed run-tests.php to run test subprocesses without a shell where
77
possible. (NickSdot)
88

9+
- Curl:
10+
. Raise a value error when the callback registered with CURLOPT_READFUNCTION
11+
returns an unexpected long. (Sjoerd Langkemper)
12+
913
- Date:
1014
. Update timelib to 2026.01. (Derick, timwolla)
1115

UPGRADING

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ PHP 8.6 UPGRADE NOTES
2424
has materialised the property by writing into the property table.
2525
The freshly-written value is returned directly. isset() is unaffected.
2626

27+
- Curl:
28+
. The callback registered with CURLOPT_READFUNCTION now throws a ValueError
29+
when returning an integer other than 0, CURL_READFUNC_ABORT or
30+
CURL_READFUNC_PAUSE.
31+
2732
- COM
2833
. It is no longer possible to clone variant objects, this is because
2934
the cloning behaviour was ill defined.
@@ -602,6 +607,7 @@ PHP 8.6 UPGRADE NOTES
602607
. CURL_SEEKFUNC_OK.
603608
. CURL_SEEKFUNC_FAIL.
604609
. CURL_SEEKFUNC_CANTSEEK.
610+
. CURL_READFUNC_ABORT.
605611

606612
- OpenSSL:
607613
. OPENSSL_RSA_PSS_SALTLEN_DIGEST.

ext/curl/curl.stub.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1788,6 +1788,11 @@
17881788
* @cvalue CURLPAUSE_SEND_CONT
17891789
*/
17901790
const CURLPAUSE_SEND_CONT = UNKNOWN;
1791+
/**
1792+
* @var int
1793+
* @cvalue CURL_READFUNC_ABORT
1794+
*/
1795+
const CURL_READFUNC_ABORT = UNKNOWN;
17911796
/**
17921797
* @var int
17931798
* @cvalue CURL_READFUNC_PAUSE

ext/curl/curl_arginfo.h

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/curl/interface.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -819,7 +819,13 @@ static size_t curl_read(char *data, size_t size, size_t nmemb, void *ctx)
819819
length = MIN(nmemb, Z_STRLEN(retval));
820820
memcpy(data, Z_STRVAL(retval), length);
821821
} else if (Z_TYPE(retval) == IS_LONG) {
822-
length = Z_LVAL_P(&retval);
822+
zend_long long_rv = Z_LVAL_P(&retval);
823+
if (long_rv == 0 || long_rv == CURL_READFUNC_ABORT || long_rv == CURL_READFUNC_PAUSE) {
824+
length = (size_t) long_rv;
825+
} else {
826+
zend_value_error("The CURLOPT_READFUNCTION callback must return a string or CURL_READFUNC_ABORT or CURL_READFUNC_PAUSE");
827+
length = CURL_READFUNC_ABORT;
828+
}
823829
}
824830
// TODO Do type error if invalid type?
825831
zval_ptr_dtor(&retval);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
--TEST--
2+
error when CURLOPT_READFUNCTION returns an integer
3+
--EXTENSIONS--
4+
curl
5+
--FILE--
6+
<?php
7+
function custom_readfunction($oCurl, $hReadHandle, $iMaxOut)
8+
{
9+
static $size = 2;
10+
return $size--;
11+
}
12+
13+
include 'server.inc';
14+
$host = curl_cli_server_start();
15+
$ch = curl_init();
16+
curl_setopt($ch, CURLOPT_URL, "{$host}/get.php?test=post");
17+
curl_setopt($ch, CURLOPT_POST, ['f' => 'f']);
18+
curl_setopt($ch, CURLOPT_TIMEOUT, 2);
19+
curl_setopt($ch, CURLOPT_READFUNCTION, "custom_readfunction" );
20+
21+
try {
22+
curl_exec($ch);
23+
} catch (ValueError $e) {
24+
echo $e->getMessage() . "\n";
25+
}
26+
var_dump(curl_error($ch));
27+
?>
28+
--EXPECT--
29+
The CURLOPT_READFUNCTION callback must return a string or CURL_READFUNC_ABORT or CURL_READFUNC_PAUSE
30+
string(29) "operation aborted by callback"
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
Returning CURL_READFUNC_ABORT aborts the transfer
3+
--EXTENSIONS--
4+
curl
5+
--FILE--
6+
<?php
7+
include 'server.inc';
8+
$host = curl_cli_server_start();
9+
10+
$ch = curl_init();
11+
curl_setopt($ch, CURLOPT_URL, "{$host}/get.inc");
12+
curl_setopt($ch, CURLOPT_POST, 1);
13+
curl_setopt($ch, CURLOPT_READFUNCTION, function () {
14+
return CURL_READFUNC_ABORT;
15+
});
16+
curl_exec($ch);
17+
18+
echo "No output expected, because transfer was aborted by read function.\n";
19+
?>
20+
--EXPECT--
21+
No output expected, because transfer was aborted by read function.

0 commit comments

Comments
 (0)