Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

curl crashes in threaded calls

I read the SO articles if curl is thread-safe. This very simple code crashes - not always but when I call the program several times in a row [not parallel] then it crashes either with a segmentation fault or with the below error.

That far I am convinced I follow the rules regarding curl and threads as stated in the documentation.

In tests I could find out that it crashes in curl_easy_perform().

#include <curl/curl.h>
#include <stdio.h>
#include <thread>

class curlClass
{
private:
    CURL * curl {};
    CURLcode res;
    const char * sUrl;

public:
    auto loadDataFromUrl()     -> void;
    static auto initCurl()     -> void;
    static auto releaseCurl()  -> void;
    static auto callbackSaveData( void * content, size_t size, size_t nmemb, curlClass * classInstance ) -> size_t;
    curlClass( const char * );
    ~curlClass();
};

auto curlClass::initCurl() -> void
{
    curl_global_init(CURL_GLOBAL_SSL);
}

auto curlClass::releaseCurl() -> void
{
    curl_global_cleanup();
}

curlClass::curlClass( const char * sUrl ) : sUrl( sUrl )
{
    curl = curl_easy_init();
}

curlClass::~curlClass()
{
    curl_easy_cleanup( curl );
}

auto curlClass::callbackSaveData( __attribute__ ((unused))  void *contents, 
    size_t size, 
    size_t nmemb, 
    __attribute__ ((unused))    curlClass * classInstance
) -> size_t
{
    return size * nmemb;
}

auto curlClass::loadDataFromUrl() -> void
{
    if ( curl )
    {
        curl_easy_setopt(curl, (CURLoption) CURLOPT_SSL_VERIFYPEER, nullptr);
        curl_easy_setopt(curl, (CURLoption) CURLOPT_URL, sUrl);
        curl_easy_setopt(curl, (CURLoption) CURLOPT_WRITEFUNCTION, callbackSaveData);
        res = curl_easy_perform(curl);
        printf( "Return: %d\n", res );
    }
}

auto worker( const char * sUrl ) -> void
{
    curlClass myInstance( sUrl );
    myInstance.loadDataFromUrl();
}

int main(void)
{
    curl_version_info_data * curl_version = curl_version_info(CURLVERSION_NOW);

    printf( "Curl version=%s\n", curl_version->version );
    curlClass::initCurl();

    std::thread thread1( worker, "https://www.google.com");
    std::thread thread2( worker, "https://www.google.com" );
    std::thread thread3( worker, "https://www.google.com" );
    std::thread thread4( worker, "https://www.google.com" );
    std::thread thread5( worker, "https://www.google.com" );
    std::thread thread6( worker, "https://www.google.com" );
    std::thread thread7( worker, "https://www.google.com" );

    thread1.join();
    thread2.join();
    thread3.join();
    thread4.join();
    thread5.join();
    thread6.join();
    thread7.join();

    curlClass::releaseCurl();
}

UPDATE:

I compiled the new curl version 7.46.0. This is one of the possible error dumps:

