系統函數dlopen()被劫持導致symbol找不到的問題記錄 頂 原 薦

問題現象

我們實現了一個名叫libilvrfplugin.so的lib,該lib鏈接了libiubsntconflib.so, 而libiubsntconflib.so 又鏈接了libipconflib.so, libipconflib.so裏面實現了一個方法check_vrf_r()用於檢查VRF的合法性。
簡單點來說,A lib鏈接了B lib,而B lib又鏈接了C lib,C lib實現了方法check_vrf_r().

某些場景下,系統會動態加載A lib, 但是A lib根本沒用到方法check_vrf_r()。注意這裏是動態加載的lib庫,也就是lib的使用方運行時會使用dlopen()加載該lib。
在新發布的版本里,我們發現該lib竟然沒起做用,好像該lib 根本就不存在一樣。

我們在syslog裏找到下面一條log:

Apr 17 15:46:27.718075 info CFPU-0 Validator: CPluginManager : Unable to load plugin libilvrfplugin.so Error:/opt/nokiasiemens/lib64/libiubsntconflib.so: undefined symbol: check_vrf_r

從syslog 裏可以看出動態加載libilvrfplugin.so(也就是 A lib)時失敗,原因是找不到符號check_vrf_r。

疑惑一:這幾個lib在上一個版本里工作正常,當前版本里沒有任何改動,爲什麼會出錯呢?
疑惑二:libilvrfplugin.so 中根本沒用到符號check_vrf_r,爲什麼會報找不到符號呢?

查找問題
根據syslog裏的線索,定位到出事的代碼:

...
lib_handle=dlopen(ep[count]->d_name,RTLD_LAZY);
if(!lib_handle)
{
    error = dlerror();
    TRACER(TRC_INFO)<<"CPluginManager : Unable to load plugin "
                    <<ep[count]->d_name << " Error:"<<error<<std::endl;
    free(ep[count]);
    continue;
} // if
...

這裏使用dlopen()來加載動態鏈接庫,並設置flag爲RTLD_LAZY,該flag控制了dlopen()加載lib時的解析方式,加載時只解析用庫裏用到的符號。本例中符號check_vrf_r就屬於只聲明(include進來)而未使用的符號。
順便說一句,dlopen()還有一種解析方式是RTLD_NOW,這就要求所有的符號都需要解析到地址,不管該符號有沒有用到。

但是本例中dlopen()解析方式是正確的,我們期望不去解析符號check_vrf_r,可是爲什麼還是去解析了呢?

原因可能是glibc的實現有變,有個bug也說不準,還有可能是哪裏改變了dlopen的實現。順便說一句由於C語言沒有命名空間的概念,所以你可以定義一個與系統函數同名的函數以覆蓋系統函數,大多數情況應該避免這麼做。

經查glibc在我們發佈的兩個版本中沒有變化,那麼很可能是哪裏改變了或影響了dlopen()。不得已,只能去翻看我們這個版本中所有代碼變化。

我們驚奇的發現,在某個腳本里新增了這麼一行代碼:

export LD_PRELOAD=/opt/nokiasiemens/SS_FConfigure/lib/libdlopeninterceptor.so

從字面意義上看跟dlopen有關,"dlopen劫持者",好霸氣的名字!接着看這個代碼會起什麼作用。
這裏export了環境變量LD_PRELOAD,該環境變量聲明瞭應用程序加載前優先加載的動態鏈接庫,換句話說如果這裏的動態鏈接庫實現了與系統函數同名的函數的話,那麼將覆蓋系統函數。

懷着激動的心情查看該動態lib的實現:

#include <dlfcn.h>
#include <syslog.h>
#include <stdlib.h>
#ifdef __cplusplus__
extern "C" {
#endif


typedef void* (*dlopen_func_t)(const char* filename, int flag);


static dlopen_func_t _glibc_dlopen = NULL;
void* dlopen(const char* filename, int flag)
{
    int realflag = flag;
    if (NULL == _glibc_dlopen) {
        _glibc_dlopen = (dlopen_func_t)dlsym(RTLD_NEXT, "dlopen");
        if (NULL == _glibc_dlopen) {
            syslog(LOG_CRIT, "dlopeninterceptor:Failed to resolve dlopen, got error:%s", dlerror());
            return NULL;
        }
    }


    if (realflag & RTLD_LAZY) {
        realflag = realflag & ~RTLD_LAZY;
        realflag = realflag | RTLD_NOW;
        syslog(LOG_DEBUG, "dlopeninterceptor:Changing dlopen flag from to %d to %d when opening %s", flag, realflag, filename);
    }


    return _glibc_dlopen(filename, realflag);
}


#ifdef __cplusplus__
}
#endif

驚奇的發現該Lib確實重寫了dlopen(), 如果dlopen()指定的flag時RTLD_LAZY將強制轉換成RTLD_NOW。找到root cause了,鬆一口氣。

最後
查找到root cause後就簡單了,給相應的組織或部門報個issue,將你的分析結果放上去就OK了,問題很快就解決了。後來聽說是某個同事誤操作在腳本中加了一行代碼讓dlopen()劫持者生效了。

類似這樣的問題,root cause是很簡單的,去fix花費的effort也不大。稍有困難的是如何在龐雜的系統中逐步定位問題,而且需要去查看整個系統的代碼實現,由於對系統其他模塊的不熟悉,必要時還需要給矛支持。感謝在此過程中給予支持的同事,也很欣慰公司有種很好的機制或氛圍,讓你在必要時都能得到給力的support。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章