Skip to content

Fix/remove incorrect rc decrement causing double free and leak reported#113

Open
faldor20 wants to merge 1 commit into
ygrek:masterfrom
faldor20:fix/remove-incorrect-rc-decrement
Open

Fix/remove incorrect rc decrement causing double free and leak reported#113
faldor20 wants to merge 1 commit into
ygrek:masterfrom
faldor20:fix/remove-incorrect-rc-decrement

Conversation

@faldor20

@faldor20 faldor20 commented Jul 19, 2025

Copy link
Copy Markdown

So I've been building a client for Picos using the multi socket api and I've been fighting double free and leaked handle errors for a week now. I finally narrowed it down to definitely being somewhere in the bindings/libcurl.

I'm sure this function is the culprit:

So rough overview:

  1. we make a handle rc=1 handles=1
  2. we add the handle to the multi rc=2 handles=1
  3. we call remove_finished rc=2 handles=2
  4. we remove the handle from the multi rc=1 handles=2
  5. the second handle goes out of scope and gets gced rc=0 handles=1 LEAK REPORTED
  6. We clean up the original handle rc= 0 handles=1
  7. the og handle goes out of scope. DOUBLE FREE (memory corruption/ crash if run in valgrind)
value caml_curlm_remove_finished(value v_multi)
{
  CAMLparam1(v_multi);
  CAMLlocal2(v_easy, v_tuple);
  CURL* handle;
  CURLM* multi_handle;
  CURLcode result;
  Connection* conn = NULL;

  multi_handle = CURLM_val(v_multi);

  caml_release_runtime_system();
  handle = curlm_remove_finished(multi_handle,&result);
  caml_acquire_runtime_system();

  if (NULL == handle)
  {
    CAMLreturn(Val_none);
  }
  else
  {
    conn = getConnection(handle);
    if (conn->curl_ERRORBUFFER != NULL)
    {
        Store_field(Field(conn->ocamlValues, Ocaml_ERRORBUFFER), 0, caml_copy_string(conn->curl_ERRORBUFFER));
    }
    conn->refcount--;
    /* NB: same handle, but different block */
    v_easy = caml_curl_alloc(conn);
    v_tuple = caml_alloc_tuple(2);
    Store_field(v_tuple,0,v_easy);
    Store_field(v_tuple,1,Val_int(result)); /* CURLcode */
    CAMLreturn(caml_alloc_some(v_tuple));
  }
}

the reason is that it makes the incorrect assumption that the refcount should be decremented, without considering that this new handle will be garbage collected and finalisation will be run twice
I also added logging to see this effect in practice:

Curl: allocate new handle 0x239cec60 , conn 0x239d01c0
...
Curl: add handle to multi 0x239cec60 , conn 0x239d01c0
...
Curl: allocate new handle 0x239cec60 , conn 0x239d01c0
Curl: removed from mulit, created new handle 0x239cec60 , conn 0x239d01c0
Curl: remove handle 0x239cec60 , conn 0x239d01c0
...
Curl: finalize handle (nil) , conn 0x239d01c0
...
Curl: finalize handle (nil) , conn 0x239d01c0

This was a hard won find...

Decrementing the rc here creates a double free because it creates a
new handle and both handles will be garbage collected freeing the
underlying connection twice.
@nojb

nojb commented Jul 19, 2025

Copy link
Copy Markdown
Collaborator

Hello, thanks for the detective work.

Just trying to understand, though:

  • we call remove_finished rc=2 handles=2
  • we remove the handle from the multi rc=1 handles=2

The function remove_finished already removes the finished handle from the multi, so I don't understand why we would remove it again in the next step. If anything, maybe the refcount should not be decreased unconditionally in caml_curl_multi_remove_handle; rather it should be decreased only if a handle is actually removed from the multi (which would not be the case if the handle was already removed by remove_finished).

@faldor20

faldor20 commented Jul 19, 2025

Copy link
Copy Markdown
Author

Ahhh, I see, I missed that, good point. I'll take another look when I'm back at my computer.

Seems like we shoudlbeb able to check if the handle was actually removed or some such.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants