Skip to content

setopt: CURLOPT_SHARE=NULL during transfer causes NULL deref in Curl_xfer_needs_flush #21604

@MegaManSec

Description

@MegaManSec

I did this

Calling curl_easy_setopt(easy, CURLOPT_SHARE, NULL) from inside a WRITEFUNCTION/READFUNCTION callback deterministically SEGVs in Curl_xfer_needs_flush on the way back out of the callback. Same setopt called from the main thread between curl_multi_perform iterations leaves the easy stuck in MSTATE_PERFORMING with data->conn == NULL, returning CURLM_INTERNAL_ERROR forever.

I realise calling setopt during a transfer isn't really sanctioned — curl_easy_pause.md is the only function the docs explicitly say is safe inside callbacks, and the rest is implicitly "between transfers". But the failure mode here is a NULL deref rather than a graceful error, and the CURLOPT_SHARE handler takes the un-link path unconditionally so the foot-gun is right at the surface. Also the fix is really simple! So I think maybe we should do it.

The setopt handler at lib/setopt.c:1503-1518 calls Curl_share_easy_unlink with no in-progress guard. Curl_share_easy_unlink (lib/curl_share.c:403-431) lands in Curl_detach_connection (lib/multi.c:938-951), which is the sole site that sets data->conn = NULL. Control returns from the callback into Curl_sendrecvCurl_req_want_sendCurl_xfer_needs_flush (lib/transfer.c:806-809):

bool Curl_xfer_needs_flush(struct Curl_easy *data)
{
  return Curl_conn_needs_flush(data, data->conn->send_idx);
}

data->conn == NULL, NULL deref at offset 0x14c (= offsetof(struct connectdata, send_idx)). The defensive if(!data->conn) return CURLM_INTERNAL_ERROR in multi_runsingle (lib/multi.c:2730-2735) only catches the next runsingle entry — it can't help inside the same runsingle that invoked the callback. The unconditional un-link in CURLOPT_SHARE predates the recent share refactor (82009c4220, 2026-03-09); the shape has been there for a while.

PoC: a write callback that calls curl_easy_setopt(easy, CURLOPT_SHARE, NULL) on its first invocation, against any HTTP server. ASan trace:

==1464774==ERROR: AddressSanitizer: SEGV on unknown address 0x00000000014c
    #0 Curl_xfer_needs_flush
    #1 Curl_sendrecv
    #2 multi_runsingle
    #3 main          F11_poc.c:259

Simplest fix is to refuse the setopt while the easy is in flight — mirrors share_in_use at lib/curl_share.c:204-208:

   case CURLOPT_SHARE: {
     struct Curl_share *set = va_arg(param, struct Curl_share *);
+    if(data->multi && data->mstate >= MSTATE_CONNECT &&
+       data->mstate < MSTATE_COMPLETED)
+      return CURLE_BAD_FUNCTION_ARGUMENT;
     result = Curl_share_easy_unlink(data);

I expected the following

Error or no crash

curl/libcurl version

master

operating system

all

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions