2
votes

I'm seeing a leak when using set_verify_callback on an ssl socket. I have a class "CClientSock" with member "boost::asio::ssl::stream m_socket;"

My CClientSock class derives from 'enable_shared_from_this


    class CClientSock : public boost::enable_shared_from_this

If I call:


    m_socket.set_verify_callback(
        boost::bind(&CClientSock::verify_certificate, 
        shared_from_this(),
        _1, 
        _2));

then my CClientSock instance is never destroyed.

If I do not call the "m_socket.set_verify_callback" then my CClientSock instance is destroyed correctly.

The code looks like this:


    void CClientSock::StartPoll()
    {
    m_socket.set_verify_mode(boost::asio::ssl::verify_peer | 
        boost::asio::ssl::verify_fail_if_no_peer_cert);

    m_socket.set_verify_callback(
        boost::bind(&CClientSock::verify_certificate, 
        shared_from_this(),
        _1, 
        _2));


    boost::asio::ip::tcp::resolver::iterator endpoint_iterator;
    endpoint_iterator = ResolveAddress("xxx.xxx.xxx.xxx", nPort);

    boost::asio::async_connect(
        m_socket.lowest_layer(), 
        endpoint_iterator,
        boost::bind(&CClientSock::handle_connect, 
        shared_from_this(),
        boost::asio::placeholders::error));
    }

    bool CClientSock::verify_certificate(
        bool preverified,
        boost::asio::ssl::verify_context& ctx
    )
    {
    char subject_name[256];
    X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
    X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);

    SYSTEMTIME st;
    GetLocalTime(&st);
    CString s;
    s.Format("%02d SSL Verify: %s", m_nId, subject_name);
    LogMsg(m_dwThreadId, &st, s, 0, NULL);

    return preverified;
    }

Is there something that has to be done in "set_verify_callback" to release a reference?

3

3 Answers

1
votes

You have a cyclical reference by retaining a shared_ptr<CClientSock> in the verify callback. The context maintains a boost::asio::ssl::detail::verify_callback member, which contains your shared pointer. To break the cycle, use a weak pointer with a lambda

std::weak_ptr<CClientSock> weak( shared_from_this() );
m_socket.set_verify_callback([weak](bool p, boost::asio::ssl::verify_context& context) {
    std::shared_ptr<CClientSock> strong( weak );
    if ( strong ) {
        strong->verify_certificate( p, context );
    }
});
1
votes

After responses from sehe and Sam Miller, I got this to work. I ended up using most of the code shown by Sam, using the 'shared pointer cycle' idea first posted by sehe.

I modified the lambda expression to use the 'by value' capture clause ([=]). This told the compiler how to handle the weak_ptr variable. Then I modified the expression for a function returning a boolean. I ended up with this, which does allow the object instance to be destroyed.

    boost::weak_ptr weak( shared_from_this() );
    m_socket.set_verify_callback([=](bool p, boost::asio::ssl::verify_context& context)->bool {
        boost::shared_ptr strong(weak);
        if ( strong ) {
            p = strong->verify_certificate(p, context);
        }
        return p;
    });

0
votes

I have a hunch that the SSL context might be re-used.

In that case, you could explicitly manage the context, so you know when it gets destroyed:

Or, there could be a shared pointer cycle created by the context holding on to the socket such that none of context or handler get released. (In that case you should be seeing more issues). Try breaking the cycle by explicitly releasing the handler:

s.set_verify_callback([](bool p, verify_context&) { return p; });