Skip to content

asiohiper.cpp: event_cb can be called with deallocated fdp pointer, causing crash #5090

Closed
@andrewoliverhall

Description

@andrewoliverhall

I did this

I am using the example code in asiohiper.cpp in a large application doing lots of http requests. We also use asio in other contexts. Often we run on osx using xcode "address sanitizer" and it flagged an issue in the asiohiper.cpp code, specifically in "event_cb". This issue was causing intermittent crashes in our application because of memory corruption.

The problem is that event_cb is passed as a function callback for async_read_some or async_write_some, where the boost::bind statement is capturing the pointer value of fdp as a parameter to event_cb. However, sometime between when async_read_some or async_write_some is invoked and when event_cb is called, the fdp pointer is deallocated via close_socket. This leads to an uninitialized memory read at the start of event_cb where it dereferences fdp.

My solution is to use a pattern I use elsewhere with asynchronous programming and boost::asio: weak_ptr. I created a struct such as:

struct SocketDesc : public std::enable_shared_from_this<SocketDesc>
{
        curl_socket_t                   mCurlSocket;
        boost::asio::ip::tcp::socket *  mBoostSocket;
        int*                            mFDP;
};

socket_map is changed to be a map from curl_socket_t to a std::shared_ptr.

Then I changed event_cb like this:

void Ntwk_CurlMulti::EventCB(const boost::system::error_code & error, std::size_t bytes, std::weak_ptr<SocketDesc> inWeakSocket, int action)
{
    auto sharedSocket = inWeakSocket.lock();
    if (!sharedSocket)
    {
        printf("%s event_cb: socket already closed", __FUNCTION__);
        return;
    }

    /* make sure the event matches what are wanted */
    if(*sharedSocket->mFDP == action || *sharedSocket->mFDP == CURL_POLL_INOUT) {
        CURLMcode rc;
        if(error)
            action = CURL_CSELECT_ERR;
        auto curlSocket = sharedSocket->mCurlSocket;
        sharedSocket.reset();
        rc = curl_multi_socket_action(mMulti, curlSocket, action, &mStillRunning);
        
        if (!CheckCurlError("event_cb: curl_multi_socket_action", rc))
            return;
        
        this->CheckMultiInfo();
        
        if(mStillRunning <= 0) {
            printf("%s last transfer done, kill timeout", __FUNCTION__);
            mTimer->cancel();
        }
        
        sharedSocket = inWeakSocket.lock();
        if (!sharedSocket)
        {
            printf("%s event_cb: socket closed after curl_multi_socket_action", __FUNCTION__);
            return;
        }
        
        /* keep on watching.
         * the socket may have been closed and/or fdp may have been changed
         * in curl_multi_socket_action(), so check them both */
        if(!error &&
           (*sharedSocket->mFDP == action || *sharedSocket->mFDP == CURL_POLL_INOUT) && mStillRunning) {
            boost::asio::ip::tcp::socket *tcp_socket = sharedSocket->mBoostSocket;
            if(action == CURL_POLL_IN)
                tcp_socket->async_read_some(boost::asio::null_buffers(), boost::bind(&Ntwk_CurlMulti::EventCB, this, _1, _2, inWeakSocket, CURL_POLL_IN));
            else if(action == CURL_POLL_OUT)
                tcp_socket->async_write_some(boost::asio::null_buffers(), boost::bind(&Ntwk_CurlMulti::EventCB, this, _1, _2, inWeakSocket, CURL_POLL_OUT));
        }
    }
}

There were various other changes to support this in the file. I like the weak_ptr technique but perhaps there is a more incremental way to solve the issue.

I expected the following

No crashes in my libcurl code.

curl/libcurl version

7.56.1. ( but this is a bug in the latest asiohiper.cpp on github, not a bug in libcurl exactly )

[curl -V output]

operating system

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions