? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? **公司??安全研究專家??? 李泉
背景
????? 在平時(shí)開發(fā)的過(guò)程中發(fā)現(xiàn)了這么一個(gè)問(wèn)題。如果以多線程的方式調(diào)用Openssl庫(kù)函數(shù)進(jìn)行安全加密解密的話,發(fā)生了內(nèi)存空間的UAF與double free的異常。





? ????????????????????????????????????????????????????(圖:SSL指針被改寫成0x21)
RSA_new_method用于創(chuàng)建一個(gè)SSL指針而這個(gè)指針是公用的,在openssl整個(gè)生態(tài)中多處采取了調(diào)用,這里就是因?yàn)槠渲心吵蓡T在其他線程中被改寫形成了沖突,大多是SSL_write、SSL_read等共享操作,如果有其他線程訪問(wèn)了已經(jīng)被Free掉的對(duì)象,自然會(huì)出現(xiàn)異常。在這里,本人則介紹一下Openssl官方的最新針對(duì)多線程操作的解決方案。
Openssl是線程安全的
程序員們經(jīng)常有一些誤解就是Openssl不是線程安全的。究其原因就是開發(fā)人員通常使用網(wǎng)絡(luò)上公開的加密解密算法,并沒有詳細(xì)的閱讀Openssl的相關(guān)說(shuō)明文檔。Openssl在于算法封裝的性能優(yōu)勢(shì),多種安全加密算法均可以利用該庫(kù)調(diào)用。高性能的運(yùn)算必然面對(duì)的是多種共享資源的讀寫。經(jīng)過(guò)研究SSL結(jié)構(gòu)中的多處成員,必須要運(yùn)行在原子鎖級(jí)別上。關(guān)鍵就是SSL_write函數(shù),經(jīng)常會(huì)并行修改其他成員變量,造成內(nèi)存沖突。所以查找多方資料,了解到Openssl是有針對(duì)多線程情況提出的解決方案。
Openssl首先判斷自身是否處于線程調(diào)用中,獲得線程的標(biāo)識(shí)符。然后Openssl要求每個(gè)線程捆綁一個(gè)互斥體對(duì)象(互斥鎖)來(lái)保持線程安全性。特殊的是,Openssl的全平臺(tái)支持特性迫使其無(wú)法再次外接互斥體創(chuàng)建、釋放、激活等方法。(不同操作系統(tǒng)線程管理原理不同,其實(shí)也是偷懶)其選擇了回調(diào)的方式,將線程管理部分轉(zhuǎn)接系統(tǒng)自身進(jìn)行操作?;卣{(diào)函數(shù)分為靜態(tài)鎖與動(dòng)態(tài)鎖,下面就分別來(lái)介紹這兩種解決方案。
Openssl靜態(tài)鎖回調(diào)
?????? 靜態(tài)鎖要求程序員提供兩個(gè)回調(diào)函數(shù),第一個(gè)主要是告訴組件在適當(dāng)?shù)臅r(shí)機(jī)獲取或者釋放鎖。定義如下:
void locking_function(int mode, int n, const char *file, int line);
mode:確定鎖執(zhí)行的操作。存在CRYPTO_LOCK標(biāo)記時(shí),進(jìn)行枷鎖,否則它應(yīng)該被釋放。
n:獲取或者釋放的鎖的編號(hào)。第一個(gè)鎖從0標(biāo)識(shí)。該值永遠(yuǎn)不會(huì)大于或者等于CRYPTO_num_locks變量的值。
File:請(qǐng)求互斥對(duì)象的對(duì)象名稱。用于輔助調(diào)試,通常由_FILE_預(yù)處理器宏提供。
Line:請(qǐng)求創(chuàng)建互斥對(duì)象的源行號(hào)。與file參數(shù)一樣,它也用于輔助調(diào)試,通常由_line_preprocessor宏提供。
下一個(gè)回調(diào)函數(shù)用于獲取調(diào)用線程的唯一標(biāo)識(shí)符。類似于windows中的GetCurrentThreadId函數(shù)。我們?nèi)菀拙兔靼走@是用來(lái)獲取當(dāng)前線程信息的,并且保證絕對(duì)的唯一。函數(shù)需要定義成如下格式。
unsigned long id_function(void);
最后,我們引入Openssl中的兩個(gè)庫(kù)函數(shù):CRYPTO_set_id_callback和CRYPTO_set_locking_callback,并且在初始化的時(shí)候調(diào)用他們,具體用法如下:
案例. Win32 與POSIX內(nèi)核下實(shí)現(xiàn)的Openssl靜態(tài)鎖
?
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;
}
使用這些靜態(tài)加鎖函數(shù),我們需要在程序啟動(dòng)線程或調(diào)用OpenSSL函數(shù)之前至少進(jìn)行一次的函數(shù)調(diào)用,并且我們必須調(diào)用THREAD_setup,如果不能分配容納互斥體的內(nèi)存,該函數(shù)通常會(huì)返回1或0。一旦THREAD_setup調(diào)用并成功返回,我們就可以在多個(gè)線程中調(diào)用Openssl,在程序線程執(zhí)行完成之后,或者使用Openssl完成之后,我們應(yīng)該調(diào)用Thread_cleanup來(lái)回收用于互斥體中的所有內(nèi)存。在以上實(shí)例中,如果擔(dān)心存在異常的情況,需要您添加異常處理代碼在捕獲異常。
Openssl動(dòng)態(tài)鎖回調(diào)
?????? 動(dòng)態(tài)所需要一個(gè)數(shù)據(jù)結(jié)構(gòu)(CRYPTO_dynlock_value)和三個(gè)回調(diào)函數(shù)。該結(jié)構(gòu)用于保存互斥對(duì)象所使用的數(shù)據(jù),這三個(gè)函數(shù)分別對(duì)應(yīng)創(chuàng)建,鎖定/解鎖和銷毀的操作。與靜態(tài)鎖定機(jī)制相同,我們還必須告訴O????? penssl關(guān)于回調(diào)函數(shù)的信息,以便在適當(dāng)?shù)臅r(shí)候調(diào)用他們。首先第一步我們需要定義CRYPTO_dynlock_value結(jié)構(gòu),這個(gè)結(jié)構(gòu)很簡(jiǎn)單,只有一個(gè)成員。
struct CRYPTO_dynlock_value
{
MUTEX_TYPE mutex;
};
?? 第一個(gè)回到函數(shù)用于創(chuàng)建一個(gè)新的互斥對(duì)象,Openssl使用干凈的內(nèi)存區(qū)域來(lái)創(chuàng)建他。必須為返回的結(jié)構(gòu)體分配內(nèi)存,并且對(duì)其初始化?;卣{(diào)的定義如下。
struct CRYPTO_dynlock_value *dyn_create_function(const char *file,
int line);
file:請(qǐng)求互斥對(duì)象的對(duì)象名稱。用于輔助調(diào)試,通常由_FILE_預(yù)處理器宏提供。
Line:請(qǐng)求創(chuàng)建互斥對(duì)象的源行號(hào)。與file參數(shù)一樣,它也用于輔助調(diào)試,通常由_line_preprocessor宏提供。
下一個(gè)回調(diào)函數(shù)用于獲取或釋放互斥對(duì)象。它的定義如下:
void dyn_lock_function(int mode, struct CRYPTO_dynlock_value *mutex, constchar *file, int line);
mode:確定鎖定函數(shù)應(yīng)該執(zhí)行的操作。設(shè)置CRYPTO_LOCK標(biāo)志時(shí),應(yīng)獲加鎖;否則,它應(yīng)該被釋放。
Mutex:互斥體應(yīng)該處于被獲取或釋放狀態(tài)。它永遠(yuǎn)不會(huì)為空。
file:請(qǐng)求互斥對(duì)象的對(duì)象名稱。用于輔助調(diào)試,通常由_FILE_預(yù)處理器宏提供。
Line:請(qǐng)求創(chuàng)建互斥對(duì)象的源行號(hào)。與file參數(shù)一樣,它也用于輔助調(diào)試,通常由_line_preprocessor宏提供。
第三個(gè)也就是最后一個(gè)回調(diào)函數(shù)用于銷毀一個(gè)不再需要OpenSSL的互斥體,使用當(dāng)前平臺(tái)的釋放方式對(duì)這段內(nèi)存進(jìn)行銷毀,并釋放分配給CRYPTO_dynlock_value結(jié)構(gòu)的任何內(nèi)存。它的定義如下:
void dyn_destroy_function(struct CRYPTO_dynlock_value *mutex, const char*file, int line);
Mutex:互斥體應(yīng)該處于被獲取或釋放狀態(tài)。它永遠(yuǎn)不會(huì)為空。
file:請(qǐng)求互斥對(duì)象的對(duì)象名稱。用于輔助調(diào)試,通常由_FILE_預(yù)處理器宏提供。
Line:請(qǐng)求創(chuàng)建互斥對(duì)象的源行號(hào)。與file參數(shù)一樣,它也用于輔助調(diào)試,通常由_line_preprocessor宏提供。
案例. 擴(kuò)展庫(kù)以支持動(dòng)態(tài)鎖定機(jī)制
?
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) 某集團(tuán)安全研究專家
主要研究領(lǐng)域|互聯(lián)網(wǎng)黑產(chǎn)、汽車安全、物聯(lián)網(wǎng)安全、終端安全等
關(guān)注我們公眾號(hào):ExploitLee