Skip to content

[BUG] The curl client does not check what curl_global_init returns #4434

Description

@thc1006

Describe your environment main at 3fb1d31, Ubuntu, gcc 14, libcurl 8.14.1, CMake.

HttpCurlGlobalInitializer is the one place the curl client initialises libcurl, and it throws away what that call returns. ext/src/http/client/curl/http_client_curl.cc:49:

HttpCurlGlobalInitializer::HttpCurlGlobalInitializer()
{
  curl_global_init(CURL_GLOBAL_ALL);
}

curl_global_init says it plainly: "If this function returns non-zero, something went wrong and you cannot use the other curl functions."

Nothing records that, so nothing can honour it. HttpClient's constructor takes the singleton and then calls curl_multi_init straight after it, which is one of the functions the sentence above is about, and every session after that reaches curl_easy_init the same way.

What is already fixed and what is not

#4404 is about the curl client treating failed allocations as success, and the branch for it checks what curl_easy_init, curl_slist_append, curl_multi_init, curl_multi_cleanup, curl_multi_remove_handle and curl_multi_add_handle return. It also fixed the ordering here, since multi_handle_ is declared before curl_global_initializer_ and member initialisation order meant curl_multi_init used to run before curl_global_init had.

So the ordering is right now. What is still missing is the condition: libcurl is initialised first, but nobody asks whether it was initialised successfully.

What it would take

Two shapes, and the choice is a design one.

The small one records the result in the singleton and has HttpClient's constructor skip initMultiHandle() when it is not CURLE_OK, reporting once. Everything downstream then behaves the way a client with no multi handle already behaves, which after #4404 means requests are refused with a reason rather than sent into libcurl. It costs one member on HttpCurlGlobalInitializer, which is a concrete class in an installed header, so it moves that layout.

The larger one gives the client a way to refuse construction, which is a wider conversation about what an exporter does when its HTTP client cannot exist.

Why I have not sent a patch

I cannot make curl_global_init fail in the test binary, and I would rather say so than ship a branch nothing runs.

The suite installs its allocator callbacks with curl_global_init_mem from SetUpTestSuite, and that call is the global initialisation: it "works exactly as curl_global_init with one addition". It has to run there, because installing the callbacks after libcurl is initialised is a silent no-op. So by the time any client exists libcurl is already initialised, and the curl_global_init inside the singleton is a reference count bump that returns CURLE_OK whatever the allocator does.

Reaching the failure needs a seam that the client calls instead of curl_global_init directly. That is worth discussing on its own terms, since the same seam would answer the other injection questions in this area, but it is production surface added for testability and not something to slip into a bug fix.

Happy to send either shape once you say which, and to write the seam if that is the direction.

Found while working through #4404.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageIndicates an issue or PR lacks a `triage/foo` label and requires one.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions