Skip to content

Commit c6b058b

Browse files
committed
Fix memory leaks when returning refcounted value from curl callback
Closes GH-18883.
1 parent 64bc12c commit c6b058b

File tree

5 files changed

+52
-6
lines changed

5 files changed

+52
-6
lines changed

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ PHP NEWS
66
. Fixed bug GH-18833 (Use after free with weakmaps dependent on destruction
77
order). (Daniil Gentili)
88

9+
- Curl:
10+
. Fix memory leaks when returning refcounted value from curl callback.
11+
(nielsdos)
12+
913
03 Jul 2025, PHP 8.3.23
1014

1115
- Core:

ext/curl/curl_private.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,9 @@ void _php_curl_multi_cleanup_list(void *data);
147147
void _php_curl_verify_handlers(php_curl *ch, bool reporterror);
148148
void _php_setup_easy_copy_handlers(php_curl *ch, php_curl *source);
149149

150+
/* Consumes `zv` */
151+
zend_long php_curl_get_long(zval *zv);
152+
150153
static inline php_curl *curl_from_obj(zend_object *obj) {
151154
return (php_curl *)((char *)(obj) - XtOffsetOf(php_curl, std));
152155
}

ext/curl/interface.c

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,7 @@ static size_t curl_write(char *data, size_t size, size_t nmemb, void *ctx)
623623
length = -1;
624624
} else if (!Z_ISUNDEF(retval)) {
625625
_php_curl_verify_handlers(ch, /* reporterror */ true);
626-
length = zval_get_long(&retval);
626+
length = php_curl_get_long(&retval);
627627
}
628628

629629
zval_ptr_dtor(&argv[0]);
@@ -667,7 +667,7 @@ static int curl_fnmatch(void *ctx, const char *pattern, const char *string)
667667
php_error_docref(NULL, E_WARNING, "Cannot call the CURLOPT_FNMATCH_FUNCTION");
668668
} else if (!Z_ISUNDEF(retval)) {
669669
_php_curl_verify_handlers(ch, /* reporterror */ true);
670-
rval = zval_get_long(&retval);
670+
rval = php_curl_get_long(&retval);
671671
}
672672
zval_ptr_dtor(&argv[0]);
673673
zval_ptr_dtor(&argv[1]);
@@ -715,7 +715,7 @@ static size_t curl_progress(void *clientp, double dltotal, double dlnow, double
715715
php_error_docref(NULL, E_WARNING, "Cannot call the CURLOPT_PROGRESSFUNCTION");
716716
} else if (!Z_ISUNDEF(retval)) {
717717
_php_curl_verify_handlers(ch, /* reporterror */ true);
718-
if (0 != zval_get_long(&retval)) {
718+
if (0 != php_curl_get_long(&retval)) {
719719
rval = 1;
720720
}
721721
}
@@ -764,7 +764,7 @@ static size_t curl_xferinfo(void *clientp, curl_off_t dltotal, curl_off_t dlnow,
764764
php_error_docref(NULL, E_WARNING, "Cannot call the CURLOPT_XFERINFOFUNCTION");
765765
} else if (!Z_ISUNDEF(retval)) {
766766
_php_curl_verify_handlers(ch, /* reporterror */ true);
767-
if (0 != zval_get_long(&retval)) {
767+
if (0 != php_curl_get_long(&retval)) {
768768
rval = 1;
769769
}
770770
}
@@ -821,6 +821,7 @@ static int curl_ssh_hostkeyfunction(void *clientp, int keytype, const char *key,
821821
}
822822
} else {
823823
zend_throw_error(NULL, "The CURLOPT_SSH_HOSTKEYFUNCTION callback must return either CURLKHMATCH_OK or CURLKHMATCH_MISMATCH");
824+
zval_ptr_dtor(&retval);
824825
}
825826
}
826827
zval_ptr_dtor(&argv[0]);
@@ -938,7 +939,7 @@ static size_t curl_write_header(char *data, size_t size, size_t nmemb, void *ctx
938939
length = -1;
939940
} else if (!Z_ISUNDEF(retval)) {
940941
_php_curl_verify_handlers(ch, /* reporterror */ true);
941-
length = zval_get_long(&retval);
942+
length = php_curl_get_long(&retval);
942943
}
943944
zval_ptr_dtor(&argv[0]);
944945
zval_ptr_dtor(&argv[1]);
@@ -1290,6 +1291,17 @@ void _php_setup_easy_copy_handlers(php_curl *ch, php_curl *source)
12901291
(*source->clone)++;
12911292
}
12921293

1294+
zend_long php_curl_get_long(zval *zv)
1295+
{
1296+
if (EXPECTED(Z_TYPE_P(zv) == IS_LONG)) {
1297+
return Z_LVAL_P(zv);
1298+
} else {
1299+
zend_long ret = zval_get_long(zv);
1300+
zval_ptr_dtor(zv);
1301+
return ret;
1302+
}
1303+
}
1304+
12931305
#if LIBCURL_VERSION_NUM >= 0x073800 /* 7.56.0 */
12941306
static size_t read_cb(char *buffer, size_t size, size_t nitems, void *arg) /* {{{ */
12951307
{

ext/curl/multi.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,7 @@ static int _php_server_push_callback(CURL *parent_ch, CURL *easy, size_t num_hea
430430
if (error == FAILURE) {
431431
php_error_docref(NULL, E_WARNING, "Cannot call the CURLMOPT_PUSHFUNCTION");
432432
} else if (!Z_ISUNDEF(retval)) {
433-
if (CURL_PUSH_DENY != zval_get_long(&retval)) {
433+
if (CURL_PUSH_DENY != php_curl_get_long(&retval)) {
434434
rval = CURL_PUSH_OK;
435435
zend_llist_add_element(&mh->easyh, &pz_ch);
436436
} else {
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
Returning refcounted value from callback must not leak
3+
--EXTENSIONS--
4+
curl
5+
--FILE--
6+
<?php
7+
include 'server.inc';
8+
$host = curl_cli_server_start();
9+
10+
$url = "{$host}/get.inc";
11+
$ch = curl_init($url);
12+
13+
function return_non_interned_string() {
14+
return str_repeat('x', random_int(5, 5));
15+
}
16+
17+
curl_setopt($ch, CURLOPT_NOPROGRESS, 0);
18+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
19+
curl_setopt($ch, CURLOPT_PROGRESSFUNCTION, 'return_non_interned_string');
20+
curl_setopt($ch, CURLOPT_XFERINFOFUNCTION, 'return_non_interned_string');
21+
curl_setopt($ch, CURLOPT_WRITEFUNCTION, 'return_non_interned_string');
22+
curl_setopt($ch, CURLOPT_HEADERFUNCTION, 'return_non_interned_string');
23+
echo curl_exec($ch), PHP_EOL;
24+
echo "ok";
25+
?>
26+
--EXPECT--
27+
ok

0 commit comments

Comments
 (0)