Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 57 additions & 34 deletions source/protocol/http/multicurlinterface.c
Original file line number Diff line number Diff line change
Expand Up @@ -252,32 +252,12 @@ T2ERROR init_connection_pool()
}
pool_entries[i].handle_available = true;

// Set common options for each handle that persist across requests
CURL_SETOPT_CHECK(pool_entries[i].easy_handle, CURLOPT_TIMEOUT, 30L);
CURL_SETOPT_CHECK(pool_entries[i].easy_handle, CURLOPT_CONNECTTIMEOUT, 10L);

// TCP keepalive settings
CURL_SETOPT_CHECK(pool_entries[i].easy_handle, CURLOPT_TCP_KEEPALIVE, 1L);
CURL_SETOPT_CHECK(pool_entries[i].easy_handle, CURLOPT_TCP_KEEPIDLE, 50L);
CURL_SETOPT_CHECK(pool_entries[i].easy_handle, CURLOPT_TCP_KEEPINTVL, 30L);

#ifdef CURLOPT_TCP_KEEPCNT
CURL_SETOPT_CHECK(pool_entries[i].easy_handle, CURLOPT_TCP_KEEPCNT, 15L);
#endif

// Connection reuse settings
CURL_SETOPT_CHECK(pool_entries[i].easy_handle, CURLOPT_FORBID_REUSE, 0L);
CURL_SETOPT_CHECK(pool_entries[i].easy_handle, CURLOPT_FRESH_CONNECT, 0L);

// Socket options
CURL_SETOPT_CHECK(pool_entries[i].easy_handle, CURLOPT_NOSIGNAL, 1L);

