Openssl 多线程支持

                                                                                  **公司  安全研究专家    李泉

背景

      在平时开发的过程中发现了这么一个问题。如果以多线程的方式调用Openssl库函数进行安全加密解密的话,发生了内存空间的UAF与double free的异常。

                                                      (图:SSL指针被改写成0x21)

RSA_new_method用于创建一个SSL指针而这个指针是公用的,在openssl整个生态中多处采取了调用,这里就是因为其中某成员在其他线程中被改写形成了冲突,大多是SSL_write、SSL_read等共享操作,如果有其他线程访问了已经被Free掉的对象,自然会出现异常。在这里,本人则介绍一下Openssl官方的最新针对多线程操作的解决方案。

Openssl是线程安全的

程序员们经常有一些误解就是Openssl不是线程安全的。究其原因就是开发人员通常使用网络上公开的加密解密算法,并没有详细的阅读Openssl的相关说明文档。Openssl在于算法封装的性能优势,多种安全加密算法均可以利用该库调用。高性能的运算必然面对的是多种共享资源的读写。经过研究SSL结构中的多处成员,必须要运行在原子锁级别上。关键就是SSL_write函数,经常会并行修改其他成员变量,造成内存冲突。所以查找多方资料,了解到Openssl是有针对多线程情况提出的解决方案。

Openssl首先判断自身是否处于线程调用中,获得线程的标识符。然后Openssl要求每个线程捆绑一个互斥体对象(互斥锁)来保持线程安全性。特殊的是,Openssl的全平台支持特性迫使其无法再次外接互斥体创建、释放、激活等方法。(不同操作系统线程管理原理不同,其实也是偷懒)其选择了回调的方式,将线程管理部分转接系统自身进行操作。回调函数分为静态锁与动态锁,下面就分别来介绍这两种解决方案。

Openssl静态锁回调

       静态锁要求程序员提供两个回调函数,第一个主要是告诉组件在适当的时机获取或者释放锁。定义如下:

void locking_function(int mode, int n, const char *file, int line);


mode:确定锁执行的操作。存在CRYPTO_LOCK标记时,进行枷锁,否则它应该被释放。


n:获取或者释放的锁的编号。第一个锁从0标识。该值永远不会大于或者等于CRYPTO_num_locks变量的值。


File:请求互斥对象的对象名称。用于辅助调试,通常由_FILE_预处理器宏提供。


Line:请求创建互斥对象的源行号。与file参数一样,它也用于辅助调试,通常由_line_preprocessor宏提供。



下一个回调函数用于获取调用线程的唯一标识符。类似于windows中的GetCurrentThreadId函数。我们容易就明白这是用来获取当前线程信息的,并且保证绝对的唯一。函数需要定义成如下格式。


unsigned long id_function(void);


最后,我们引入Openssl中的两个库函数:CRYPTO_set_id_callback和CRYPTO_set_locking_callback,并且在初始化的时候调用他们,具体用法如下:


案例. Win32 POSIX内核下实现的Openssl静态锁

 

int THREAD_setup(void);

int THREAD_cleanup(void);

#if defined(WIN32)

#define MUTEX_TYPE HANDLE

#define MUTEX_SETUP(x) (x) = CreateMutex(NULL, FALSE, NULL)

#define MUTEX_CLEANUP(x) CloseHandle(x)

#define MUTEX_LOCK(x) WaitForSingleObject((x), INFINITE)

#define MUTEX_UNLOCK(x) ReleaseMutex(x)

#define THREAD_ID GetCurrentThreadId()

#elif defined(_POSIX_THREADS)

/* _POSIX_THREADS is normally defined in unistd.h if pthreads are availableon your platform. */

#define MUTEX_TYPE pthread_mutex_t

#define MUTEX_SETUP(x) pthread_mutex_init(&(x), NULL)

#define MUTEX_CLEANUP(x) pthread_mutex_destroy(&(x))

#define MUTEX_LOCK(x) pthread_mutex_lock(&(x))

#define MUTEX_UNLOCK(x) pthread_mutex_unlock(&(x))

#define THREAD_ID pthread_self()

#else

#error You must define mutex operations appropriate for your

platform!

#endif

/* 保存有效的mutex. */

static MUTEX_TYPE *mutex_buf = NULL;

static void locking_function(int mode, int n, const char * file, int

line)

{

if (mode & CRYPTO_LOCK)

MUTEX_LOCK(mutex_buf[n]);

else

MUTEX_UNLOCK(mutex_buf[n]);

}

static unsigned long id_function(void)

{

return ((unsigned long)THREAD_ID);

}

int THREAD_setup(void)

{

int i;

mutex_buf = (MUTEX_TYPE *)malloc(CRYPTO_num_locks() *

sizeof(MUTEX_TYPE));

if (!mutex_buf)

return 0;

for (i = 0; i < CRYPTO_num_locks(); i++)

MUTEX_SETUP(mutex_buf[i]);

CRYPTO_set_id_callback(id_function);

CRYPTO_set_locking_callback(locking_function);

return 1;

}

int THREAD_cleanup(void)

{

int i;

if (!mutex_buf)

return 0;

CRYPTO_set_id_callback(NULL);

CRYPTO_set_locking_callback(NULL);

for (i = 0; i < CRYPTO_num_locks(); i++)

MUTEX_CLEANUP(mutex_buf[i]);

free(mutex_buf);

mutex_buf = NULL;

return 1;

}


使用这些静态加锁函数,我们需要在程序启动线程或调用OpenSSL函数之前至少进行一次的函数调用,并且我们必须调用THREAD_setup,如果不能分配容纳互斥体的内存,该函数通常会返回1或0。一旦THREAD_setup调用并成功返回,我们就可以在多个线程中调用Openssl,在程序线程执行完成之后,或者使用Openssl完成之后,我们应该调用Thread_cleanup来回收用于互斥体中的所有内存。在以上实例中,如果担心存在异常的情况,需要您添加异常处理代码在捕获异常。

Openssl动态锁回调

       动态所需要一个数据结构(CRYPTO_dynlock_value)和三个回调函数。该结构用于保存互斥对象所使用的数据,这三个函数分别对应创建,锁定/解锁和销毁的操作。与静态锁定机制相同,我们还必须告诉O      penssl关于回调函数的信息,以便在适当的时候调用他们。首先第一步我们需要定义CRYPTO_dynlock_value结构,这个结构很简单,只有一个成员。


struct CRYPTO_dynlock_value

{

MUTEX_TYPE mutex;

};


   第一个回到函数用于创建一个新的互斥对象,Openssl使用干净的内存区域来创建他。必须为返回的结构体分配内存,并且对其初始化。回调的定义如下。


struct CRYPTO_dynlock_value *dyn_create_function(const char *file,

int line);


file:请求互斥对象的对象名称。用于辅助调试,通常由_FILE_预处理器宏提供。


Line:请求创建互斥对象的源行号。与file参数一样,它也用于辅助调试,通常由_line_preprocessor宏提供。


下一个回调函数用于获取或释放互斥对象。它的定义如下:


void dyn_lock_function(int mode, struct CRYPTO_dynlock_value *mutex, constchar *file, int line);


mode:确定锁定函数应该执行的操作。设置CRYPTO_LOCK标志时,应获加锁;否则,它应该被释放。


Mutex:互斥体应该处于被获取或释放状态。它永远不会为空。


file:请求互斥对象的对象名称。用于辅助调试,通常由_FILE_预处理器宏提供。


Line:请求创建互斥对象的源行号。与file参数一样,它也用于辅助调试,通常由_line_preprocessor宏提供。


第三个也就是最后一个回调函数用于销毁一个不再需要OpenSSL的互斥体,使用当前平台的释放方式对这段内存进行销毁,并释放分配给CRYPTO_dynlock_value结构的任何内存。它的定义如下:


void dyn_destroy_function(struct CRYPTO_dynlock_value *mutex, const char*file, int line);


Mutex:互斥体应该处于被获取或释放状态。它永远不会为空。


file:请求互斥对象的对象名称。用于辅助调试,通常由_FILE_预处理器宏提供。


Line:请求创建互斥对象的源行号。与file参数一样,它也用于辅助调试,通常由_line_preprocessor宏提供。


案例. 扩展库以支持动态锁定机制

 

struct CRYPTO_dynlock_value

{

MUTEX_TYPE mutex;

};

static struct CRYPTO_dynlock_value * dyn_create_function(const char *file,

int line)

{

struct CRYPTO_dynlock_value *value;

value = (struct CRYPTO_dynlock_value *)malloc(sizeof(

struct CRYPTO_dynlock_value));

if (!value)

return NULL;

MUTEX_SETUP(value->mutex);

return value;

}

static void dyn_lock_function(int mode, struct CRYPTO_dynlock_value *l,

const char *file, int line)

{

if (mode & CRYPTO_LOCK)

MUTEX_LOCK(l->mutex);

else

MUTEX_UNLOCK(l->mutex);

}

static void dyn_destroy_function(struct CRYPTO_dynlock_value *l,

const char *file, int line)

{

MUTEX_CLEANUP(l->mutex);

free(l);

}

int THREAD_setup(void)

{

int i;

mutex_buf = (MUTEX_TYPE *)malloc(CRYPTO_num_locks() *

sizeof(MUTEX_TYPE));

if (!mutex_buf)

return 0;

for (i = 0; i < CRYPTO_num_locks(); i++)

MUTEX_SETUP(mutex_buf[i]);

CRYPTO_set_id_callback(id_function);

CRYPTO_set_locking_callback(locking_function);

/* The following three CRYPTO_... functions are the OpenSSL functions

for registering the callbacks we implemented above */

CRYPTO_set_dynlock_create_callback(dyn_create_function);

CRYPTO_set_dynlock_lock_callback(dyn_lock_function);

CRYPTO_set_dynlock_destroy_callback(dyn_destroy_function);

return 1;

}

int THREAD_cleanup(void)

{

int i;

if (!mutex_buf)

return 0;

CRYPTO_set_id_callback(NULL);

CRYPTO_set_locking_callback(NULL);

CRYPTO_set_dynlock_create_callback(NULL);

CRYPTO_set_dynlock_lock_callback(NULL);

CRYPTO_set_dynlock_destroy_callback(NULL);

for (i = 0; i < CRYPTO_num_locks(); i++)

MUTEX_CLEANUP(mutex_buf[i]);

free(mutex_buf);

mutex_buf = NULL;

return 1;

}

作者|李泉(liquan165) 某集团安全研究专家

主要研究领域|互联网黑产、汽车安全、物联网安全、终端安全等

关注我们公众号:ExploitLee

你可能感兴趣的:(Openssl 多线程支持)