天天看點

高通TrustZone接口QSEECOM Use-After-Free漏洞分析高通QSEECOM接口漏洞(CVE-2019-14040)分析阿裡安全(侯客)

高通QSEECOM接口漏洞(CVE-2019-14040)分析

阿裡安全(侯客)

背景:

 上周五看到一篇國外的安全公司zimperium的研究人員寫的一篇他們分析發現的高通的QSEECOM接口漏洞文章,[

https://blog.zimperium.com/multiple-kernel-vulnerabilities-affecting-all-qualcomm-devices/]

其中一個Use-After-Free的漏洞(CVE-2019-14041)我覺得挺有意思,但是原文有些部分寫的比較生澀或者沒有提到關鍵點上,是以我想稍微續叼寫的更具體一些,以及我對這種類型漏洞的一些思考或者是對我的啟發,以及安全研究人員和産品開發人員對安全的了解方式。

 這名叫Tamir Zahavi-Brunner的安全研究在2019年的7月底發現兩個高通QSEECOM接口的漏洞,一個是條件競争的漏洞CVE-2019-14041,一個就是我今天要講的核心記憶體映射相關的Use-After-Free漏洞CVE-2019-14040。

 簡單介紹一下這個QSEECOM接口,它是一個核心驅動連接配接使用者态Normal world和Secure world的一個橋梁,Secure world就是我們常說的Trustzone/TEE/Security Enclave安全運作環境,Normal world就是非安全運作環境,這個高通的QSEECOM接口可以實作一些從使用者态加載/解除安裝一些安全的TA(Trust Applcation)到TrustZone中去運作,比如我們手機常用的指紋/人臉識别的應用,這些應用都是在TrustZone中運作的,在這種運作環境下,可以保證我們使用者的關鍵隐私不被竊取,這個QSEECOM架構如下。

 要想了解這個漏洞的成因,需要先了解這個QSEECOM接口的功能處理邏輯,使用者态通過ION裝置(一個記憶體管理器,可以通過打開/dev/ion進行通路)申請的記憶體可以通過QSEECOM接口映射到核心位址空間,可供核心或者TrustZone通路,而對于QSEECOM驅動模型中(/dev/qseecom)提供給使用者的接口有open/close/ioctl,對應着QSEECOM核心處理函數為qseecom_open/qseecom_ioctl/qseecom_release。

漏洞成因:

說到Use-After-Free漏洞,我們需要先了解記憶體在哪裡Free掉的,然後是在哪裡Use的,如何Use的。

Free操作過程:

 使用者态每次打開qseecom裝置(/dev/qseecom),都會在核心态生成一個qseecom_dev_handle的結構指針,這個結構指針會被關閉qseecom裝置(使用者态通過close函數)或者來自使用者的IO操作号QSEECOM_IOCTL_UNLOAD_APP_REQ請求予以銷毀,需要了解這個結構指針的銷毀過程,那麼得先了解這個指針的初始化過程。

打開qseecom裝置時會調用qseecom_open配置設定一個qseecom_dev_handle結構體

static int qseecom_open(struct inode *inode, struct file *file)
{
    int ret = 0;
    struct qseecom_dev_handle *data;
        data = kzalloc(sizeof(*data), GFP_KERNEL);//配置設定qseecom_dev_handle結構體記憶體
    if (!data)
        return -ENOMEM;
    file->private_data = data;
    data->abort = 0;
    …
           

 然後使用者通過QSEECOM_IOCTL_SET_MEM_PARAM_REQ ioctl請求通過函數qseecom_set_client_mem_param來建立使用者态ion記憶體在核心位址空間的映射,而qseecom_set_client_mem_param函數通過copy_from_user函數來擷取使用者傳遞的ion使用者記憶體的位址資訊以及這個記憶體的長度資訊,我把關鍵的代碼标示出來(markdown文法好像無法标示代碼塊裡面的特定行的代碼)。

static int qseecom_set_client_mem_param(struct qseecom_dev_handle *data,
                        void __user *argp)
{
    ion_phys_addr_t pa;
    int32_t ret;
    struct qseecom_set_sb_mem_param_req req;
    size_t len;
    /* Copy the relevant information needed for loading the image */
    if (copy_from_user(&req, (void __user *)argp, sizeof(req)))
        return -EFAULT;
    ...
    data->client.ihandle = ion_import_dma_buf_fd(qseecom.ion_clnt,
                        req.ifd_data_fd);//擷取client的ihandle資訊
    ...
    /* Get the physical address of the ION BUF */
    ret = ion_phys(qseecom.ion_clnt, data->client.ihandle, &pa, &len);//擷取使用者态送出ion虛拟記憶體所映射的實體記憶體址與真實長度資訊
    if (ret) {
        pr_err("Cannot get phys_addr for the Ion Client, ret = %d\n",
            ret);
        return ret;
    }
    if (len < req.sb_len) {
        pr_err("Requested length (0x%x) is > allocated (%zu)\n",
            req.sb_len, len);
        return -EINVAL;
    }
    /* Populate the structure for sending scm call to load image */
    data->client.sb_virt = (char *) ion_map_kernel(qseecom.ion_clnt,
                            data->client.ihandle);
    if (IS_ERR_OR_NULL(data->client.sb_virt)) {
        pr_err("ION memory mapping for client shared buf failed\n");
        return -ENOMEM;
    }
    data->client.sb_phys = (phys_addr_t)pa;//
    data->client.sb_length = req.sb_len;//
    data->client.user_virt_sb_base = (uintptr_t)req.virt_sb_base;//完善資訊
    return 0;
}           

這個代碼流程如下:

高通TrustZone接口QSEECOM Use-After-Free漏洞分析高通QSEECOM接口漏洞(CVE-2019-14040)分析阿裡安全(侯客)

我們從qseecom_dev_handle結構體上能夠發現client是它的子成員結構體

struct qseecom_dev_handle {
    enum qseecom_client_handle_type type;
    union {
        struct qseecom_client_handle client;//這個指針沒有置空
        struct qseecom_listener_handle listener;
    };
    bool released;
…

struct qseecom_client_handle {
    u32  app_id;
    u8 *sb_virt;
    phys_addr_t sb_phys;
    unsigned long user_virt_sb_base;
    size_t sb_length;
    struct ion_handle *ihandle;        /* Retrieve phy addr */
    char app_name[MAX_APP_NAME_SIZE];
    u32  app_arch;
    struct qseecom_sec_buf_fd_info sec_buf_fd[MAX_ION_FD];
    bool from_smcinvoke;
};           

 而銷毀qseecom_dev_handle結構指針的時候隻是把子成員結構體client的子成員ion_handle結構指針ihandle給置空了,client結構體的其它成員并沒有置空,也就是說client結構體中的sb_virt位址還sb_length的值還是殘留的,這也為後續的freed的記憶體重新use提供了前提。

static int qseecom_unmap_ion_allocated_memory(struct qseecom_dev_handle *data)
{
    int ret = 0;
    if (!IS_ERR_OR_NULL(data->client.ihandle)) {
        ion_unmap_kernel(qseecom.ion_clnt, data->client.ihandle);//解除使用者态ion記憶體到核心态的映射
        ion_free(qseecom.ion_clnt, data->client.ihandle);//
        data->client.ihandle = NULL; //隻是把這個指針置空了
    }
    return ret;
}           

Use的過程:

 上面我們已經講了qseecom_dev_handle的銷毀的過程,接下來我們看看攻擊者是如何使用釋放掉的記憶體的。

 我們知道當釋放掉的記憶體被以同樣大小以及同樣的記憶體配置設定式來申請的時候,之前釋放掉的記憶體是很容易被重新命中的,同理常見于浏覽器use-after-free漏洞通過heap spray的方式進行大量記憶體申請來命中之前被釋放掉的對象。之前我們說過了,通過qseecom_open打開qseecom裝置的時候會配置設定一個qseecom_dev_handle結構體,但是很不幸的是這個初始化過程也沒有完全把這片記憶體給清0。

static int qseecom_open(struct inode *inode, struct file *file)
{
    int ret = 0;
    struct qseecom_dev_handle *data;
    data = kzalloc(sizeof(*data), GFP_KERNEL);
    if (!data)
        return -ENOMEM;
    file->private_data = data;
    data->abort = 0;
    data->type = QSEECOM_GENERIC;
    data->released = false;
    memset((void *)data->client.app_name, 0, MAX_APP_NAME_SIZE);//似乎還差一點點           

這個初始化前後的記憶體對比是這樣的

高通TrustZone接口QSEECOM Use-After-Free漏洞分析高通QSEECOM接口漏洞(CVE-2019-14040)分析阿裡安全(侯客)

 接下來就是use過程的關鍵了,我們的目标就是能夠使用這些free掉的結構中殘留的資料,如何能夠保證殘留資料可用,第一,殘留的關鍵資料不被接下來的流程所覆寫,第二,保護流程正常走下去,現有的qseecom_dev_handle結構不被無效的操作釋放,滿足這兩條,後續的正常業務處理邏輯就會use之前殘留的free掉的記憶體完成free掉記憶體的use。為了保證滿足第二條,我們需要滿足qseecom_dev_handle成員client的ihandle指針不能為空(__validate_send_service_cmd_inputs會檢查),因為之前釋放的時候這裡被置空了。好的,現在隻需要保證第一條,關鍵的殘留資料不被覆寫就好了。

 為了達到這個殘留資料不被覆寫的目标,隻需要使用者态發送一個QSEECOM_IOCTL_SET_MEM_PARAM_REQ ioctl請求,且使用者送出的ION記憶體配置設定的長度資訊大于實際使用者所配置設定的大小即可(例如使用者隻配置設定了0x1000位元組記憶體,但是使用者送出給核心說我配置設定了0x2000個位元組,當然核心也不是傻子,你說多少就多少,核心說我要檢查一下,檢查發現,好小子你才配置設定了0x1000位元組的記憶體,你卻告訴我有0x2000位元組,是不是當我傻,核心就立即傳回操作出錯的資訊給使用者),還記得上面提到的qseecom_set_client_mem_param函數處理流程嗎? 雖然核心直接傳回操作錯誤告之給使用者态,但是最重要的是qseecom_dev_handle指針沒有被銷毀,而且就是因為這個錯誤的操作,那個殘留資料也沒有被覆寫,且結構體裡面的ihandle也指派了不為空,兩個條件都滿足了,然後接下來的正常業務處理邏輯将會把之前殘留的sb_virt/sb_phys位址用于記憶體讀寫操作,完成真正的use操作。

當然最後這個漏洞的修補過程也比較簡單,把client結構成員全部清空即可。

高通TrustZone接口QSEECOM Use-After-Free漏洞分析高通QSEECOM接口漏洞(CVE-2019-14040)分析阿裡安全(侯客)

 寫到這裡漏洞分析過程就結束了,這個漏洞的利用危害,我覺得比較容易實作的一點可能是洩露一些敏感資訊,這個需要關聯上下文深入研究,作者提到可能用于提權擷取root權限,我覺得還是挺麻煩的,而且需要把不太可控的讀寫轉化成可控的讀寫,比較複雜,最終也有可能利用不成功,因為越是複雜的系統摻雜的噪音越多,排查起來比較麻煩,加上核心态的調試困難以及,而且對記憶體布局要求也非常高。

最後的一些思考:

 也是我覺得比較有意思的一點,這個漏洞的根源當然是釋放的記憶體沒有清空,但是有一個很重要點就是核心态和使用者态的狀态機制不同步造成的(不知道這樣說對不對),比如核心傳回給使用者說,我判斷了,你給我的資訊不對,你的行為不對,我警告過你了,但是使用者根本不管,我繼續做我認為是正确的事情,從這裡可以看出安全研究人員與開發人員對于安全風險視角的不同了,或者可以看出安全研究人員是如何定位攻擊面,如何挖掘漏洞的。

參考

https://android.googlesource.com/kernel/msm/+/2786ec57c52839f02802c01b0a12f24255064b10/drivers/misc/qseecom.c https://source.codeaurora.org/quic/la/kernel/msm-3.18/commit/?id=c4f42c24e02ce82392d8f8fe215570568380c8ab https://github.com/tamirzb/CVE-2019-14040

繼續閱讀