// HTTP version and SSL settings
CURL_SETOPT_CHECK(pool_entries[i].easy_handle, CURLOPT_PIPEWAIT, 0L);
CURL_SETOPT_CHECK(pool_entries[i].easy_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
//SSL automatically negotiates the highest SSL/TLS version supported by both client and server
CURL_SETOPT_CHECK(pool_entries[i].easy_handle, CURLOPT_SSLVERSION, CURL_SSLVERSION_DEFAULT);
CURL_SETOPT_CHECK(pool_entries[i].easy_handle, CURLOPT_SSL_VERIFYPEER, 1L);
// Common curl options are NOT set here anymore.
// They are set via configure_common_curl_options() before each
// curl_easy_perform(), and cleared via curl_easy_reset() after
// each operation in release_pool_handle(). This prevents memory
// leaks from internally duplicated strings accumulating across
// handle reuses.

#ifdef LIBRDKCERTSEL_BUILD
// Initialize certificate selectors for each easy handle
Expand Down Expand Up @@ -431,6 +411,18 @@ static T2ERROR acquire_pool_handle(CURL **easy, int *idx)

static void release_pool_handle(int idx)
{
// Reset the handle before releasing it back to the pool.
// curl_easy_reset() frees all internally duplicated strings (URL, certs, etc.)
// preventing memory leaks across reuses, while preserving:
// - Live connections (connection cache)
// - DNS cache
// - Session ID cache
// So connection pooling benefits are fully retained.
if (idx >= 0 && idx < pool_size && pool_entries[idx].easy_handle)
{
curl_easy_reset(pool_entries[idx].easy_handle);
}

pthread_mutex_lock(&pool_mutex);
if (idx >= 0 && idx < pool_size)
{
Comment on lines +421 to 428
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The curl_easy_reset() call at line 421–424 occurs outside the pool_mutex lock, while the subsequent pool state update (marking the handle available) is protected by the mutex. If another thread acquires the same handle index between the reset and the mutex lock — for example, after a spurious or erroneous double-release — the reset could corrupt a handle that is actively in use. The curl_easy_reset() should be moved to inside the mutex-protected section, after the bounds and availability checks, to ensure the handle is not being used by another thread when it is reset.

Suggested change
if (idx >= 0 && idx < pool_size && pool_entries[idx].easy_handle)
{
curl_easy_reset(pool_entries[idx].easy_handle);
}
pthread_mutex_lock(&pool_mutex);
if (idx >= 0 && idx < pool_size)
{
pthread_mutex_lock(&pool_mutex);
if (idx >= 0 && idx < pool_size)
{
if (pool_entries[idx].easy_handle)
{
curl_easy_reset(pool_entries[idx].easy_handle);
}

Copilot uses AI. Check for mistakes.
Expand All @@ -445,6 +437,39 @@ static void release_pool_handle(int idx)
pthread_mutex_unlock(&pool_mutex);
}

// Configure common curl options that must be set before each request.
// Since curl_easy_reset() clears all options on the handle after each use,
// these need to be re-applied before every curl_easy_perform().
static void configure_common_curl_options(CURL *easy)
{
// Timeout settings
CURL_SETOPT_CHECK(easy, CURLOPT_TIMEOUT, 30L);
CURL_SETOPT_CHECK(easy, CURLOPT_CONNECTTIMEOUT, 10L);

// TCP keepalive settings
CURL_SETOPT_CHECK(easy, CURLOPT_TCP_KEEPALIVE, 1L);
CURL_SETOPT_CHECK(easy, CURLOPT_TCP_KEEPIDLE, 50L);
CURL_SETOPT_CHECK(easy, CURLOPT_TCP_KEEPINTVL, 30L);

#ifdef CURLOPT_TCP_KEEPCNT
CURL_SETOPT_CHECK(easy, CURLOPT_TCP_KEEPCNT, 15L);
#endif

// Connection reuse settings
CURL_SETOPT_CHECK(easy, CURLOPT_FORBID_REUSE, 0L);
CURL_SETOPT_CHECK(easy, CURLOPT_FRESH_CONNECT, 0L);

// Socket options
CURL_SETOPT_CHECK(easy, CURLOPT_NOSIGNAL, 1L);

// HTTP version and SSL settings
CURL_SETOPT_CHECK(easy, CURLOPT_PIPEWAIT, 0L);
CURL_SETOPT_CHECK(easy, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
//SSL automatically negotiates the highest SSL/TLS version supported by both client and server
CURL_SETOPT_CHECK(easy, CURLOPT_SSLVERSION, CURL_SSLVERSION_DEFAULT);
CURL_SETOPT_CHECK(easy, CURLOPT_SSL_VERIFYPEER, 1L);
}

#if defined(ENABLE_RDKB_SUPPORT) && !defined(RDKB_EXTENDER)
static void configure_wan_interface(CURL *easy)
{
Expand Down Expand Up @@ -501,11 +526,9 @@ T2ERROR http_pool_get(const char *url, char **response_data, bool enable_file_ou
return ret;
}

// Clear any POST-specific settings so that the handle can be used for GET operations
// curl_easy_perform ends up in crash when POST specific options are not cleared
CURL_SETOPT_CHECK(easy, CURLOPT_POSTFIELDS, NULL);
CURL_SETOPT_CHECK(easy, CURLOPT_POSTFIELDSIZE, 0L);
CURL_SETOPT_CHECK(easy, CURLOPT_HTTPHEADER, NULL);
// Set all common curl options before this request.
// Handle was reset (curl_easy_reset) after last use, so all options are clean.
configure_common_curl_options(easy);

// Allocate response buffer locally for GET requests only
curlResponseData* response = (curlResponseData *) malloc(sizeof(curlResponseData));
Expand Down Expand Up @@ -812,9 +835,9 @@ T2ERROR http_pool_post(const char *url, const char *payload)
return ret;
}

// Clear any GET-specific settings from previous use
CURL_SETOPT_CHECK(easy, CURLOPT_WRITEFUNCTION, NULL);
CURL_SETOPT_CHECK(easy, CURLOPT_WRITEDATA, (void *)stdout);
// Set all common curl options before this request.
// Handle was reset (curl_easy_reset) after last use, so all options are clean.
configure_common_curl_options(easy);

// Configure request-specific options for POST
CURL_SETOPT_CHECK_STR(easy, CURLOPT_URL, url);
Expand Down
Loading