diff --git a/NEWS b/NEWS index 5a70b313c97..5605ce5ffdf 100644 --- a/NEWS +++ b/NEWS @@ -6,6 +6,10 @@ PHP NEWS . Fixed bug GH-18833 (Use after free with weakmaps dependent on destruction order). (Daniil Gentili) +- Curl: + . Fix memory leaks when returning refcounted value from curl callback. + (nielsdos) + 03 Jul 2025, PHP 8.4.9 - BcMath: diff --git a/ext/curl/curl_private.h b/ext/curl/curl_private.h index 19e43094574..2635327f422 100644 --- a/ext/curl/curl_private.h +++ b/ext/curl/curl_private.h @@ -139,6 +139,9 @@ void _php_curl_multi_cleanup_list(void *data); void _php_curl_verify_handlers(php_curl *ch, bool reporterror); void _php_setup_easy_copy_handlers(php_curl *ch, php_curl *source); +/* Consumes `zv` */ +zend_long php_curl_get_long(zval *zv); + static inline php_curl *curl_from_obj(zend_object *obj) { return (php_curl *)((char *)(obj) - XtOffsetOf(php_curl, std)); } diff --git a/ext/curl/interface.c b/ext/curl/interface.c index 3b13744abce..efccb8c34d0 100644 --- a/ext/curl/interface.c +++ b/ext/curl/interface.c @@ -600,7 +600,7 @@ static size_t curl_write(char *data, size_t size, size_t nmemb, void *ctx) if (!Z_ISUNDEF(retval)) { _php_curl_verify_handlers(ch, /* reporterror */ true); /* TODO Check callback returns an int or something castable to int */ - length = zval_get_long(&retval); + length = php_curl_get_long(&retval); } zval_ptr_dtor(&argv[0]); @@ -633,7 +633,7 @@ static int curl_fnmatch(void *ctx, const char *pattern, const char *string) if (!Z_ISUNDEF(retval)) { _php_curl_verify_handlers(ch, /* reporterror */ true); /* TODO Check callback returns an int or something castable to int */ - rval = zval_get_long(&retval); + rval = php_curl_get_long(&retval); } zval_ptr_dtor(&argv[0]); zval_ptr_dtor(&argv[1]); @@ -670,7 +670,7 @@ static size_t curl_progress(void *clientp, double dltotal, double dlnow, double if (!Z_ISUNDEF(retval)) { _php_curl_verify_handlers(ch, /* reporterror */ true); /* TODO Check callback returns an int or something castable to int */ - if (0 != zval_get_long(&retval)) { + if (0 != php_curl_get_long(&retval)) { rval = 1; } } @@ -708,7 +708,7 @@ static size_t curl_xferinfo(void *clientp, curl_off_t dltotal, curl_off_t dlnow, if (!Z_ISUNDEF(retval)) { _php_curl_verify_handlers(ch, /* reporterror */ true); /* TODO Check callback returns an int or something castable to int */ - if (0 != zval_get_long(&retval)) { + if (0 != php_curl_get_long(&retval)) { rval = 1; } } @@ -807,6 +807,7 @@ static int curl_ssh_hostkeyfunction(void *clientp, int keytype, const char *key, } } else { zend_throw_error(NULL, "The CURLOPT_SSH_HOSTKEYFUNCTION callback must return either CURLKHMATCH_OK or CURLKHMATCH_MISMATCH"); + zval_ptr_dtor(&retval); } } @@ -901,7 +902,7 @@ static size_t curl_write_header(char *data, size_t size, size_t nmemb, void *ctx if (!Z_ISUNDEF(retval)) { // TODO: Check for valid int type for return value _php_curl_verify_handlers(ch, /* reporterror */ true); - length = zval_get_long(&retval); + length = php_curl_get_long(&retval); } zval_ptr_dtor(&argv[0]); zval_ptr_dtor(&argv[1]); @@ -1322,6 +1323,17 @@ void _php_setup_easy_copy_handlers(php_curl *ch, php_curl *source) (*source->clone)++; } +zend_long php_curl_get_long(zval *zv) +{ + if (EXPECTED(Z_TYPE_P(zv) == IS_LONG)) { + return Z_LVAL_P(zv); + } else { + zend_long ret = zval_get_long(zv); + zval_ptr_dtor(zv); + return ret; + } +} + static size_t read_cb(char *buffer, size_t size, size_t nitems, void *arg) /* {{{ */ { struct mime_data_cb_arg *cb_arg = (struct mime_data_cb_arg *) arg; diff --git a/ext/curl/multi.c b/ext/curl/multi.c index bb601f575db..a484373c107 100644 --- a/ext/curl/multi.c +++ b/ext/curl/multi.c @@ -410,7 +410,7 @@ static int _php_server_push_callback(CURL *parent_ch, CURL *easy, size_t num_hea zval_ptr_dtor_nogc(&headers); if (!Z_ISUNDEF(retval)) { - if (CURL_PUSH_DENY != zval_get_long(&retval)) { + if (CURL_PUSH_DENY != php_curl_get_long(&retval)) { rval = CURL_PUSH_OK; zend_llist_add_element(&mh->easyh, &pz_ch); } else { diff --git a/ext/curl/tests/refcounted_return_must_not_leak.phpt b/ext/curl/tests/refcounted_return_must_not_leak.phpt new file mode 100644 index 00000000000..b4b07a1a4fc --- /dev/null +++ b/ext/curl/tests/refcounted_return_must_not_leak.phpt @@ -0,0 +1,27 @@ +--TEST-- +Returning refcounted value from callback must not leak +--EXTENSIONS-- +curl +--FILE-- + +--EXPECT-- +ok