Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unsolicited messages in boost::asio crashes application, without SSL it works fine, why?

I want to send unsolicited messages over an SSL connection. Meaning that the server sends a message not based on a request from a client, but because some event happened that the client needs to know about.

I just use the SSL server example from the boost site, added a timer that sends 'hello' after 10 seconds, everything works fine before the timer expires (the server echo's everything), the 'hello' is also received, but after that the application crashes on the next time a text is sent to the server.

For me even more strange is the fact that when I disable the SSL code, so use a normal socket and do the same using telnet, it works FINE and keeps on working fine!!!

I ran into this problem for the second time now, and I really do not have an idea why this is happening the way it happens.

Below is the total source that I altered to demonstrate the problem. Compile it without the SSL define and use telnet and everything works OK, define SSL and use openssl, or the client SSL example from the boost website and the thing crashes.

#include <cstdlib>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>

//#define SSL

typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_socket;

class session
{
public:
  session(boost::asio::io_service& io_service,
      boost::asio::ssl::context& context)
#ifdef SSL  
    : socket_(io_service, context)
#else
    : socket_(io_service)

#endif    
  {
  }

  ssl_socket::lowest_layer_type& socket()
  {
    return socket_.lowest_layer();
  }

  void start()
  {
#ifdef SSL      
    socket_.async_handshake(boost::asio::ssl::stream_base::server,
        boost::bind(&session::handle_handshake, this,
          boost::asio::placeholders::error));
#else
      socket_.async_read_some(boost::asio::buffer(data_, max_length),
          boost::bind(&session::handle_read, this,
            boost::asio::placeholders::error,
            boost::asio::placeholders::bytes_transferred));

      boost::shared_ptr< boost::asio::deadline_timer > timer(new     boost::asio::deadline_timer( socket_.get_io_service() ));
      timer->expires_from_now( boost::posix_time::seconds( 10 ) );
      timer->async_wait( boost::bind( &session::SayHello, this, _1, timer ) );

#endif    
  }

  void handle_handshake(const boost::system::error_code& error)
  {
    if (!error)
    {
      socket_.async_read_some(boost::asio::buffer(data_, max_length),
          boost::bind(&session::handle_read, this,
            boost::asio::placeholders::error,
            boost::asio::placeholders::bytes_transferred));

      boost::shared_ptr< boost::asio::deadline_timer > timer(new     boost::asio::deadline_timer( socket_.get_io_service() ));
      timer->expires_from_now( boost::posix_time::seconds( 10 ) );
      timer->async_wait( boost::bind( &session::SayHello, this, _1, timer ) );

    }
    else
    {
      delete this;
    }
  }

  void SayHello(const boost::system::error_code& error, boost::shared_ptr<     boost::asio::deadline_timer > timer) {
      std::string hello = "hello";

        boost::asio::async_write(socket_,
          boost::asio::buffer(hello, hello.length()),
          boost::bind(&session::handle_write, this,
            boost::asio::placeholders::error));

      timer->expires_from_now( boost::posix_time::seconds( 10 ) );
      timer->async_wait( boost::bind( &session::SayHello, this, _1, timer ) );

  }


  void handle_read(const boost::system::error_code& error,
      size_t bytes_transferred)
  {
    if (!error)
    {
      boost::asio::async_write(socket_,
          boost::asio::buffer(data_, bytes_transferred),
          boost::bind(&session::handle_write, this,
            boost::asio::placeholders::error));
    }
    else
    {
      std::cout << "session::handle_read() -> Delete, ErrorCode: "<< error.value() <<     std::endl;
      delete this;
    }
  }

  void handle_write(const boost::system::error_code& error)
  {
    if (!error)
    {
      socket_.async_read_some(boost::asio::buffer(data_, max_length),
          boost::bind(&session::handle_read, this,
            boost::asio::placeholders::error,
            boost::asio::placeholders::bytes_transferred));
    }
    else
    {
      std::cout << "session::handle_write() -> Delete, ErrorCode: "<< error.value() <<     std::endl;
      delete this;
    }
  }

private:
#ifdef SSL    
  ssl_socket socket_;
#else
  boost::asio::ip::tcp::socket socket_;
#endif  
  enum { max_length = 1024 };
  char data_[max_length];
};

class server
{
public:
  server(boost::asio::io_service& io_service, unsigned short port)
    : io_service_(io_service),
      acceptor_(io_service,
          boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)),
      context_(boost::asio::ssl::context::sslv23)
  {
#ifdef SSL        
    context_.set_options(
        boost::asio::ssl::context::default_workarounds
        | boost::asio::ssl::context::no_sslv2
        | boost::asio::ssl::context::single_dh_use);
    context_.set_password_callback(boost::bind(&server::get_password, this));
    context_.use_certificate_chain_file("server.crt");
    context_.use_private_key_file("server.key", boost::asio::ssl::context::pem);
    context_.use_tmp_dh_file("dh512.pem");
#endif
    start_accept();
  }

  std::string get_password() const
  {
    return "test";
  }

  void start_accept()
  {
    session* new_session = new session(io_service_, context_);
    acceptor_.async_accept(new_session->socket(),
        boost::bind(&server::handle_accept, this, new_session,
          boost::asio::placeholders::error));
  }

  void handle_accept(session* new_session,
      const boost::system::error_code& error)
  {
    if (!error)
    {
      new_session->start();
    }
    else
    {
      delete new_session;
    }

    start_accept();
  }

private:
  boost::asio::io_service& io_service_;
  boost::asio::ip::tcp::acceptor acceptor_;
  boost::asio::ssl::context context_;
};

int main(int argc, char* argv[])
{
  try
  {
    boost::asio::io_service io_service;

    using namespace std; // For atoi.
    server s(io_service, 7777 /*atoi(argv[1])*/);

    io_service.run();
  }
  catch (std::exception& e)
  {
    std::cerr << "Exception: " << e.what() << "\n";
  }

  return 0;
}

I use boost 1.49 and OpenSSL 1.0.0i-fips 19 Apr 2012. I tried investigating this problem as much as possible, the last time I had this problem (a couple of months ago), I received an error number that I could trace to this error message: error: decryption failed or bad record mac.

But I have no idea what is going wrong and how to fix this, any suggestions are welcome.

like image 696
ikku Avatar asked Jan 15 '23 20:01

ikku


1 Answers

The problem is multiple concurrent async read and writes. I were able to crash this program even with raw sockets (glibc detected double free or corruption). Let's see what happens after session starts (in braces I put number of concurrent scheduled async reads and writes):

  1. schedule async read (1, 0)

  2. (assume that data comes) handle_read is executed, it schedules async write (0, 1)

  3. (data are written) handle_write is executed, it schedules async read (1, 0)

    Now, it could loop over 1. - 3. without any problem indefinitely. But then timer expires...

  4. (assume, that no new data come from client, so there is still one async read scheduled) timer expires, so SayHello is executed, it schedules async write, still no problem (1, 1)

  5. (data from SayHello are written, but still no new data comes from client) handle_write is executed, it schedules async read (2, 0)

    Now, we are done. If any new data from client will come, part of them could be read by one async read and part by another. For raw sockets, it might even seem to work (despite possibility, that there might be 2 concurrent writes scheduled, so echo on client side might look mixed). For SSL this might corrupt incoming data stream, and this is probably what happens.

How to fix it:

  1. strand will not help in this case (it is not concurrent handler executions, but scheduled async reads and writes).
  2. It is not enough, if async write handler in SayHello does nothing (there will be no concurrent reads then, but still concurrent writes might occur).
  3. If you really want to have two diffident kind of writes (echo and timer), you have to implement some kind of queue of messages to write, to avoid mixing writes from echo and timer.
  4. General remark: it was simple example, but using shared_ptr instead of delete this is much better way of handling memory allocation with boost::asio. It will prevent from missing errors resulting in memory leak.
like image 121
Greg Avatar answered Jan 23 '23 14:01

Greg