Curl version=7.46.0
*** glibc detected *** ./curl_crash: double free or corruption (out): 0x00007fcd200056d0 ***
======= Backtrace: =========
/lib64/libc.so.6(+0x76618)[0x7fcd2ff4a618]
/lib64/libc.so.6(cfree+0x6c)[0x7fcd2ff4f65c]
/usr/lib64/libcrypto.so.0.9.8(CRYPTO_free+0x19)[0x7fcd2f9f13f9]
/usr/lib64/libcrypto.so.0.9.8(+0xf407d)[0x7fcd2f9d307d]
/usr/lib64/libcrypto.so.0.9.8(ERR_clear_error+0xd)[0x7fcd2f9efd1d]
/usr/local/lib/libcurl.so.4(+0x4849c)[0x7fcd30a2d49c]
/usr/local/lib/libcurl.so.4(+0x4b970)[0x7fcd30a30970]
/usr/local/lib/libcurl.so.4(+0x1073d)[0x7fcd309f573d]
/usr/local/lib/libcurl.so.4(+0x20451)[0x7fcd30a05451]
/usr/local/lib/libcurl.so.4(+0x31e0e)[0x7fcd30a16e0e]
/usr/local/lib/libcurl.so.4(curl_multi_perform+0xdd)[0x7fcd30a1780d]
/usr/local/lib/libcurl.so.4(curl_easy_perform+0x10b)[0x7fcd30a1001b]
./curl_crash(_ZN9curlClass15loadDataFromUrlEv+0x88)[0x408002]
./curl_crash(_Z6workerPKc+0x2c)[0x408051]
./curl_crash(_ZNSt12_Bind_simpleIFPFvPKcES1_EE9_M_invokeIILm0EEEEvSt12_Index_tupleIIXspT_EEE+0x40)[0x409818]
./curl_crash(_ZNSt12_Bind_simpleIFPFvPKcES1_EEclEv+0x1d)[0x409711]
./curl_crash(_ZNSt6thread5_ImplISt12_Bind_simpleIFPFvPKcES3_EEE6_M_runEv+0x1c)[0x40968e]
/usr/local/lib64/libstdc++.so.6(+0xb5c10)[0x7fcd30790c10]
/lib64/libpthread.so.0(+0x77f6)[0x7fcd2e81c7f6]
/lib64/libc.so.6(clone+0x6d)[0x7fcd2ffaf09d]
======= Memory map: ========

I do not have really a clue what is going wrong. When I start the program 5 times then at least once it crashes.

What am I doing wrong or is it possible - according to the dump - that I am using an old SSL library?

Command line to compile:

g++ --std=c++11 -Wall -Werror -pedantic -Wextra curl_crash.cpp -o curl_crash -lcurl -rdynamic && ./curl_crash

like image 216
Peter VARGA Avatar asked Jan 25 '16 17:01

Peter VARGA


1 Answers

I stopped too early reading the SSL library documentation. Thanks to Petesh, who pointed out the issue while accessing openssl, I could then fix fast the problem.

As stated in the curl documentation Thread-safe --> TLS --> OpenSSL I had to use the functions. I adapted it to C++11 standard:

#include <mutex>
#include <openssl/err.h>
#include <vector>

class SslCurlWrapper
{
private:
    static std::vector<std::mutex> vectorOfSslMutex;
    static auto id_function() -> unsigned long { return ( pthread_self() ); }
    static auto locking_function(int, int, const char *, int) -> void;

public:
    SslCurlWrapper();
    ~SslCurlWrapper();
};

std::vector<std::mutex> SslCurlWrapper::vectorOfSslMutex( CRYPTO_num_locks() );

//----------------------------------------
auto SslCurlWrapper::locking_function( int mode,
                                       int n,
                                       __attribute__ ((unused)) const char * file,
                                       __attribute__ ((unused)) int line
                                     ) -> void
//----------------------------------------
{
    if ( mode & CRYPTO_LOCK )   vectorOfSslMutex [n].lock();
    else                        vectorOfSslMutex [n].unlock();
}

//------------------------------
SslCurlWrapper::SslCurlWrapper()
//------------------------------
{
    CRYPTO_set_id_callback( id_function );
    CRYPTO_set_locking_callback( locking_function );
}

//-------------------------------
SslCurlWrapper::~SslCurlWrapper()
//-------------------------------
{
    CRYPTO_set_id_callback( nullptr );
    CRYPTO_set_locking_callback( nullptr );
}

It can be then used like this:

int main(void)
{
        SslCurlWrapper sslObject;    // hook is set up
        // here it is safe to use the curl library in a multi-thread-environment
}       // hook is released/uninstalled

Compile command:

g++ -std=c++11 -Wall -Werror -Wextra -pedantic -c SslCurlWrapper.cpp

The -lcrypto library has to be added to the compiler command line in order the linker does not complain.

My personal remark is that I am a bit surprised that the default mode in the openssl library does not implement the both mentioned function that way to avoid each programmer has to start from scratch. This could be the minimum-implementation...

The test shell script called the program 200 times without any crash. Before it crashed immediately. For me it is fixed.

I do not understand the very useless comment from SergeyA.

like image 146
Peter VARGA Avatar answered Sep 17 '22 18:09

Peter VARGA