Repository navigation
ext/curl: Free the pushed handle when the push callback denies or throws #23850
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: PHP-8.4
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -409,15 +409,20 @@ static int _php_server_push_callback(CURL *parent_ch, CURL *easy, size_t num_hea | |
| zend_call_known_fcc(&mh->handlers.server_push, &retval, /* param_count */ 3, call_args, /* named_params */ NULL); | ||
| zval_ptr_dtor_nogc(&headers); | ||
|
|
||
| if (!Z_ISUNDEF(retval)) { | ||
| if (CURL_PUSH_DENY != php_curl_get_long(&retval)) { | ||
| rval = CURL_PUSH_OK; | ||
| zend_llist_add_element(&mh->easyh, &pz_ch); | ||
| } else { | ||
| /* libcurl will free this easy handle, avoid double free */ | ||
| ch->cp = NULL; | ||
| } | ||
| if (!Z_ISUNDEF(retval) && CURL_PUSH_DENY != php_curl_get_long(&retval)) { | ||
| rval = CURL_PUSH_OK; | ||
| zend_llist_add_element(&mh->easyh, &pz_ch); | ||
| return rval; | ||
| } | ||
|
|
||
| if (ch->share) { | ||
| curl_easy_setopt(easy, CURLOPT_SHARE, NULL); | ||
| } | ||
| ch->cp = NULL; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it might be better to re-init object here as the handling between different functions (e.g. curl_reset, curl_setopt, curl_getinfo) seems quite inconsistent
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reworked along those lines: a denied handle the callback keeps now gets a fresh easy handle with curl_init()'s defaults and its own option lists, so curl_reset(), curl_getinfo(), copy and clone behave as on a new handle. A handle the callback drops is freed as before. |
||
| if (Z_REFCOUNT(pz_ch) > 1) { | ||
| _php_curl_reinit_handle(ch); | ||
| } | ||
| zval_ptr_dtor(&pz_ch); | ||
|
|
||
| return rval; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| --TEST-- | ||
| CURLMOPT_PUSHFUNCTION deny frees the pushed handle | ||
| --EXTENSIONS-- | ||
| curl | ||
| --SKIPIF-- | ||
| <?php | ||
| include 'skipif-nocaddy.inc'; | ||
|
|
||
| $curl_version = curl_version(); | ||
| if ($curl_version['version_number'] < 0x080100) { | ||
| exit("skip: test may crash with curl < 8.1.0"); | ||
| } | ||
| ?> | ||
| --FILE-- | ||
| <?php | ||
| $pushed_path = null; | ||
| $pushed_ref = null; | ||
| $callback = function ($parent_ch, $pushed_ch, array $headers) use (&$pushed_path, &$pushed_ref) { | ||
| $pushed_path = preg_grep('/^:path:/', $headers); | ||
| $pushed_ref = WeakReference::create($pushed_ch); | ||
| return CURL_PUSH_DENY; | ||
| }; | ||
|
|
||
| $mh = curl_multi_init(); | ||
| curl_multi_setopt($mh, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX); | ||
| curl_multi_setopt($mh, CURLMOPT_PUSHFUNCTION, $callback); | ||
|
|
||
| $ch = curl_init(); | ||
| curl_setopt($ch, CURLOPT_URL, "https://localhost/serverpush"); | ||
| curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); | ||
| curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); | ||
| curl_multi_add_handle($mh, $ch); | ||
|
|
||
| do { | ||
| curl_multi_exec($mh, $active); | ||
| $info = curl_multi_info_read($mh); | ||
| } while ($info === false || $info['msg'] !== CURLMSG_DONE); | ||
|
|
||
| var_dump(curl_multi_getcontent($ch)); | ||
| var_dump(array_values($pushed_path)); | ||
| var_dump($pushed_ref->get()); | ||
| ?> | ||
| --EXPECT-- | ||
| string(13) "main response" | ||
| array(1) { | ||
| [0]=> | ||
| string(24) ":path:/serverpush/pushed" | ||
| } | ||
| NULL |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| --TEST-- | ||
| CURLMOPT_PUSHFUNCTION denied handle kept by the callback is a fresh handle | ||
| --EXTENSIONS-- | ||
| curl | ||
| --SKIPIF-- | ||
| <?php | ||
| include 'skipif-nocaddy.inc'; | ||
|
|
||
| $curl_version = curl_version(); | ||
| if ($curl_version['version_number'] < 0x080100) { | ||
| exit("skip: test may crash with curl < 8.1.0"); | ||
| } | ||
| ?> | ||
| --FILE-- | ||
| <?php | ||
| $kept = null; | ||
| $callback = function ($parent_ch, $pushed_ch, array $headers) use (&$kept) { | ||
| $kept = $pushed_ch; | ||
| return CURL_PUSH_DENY; | ||
| }; | ||
|
|
||
| $mh = curl_multi_init(); | ||
| curl_multi_setopt($mh, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX); | ||
| curl_multi_setopt($mh, CURLMOPT_PUSHFUNCTION, $callback); | ||
|
|
||
| $ch = curl_init(); | ||
| curl_setopt($ch, CURLOPT_URL, "https://localhost/serverpush"); | ||
| curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); | ||
| curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); | ||
| curl_setopt($ch, CURLOPT_PRIVATE, "parent"); | ||
| curl_multi_add_handle($mh, $ch); | ||
|
|
||
| do { | ||
| curl_multi_exec($mh, $active); | ||
| $info = curl_multi_info_read($mh); | ||
| } while ($info === false || $info['msg'] !== CURLMSG_DONE); | ||
|
|
||
| curl_multi_remove_handle($mh, $ch); | ||
| unset($mh, $ch, $info); | ||
|
|
||
| var_dump($kept instanceof CurlHandle); | ||
| var_dump(curl_getinfo($kept, CURLINFO_PRIVATE)); | ||
| var_dump(curl_pause($kept, CURLPAUSE_CONT) === curl_pause(curl_init(), CURLPAUSE_CONT)); | ||
|
|
||
| var_dump(curl_setopt($kept, CURLOPT_URL, "https://localhost/")); | ||
| var_dump(curl_exec($kept)); | ||
| var_dump(curl_getinfo($kept, CURLINFO_RESPONSE_CODE)); | ||
|
|
||
| curl_reset($kept); | ||
| curl_setopt($kept, CURLOPT_URL, "https://localhost/"); | ||
| curl_setopt($kept, CURLOPT_RETURNTRANSFER, true); | ||
| var_dump(curl_exec($kept)); | ||
|
|
||
| $copy = curl_copy_handle($kept); | ||
| var_dump(curl_exec($copy)); | ||
|
|
||
| $clone = clone $kept; | ||
| var_dump(curl_exec($clone)); | ||
| ?> | ||
| --EXPECT-- | ||
| bool(true) | ||
| bool(false) | ||
| bool(true) | ||
| bool(true) | ||
| Caddy is up and runningbool(true) | ||
| int(200) | ||
| string(23) "Caddy is up and running" | ||
| string(23) "Caddy is up and running" | ||
| string(23) "Caddy is up and running" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't this result in
ch->clone != NULLno longer implying thech->cp != NULL? If so, is that actually safe?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but now only for a denied handle that is being released, or one kept when curl_easy_init() fails in the callback. The dtor is safe there: curl_easy_cleanup(NULL) is a no-op and _php_curl_verify_handlers() only passes the NULL handle to curl_easy_setopt(), which rejects it.