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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions