[openssl-users] Double free of session occurs in multithread program.

共通基盤SSL[業務ID] / COMMONSSL,GYOUMU hrf-ssl at itg.hitachi.co.jp
Fri Oct 6 06:24:56 UTC 2017


Hello,

I am using OpenSSL's API to create multithreaded programs.
Check the contents of the program in ssl_test.c.

I have the following two questions.
The purpose of the question is to create a program that does not cause double free.

Question 1. Is this program correct as a program without double free?

Question 2. Because this program is complicated, I want to make it a simple program.
           I want to easily create a program that does not cause double free.
           If you can create with this OpenSSL API different from this program, please tell us about API and usage.


I will explain this program.

It takes time to create a session each time communicate.
Therefore, this program reuses sessions.

If it set the references of the SSL_SESSION structure to 1 or higher, the session will not be released.
SSL_connect() creates a new session.
At this time, references are incremented with SSL_get1_session().
This will cause references to be greater than 1, so the session will not be freed.
Multiple threads will share this session on subsequent communications.

When the session expires, it is necessary to release the old session that was being reused.
SSL_SESSION_free() can free old sessions that were being reused.
When executing SSL_SESSION_free(), specify the address of the session(SSL_SESSION structure).

This program outputs a core dump when deleting the line of "/*add*/" in ssl_test.c.
The cause is to double free the address of the same session by two threads.

As a measure against this problem, I limit the old session to one release.
Therefore, flag (fs_isDelete) is set to 1 when the preceding thread releases the old session.
After that, the following thread does not release the old session because the flag (fs_isDelete) is 1.


I will explain the API that affects the references of the SSL_SESSION structure.

(1) SSL_set_session()
Create a session to reuse.
I will explain the references.
The references of the reusing session are incremented.

(2) SSL_connect()
If the session has not expired, we will use the session to reuse.
If the session has expired, we will create a new session.
I will explain the references.
If the session has not expired, references will not change.
If the session has expired, the references for the new session are 1.
If the session has expired, the references of the old session will be decremented.

(3) SSL_get1_session()
If the session has not expired, obtain the address of the session to reuse.
If the session has expired, we will get a new session.
I will explain the references.
If the session has not expired, the references of the session to be reused will increment.
If the session has expired, the references of the new session will increment.

(4) SSL_SESSION_free()
SSL_SESSION_free() can free old sessions that were being reused.
When executing SSL_SESSION_free(), specify the address of the session (SSL_SESSION structure).
I will explain the references.
The references of the old session are decremented.
If references is 0, old sessions are freed.
If references are already 0, double the address of the same session.


I will explain the sequence of double free.

I will explain the operation in single thread.

First, thread 0 creates a new session.

Thread 0
                References for sessions to reuse SSL processing starts.
SSL_connect() ---------> (+1) (1)
SSL_get1_session() ----> (+1) (2)
SSL_free() ------------> (-1) (1)
SSL processing is terminated.

After that, thread 1 reuses the session.
At this time, the session may expire.
In that case, SSL_SESSION_free() decrements the references of the old session.

Thread 1
                References of old sessions that have expired SSL processing starts.
SSL_set_session() -----> (+1) (2)
SSL_connect() ---------> (-1) (1)
SSL_SESSION_free() ----> (-1) (0)
SSL processing is terminated.

I will explain the operation in multithreading.

Thread 1, SSL_SESSION_free() decrements references for old sessions.
After that, thread 2 also decrements the references of the same session and double-releases it.

Thread 1 Thread 2
SSL processing starts. SSL processing starts.
                 References of old sessions that have expired
SSL_set_session() -----> (+1) (2)
                         (+1) (3) <----- SSL_set_session()
SSL_connect() ---------> (-1) (2)
                         (-1) (1) <----- SSL_connect()
SSL_SESSION_free() ----> (-1) (0)
                         (-1) (-1) <---- SSL_SESSION_free() SSL processing is terminated. Doubles the address of the same session.


ssl_test.c
---------------------------------------
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/uio.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <sys/time.h>
#include <fcntl.h>
#include <pthread.h>

#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/crypto.h>

#define MAX_SOCKET_COUNT  2
#define SOCKID_EMPTY 0

int localSocket[MAX_SOCKET_COUNT];
int currentSocketCount = 0;

char *g_premorthostname = "hoge";
char *g_pserver_port_no = "12345";

#define CERTIFICATEFILEPATH "/home/server.crt"
SSL_CTX *ctx = NULL;
SSL_SESSION *fs_session = NULL;
SSL_SESSION *fs_session_new = NULL;
static int fs_refCount = 0;
static int fs_isDelete = 0;
static int fs_isAdd = 0;


static pthread_mutex_t fs_lock_mutex;
static pthread_mutex_t *fs_lock_mp = NULL; static long *fs_lock_count;

void finalizer(void)
{
}

void do_connect(char *argv1,char *argv2, int *p_localsocket) {
  int rc;

  struct  hostent     connecthostentstruct;
  struct  hostent     *connecthostentptr;
  struct  sockaddr_in   connectaddr;
  memset((void *)&connectaddr,0,sizeof(struct sockaddr_in));

  *p_localsocket = -1;

   int sock = socket(AF_INET, SOCK_STREAM, 0);
  *p_localsocket = sock;

  rc = fcntl(sock,F_SETFD,FD_CLOEXEC);

  int ival = 1;
  rc = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&ival, sizeof ival);

  u_long lval = 1;
  int flag = fcntl(sock, F_GETFL, 0);
  rc = fcntl(sock, F_SETFL, O_NONBLOCK|flag);

  connecthostentptr = gethostbyname (argv1);

  connecthostentstruct = *connecthostentptr;
  connectaddr.sin_family = AF_INET;
  connectaddr.sin_port = htons(atoi(argv2));
  connectaddr.sin_addr = * ((struct in_addr *) connecthostentstruct.h_addr); 

  rc = connect(sock, (const struct sockaddr*)&connectaddr, sizeof connectaddr);

  if (errno == EINPROGRESS ) {
    fd_set fds_write;
    fd_set fds_except;
    FD_ZERO(&fds_write);
    FD_SET(sock, &fds_write);
    FD_ZERO(&fds_except);
    FD_SET(sock, &fds_except);
    struct timeval timeout;
    timeout.tv_sec = 10;
    timeout.tv_usec = 0;
    rc = select(sock+1, 0, &fds_write, &fds_except, &timeout);
    int err = 0;
    socklen_t err_len = sizeof(err);
    rc = getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, &err_len );
  }

  lval = 0;
  rc = fcntl(sock, F_SETFL, flag);
}

void linux_locking_callback(int mode, int type, const char *file, int line) {
  if (mode & CRYPTO_LOCK) {
    pthread_mutex_lock(&(fs_lock_mp[type]));
  } else {
    pthread_mutex_unlock(&(fs_lock_mp[type]));
  }
}

unsigned long linux_thread_id(void)
{
  unsigned long ret;

  ret = (unsigned long)pthread_self();

  return ret;
}

int thread_setup()
{
  int i;

  fs_lock_mp = (pthread_mutex_t *)OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));

  for (i = 0; i < CRYPTO_num_locks(); i++) {
    pthread_mutex_init(&(fs_lock_mp[i]), NULL);
  }

  CRYPTO_set_id_callback(linux_thread_id);
  CRYPTO_set_locking_callback(linux_locking_callback);
}

int ThreadFunc(void *argv)
{
  int *p_threadIdx = (int *)argv;
  int threadIdx = *p_threadIdx;
  long cnt = 0;
  int localsocket = -1;
  SSL *ssl = NULL;
  int ret = 0;
  SSL_SESSION *session = NULL;
  SSL_SESSION *session_new = NULL;
  int nSSLError = SSL_ERROR_NONE;

  while (1) {
    session = NULL;
    session_new = NULL;

    ssl = SSL_new(ctx);

    do_connect(g_premorthostname,g_pserver_port_no,&localsocket);

    pthread_mutex_lock(&fs_lock_mutex);
    if (fs_isDelete && fs_refCount <= 0) {
      fs_isDelete = 0;
      fs_isAdd = 0;
      fs_session = fs_session_new;
    }
    if (fs_isDelete == 0) {
      fs_refCount++;
      session = fs_session;
    }
    if (session != NULL) {
      ret = SSL_set_session(ssl, session);        /*(1)*/
    }
    pthread_mutex_unlock(&fs_lock_mutex);

    ret = SSL_set_fd(ssl, localsocket);

    ret = SSL_connect(ssl);                       /*(2)*/

    session_new = SSL_get0_session(ssl);
    pthread_mutex_lock(&fs_lock_mutex);
    if (session != NULL) {
      fs_refCount--;
      if ((session_new != session) && (session_new != NULL)) {
        if (fs_isDelete == 0) {                   /*add*/
          fs_isDelete = 1
          SSL_SESSION_free(session);              /*(4)*/
          fs_isAdd = 1;
        }                                         /*add*/
      }
    } else if (session_new != NULL) {
      fs_isAdd = 1;
    }

    if (fs_isAdd) {
      if (fs_session_new == NULL) {
        fs_session_new = SSL_get1_session(ssl);    /*(3)*/
      }
    }
    pthread_mutex_unlock(&fs_lock_mutex);
    sleep(1);

    SSL_shutdown(ssl);
    SSL_free(ssl);
    close(localsocket);
  }
}

int main(int argc,char *argv[])
{
  int retCode = 0;
  pthread_attr_t attr[MAX_SOCKET_COUNT];
  pthread_t thid[MAX_SOCKET_COUNT];

  int ret = 0;

  SSL_load_error_strings();
  SSL_library_init();

  atexit(finalizer);
  pthread_mutex_init(&fs_lock_mutex, NULL);

  ctx = SSL_CTX_new(SSLv23_client_method());

  SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1);

  ret = SSL_CTX_load_verify_locations(ctx, CERTIFICATEFILEPATH, NULL);

  thread_setup();

  while (1) {
    if(currentSocketCount >= MAX_SOCKET_COUNT)
    {
      sleep(1);
      continue;
    }
    retCode = pthread_attr_init(&attr[currentSocketCount]);
    pthread_attr_setdetachstate(&attr[currentSocketCount], PTHREAD_CREATE_JOINABLE);
    retCode = pthread_create(&thid[currentSocketCount], &attr[currentSocketCount], (void *)ThreadFunc, (void*)&currentSocketCount);
    pthread_attr_destroy(&attr[currentSocketCount]);
    currentSocketCount++;
    usleep(200000);

  }

  SSL_CTX_free(ctx);
  ERR_free_strings();

  return 0;
}
---------------------------------------

Thanks in advance.


Regards,

Manabu


More information about the openssl-users mailing list