From a6af50c484f6bbd1c01573685d6be6234ae944ea Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 25 Jun 2026 22:52:07 +0200 Subject: [PATCH] multi: forbid curl_easy_pause from within multi socket callback - there is a risk for a nasty recursive situation - we avoid certain risks that the pause call changes things so that when returning from the callback, the state of some internals is undefined and we need to reload which is easy to miss - we can't think of legitiate use cases for doing this. This is basically just the new favorite point for AI and security researchers to find hypothetical problems Closes #22179 --- docs/libcurl/curl_easy_pause.md | 4 ++-- lib/easy.c | 11 ++++++----- lib/multi.c | 7 ++++--- lib/multi_ev.c | 13 +++++-------- lib/multihandle.h | 6 +++++- lib/multiif.h | 4 ++-- tests/libtest/lib758.c | 9 +++++++++ 7 files changed, 33 insertions(+), 21 deletions(-) diff --git a/docs/libcurl/curl_easy_pause.md b/docs/libcurl/curl_easy_pause.md index 4a37ed1c14..f0c9230da5 100644 --- a/docs/libcurl/curl_easy_pause.md +++ b/docs/libcurl/curl_easy_pause.md @@ -28,8 +28,8 @@ CURLcode curl_easy_pause(CURL *handle, int action); Using this function, you can explicitly mark a running connection to get paused, and you can unpause a connection that was previously paused. Unlike -most other libcurl functions, curl_easy_pause(3) can be used from within -callbacks. +most other libcurl functions, curl_easy_pause(3) can be used from within all +callbacks except the socket callback set with CURLMOPT_SOCKETFUNCTION(3). A connection can be paused by using this function or by letting the read or the write callbacks return the proper return code (*CURL_READFUNC_PAUSE* and diff --git a/lib/easy.c b/lib/easy.c index d60bdaed7b..2e2c4d0e25 100644 --- a/lib/easy.c +++ b/lib/easy.c @@ -1138,18 +1138,19 @@ void curl_easy_reset(CURL *curl) CURLcode curl_easy_pause(CURL *curl, int action) { CURLcode result = CURLE_OK; - bool recursive = FALSE; bool changed = FALSE; struct Curl_easy *data = curl; bool recv_paused, recv_paused_new; bool send_paused, send_paused_new; + uint8_t in_c; if(!GOOD_EASY_HANDLE(data) || !data->conn) /* crazy input, do not continue */ return CURLE_BAD_FUNCTION_ARGUMENT; - if(Curl_is_in_callback(data)) - recursive = TRUE; + in_c = Curl_is_in_callback(data); + if(in_c == IN_CALLBACK_FORBID_EASY_PAUSE) + return CURLE_RECURSIVE_API_CALL; recv_paused = Curl_xfer_recv_is_paused(data); recv_paused_new = (action & CURLPAUSE_RECV); @@ -1188,10 +1189,10 @@ CURLcode curl_easy_pause(CURL *curl, int action) if(Curl_multi_ev_assess_xfer(data->multi, data) && !result) result = CURLE_ABORTED_BY_CALLBACK; - if(recursive) + if(in_c) /* this might have called a callback recursively which might have set this to false again on exit */ - Curl_set_in_callback(data, TRUE); + Curl_set_in_callback(data, in_c); return result; } diff --git a/lib/multi.c b/lib/multi.c index d6ae111d8e..51b0917ce7 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -3906,15 +3906,16 @@ static void process_pending_handles(struct Curl_multi *multi) } } -void Curl_set_in_callback(struct Curl_easy *data, bool value) +/* 'value' used to be a boolean but can now also contain more info */ +void Curl_set_in_callback(struct Curl_easy *data, uint8_t value) { if(data && data->multi) data->multi->in_callback = value; } -bool Curl_is_in_callback(struct Curl_easy *data) +uint8_t Curl_is_in_callback(struct Curl_easy *data) { - return data && data->multi && data->multi->in_callback; + return (data && data->multi) ? data->multi->in_callback : IN_CALLBACK_NO; } unsigned int Curl_multi_max_concurrent_streams(struct Curl_multi *multi) diff --git a/lib/multi_ev.c b/lib/multi_ev.c index 71e22b9b19..12d1ca2abb 100644 --- a/lib/multi_ev.c +++ b/lib/multi_ev.c @@ -34,8 +34,7 @@ #include "uint-spbset.h" #include "multihandle.h" - -static void mev_in_callback(struct Curl_multi *multi, bool value) +static void mev_in_callback(struct Curl_multi *multi, uint8_t value) { multi->in_callback = value; } @@ -209,12 +208,10 @@ static CURLMcode mev_forget_socket(struct Curl_multi *multi, if(entry->announced && multi->socket_cb) { NOVERBOSE((void)cause); CURL_TRC_M(data, "ev %s, call(fd=%" FMT_SOCKET_T ", ev=REMOVE)", cause, s); - mev_in_callback(multi, TRUE); + mev_in_callback(multi, IN_CALLBACK_FORBID_EASY_PAUSE); rc = multi->socket_cb(data, s, CURL_POLL_REMOVE, multi->socket_userp, entry->user_data); - mev_in_callback(multi, FALSE); - /* curl_easy_pause() is documented as callable from any callback; it - * re-enters mev_assess() which may free this 'entry'. Re-fetch. */ + mev_in_callback(multi, IN_CALLBACK_NO); entry = mev_sh_entry_get(&multi->ev.sh_entries, s); if(entry) entry->announced = FALSE; @@ -285,10 +282,10 @@ static CURLMcode mev_sh_entry_update(struct Curl_multi *multi, CURL_TRC_M(data, "ev update call(fd=%" FMT_SOCKET_T ", ev=%s%s)", s, (comboaction & CURL_POLL_IN) ? "IN" : "", (comboaction & CURL_POLL_OUT) ? "OUT" : ""); - mev_in_callback(multi, TRUE); + mev_in_callback(multi, IN_CALLBACK_FORBID_EASY_PAUSE); rc = multi->socket_cb(data, s, comboaction, multi->socket_userp, entry->user_data); - mev_in_callback(multi, FALSE); + mev_in_callback(multi, IN_CALLBACK_NO); if(rc == -1) { multi->dead = TRUE; return CURLM_ABORTED_BY_CALLBACK; diff --git a/lib/multihandle.h b/lib/multihandle.h index 19dd2ffcdf..5cf2c640e5 100644 --- a/lib/multihandle.h +++ b/lib/multihandle.h @@ -45,6 +45,10 @@ struct Curl_message { struct CURLMsg extmsg; }; +#define IN_CALLBACK_NO 0 +#define IN_CALLBACK_YES 1 +#define IN_CALLBACK_FORBID_EASY_PAUSE 2 + /* NOTE: if you add a state here, add the name to the statenames[] array * in curl_trc.c as well! */ @@ -179,10 +183,10 @@ struct Curl_multi { #endif uint32_t last_pending_mid; /* mid of last pending transfer rescheduled */ uint32_t last_resolv_id; /* id of the last DNS resolve operation */ + uint8_t in_callback; /* conditions for executing in callbacks */ BIT(ipv6_works); BIT(multiplexing); /* multiplexing wanted */ BIT(recheckstate); /* see Curl_multi_connchanged */ - BIT(in_callback); /* true while executing a callback */ BIT(in_ntfy_callback); /* true while dispatching notifications */ #ifdef USE_OPENSSL BIT(ssl_seeded); diff --git a/lib/multiif.h b/lib/multiif.h index 039db269e0..7fad9ca33e 100644 --- a/lib/multiif.h +++ b/lib/multiif.h @@ -37,8 +37,8 @@ void Curl_attach_connection(struct Curl_easy *data, struct connectdata *conn); void Curl_detach_connection(struct Curl_easy *data); bool Curl_multiplex_wanted(const struct Curl_multi *multi); -void Curl_set_in_callback(struct Curl_easy *data, bool value); -bool Curl_is_in_callback(struct Curl_easy *data); +void Curl_set_in_callback(struct Curl_easy *data, uint8_t value); +uint8_t Curl_is_in_callback(struct Curl_easy *data); CURLcode Curl_preconnect(struct Curl_easy *data); bool Curl_is_connecting(struct Curl_easy *data); diff --git a/tests/libtest/lib758.c b/tests/libtest/lib758.c index 2fa8ef3ab1..794e4cce06 100644 --- a/tests/libtest/lib758.c +++ b/tests/libtest/lib758.c @@ -147,6 +147,7 @@ static int t758_curlSocketCallback(CURL *curl, curl_socket_t s, int action, void *userp, void *socketp) { struct t758_ReadWriteSockets *sockets = userp; + CURLcode result; (void)curl; (void)socketp; @@ -158,6 +159,14 @@ static int t758_curlSocketCallback(CURL *curl, curl_socket_t s, int action, return -1; } + /* Pause is forbidden in this callback. This also returns + CURLE_BAD_FUNCTION_ARGUMENT before the connection has been setup. */ + result = curl_easy_pause(curl, CURLPAUSE_ALL); + if(!result) { + t758_msg("<- curl_easy_pause should return error!"); + return -1; + } + if(action == CURL_POLL_IN || action == CURL_POLL_INOUT) if(t758_addFd(&sockets->read, s, "read")) return -1; /* bail out */