LOGO OA教程 ERP教程 模切知識(shí)交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

GPU 驅(qū)動(dòng)漏洞:窺探驅(qū)動(dòng)漏洞利用的技術(shù)奧秘

freeflydom
2024年12月16日 9:31 本文熱度 742

本文嘗試以 GPU 漏洞為引介紹圍繞 GPU 驅(qū)動(dòng)這一攻擊面,安全研究人員對(duì)內(nèi)核漏洞利用技術(shù)做的一些探索。

背景介紹

目前移動(dòng) SOC 平臺(tái)上由多個(gè)硬件模塊組成,常見的硬件模塊有:CPU、GPU、Modem基帶處理器、ISP(圖像處理器)等,這些硬件模塊通過硬件總線互聯(lián),協(xié)同完成任務(wù)。

對(duì)于 GPU 驅(qū)動(dòng)漏洞研究來說,我們需要關(guān)注的一個(gè)關(guān)鍵特性是 GPU 和 CPU 共用同一塊 RAM。 在 CPU 側(cè)操作系統(tǒng)通過管理 CPU MMU 的頁表來實(shí)現(xiàn)虛擬地址到物理地址的映射

GPU 也有自己的 MMU,不過 GPU 的頁表由 CPU 內(nèi)核中的 GPU 驅(qū)動(dòng)管理,從而限制 GPU 能夠訪問的物理地址范圍。

在實(shí)際的業(yè)務(wù)使用中,一般是 CPU 側(cè)分配一段物理內(nèi)存,然后映射給 GPU , GPU 從共享內(nèi)存中取出數(shù)據(jù)完成計(jì)算、渲染后再將結(jié)果寫回共享內(nèi)存,從而完成 GPU 與 GPU 之間的交互。對(duì)于 GPU 驅(qū)動(dòng)安全研究來說,特殊的攻擊面在于由于其需要維護(hù) GPU 頁表,這個(gè)過程比較復(fù)雜,涉及到內(nèi)核中的各個(gè)模塊的配合,非常容易出現(xiàn)問題,歷史上也出現(xiàn)了多個(gè)由于 GPU 頁表管理失誤導(dǎo)致的安全漏洞

以 ARM Mali 驅(qū)動(dòng)為例,這幾年出現(xiàn)的幾個(gè)比較有代表性的漏洞如下:

漏洞類型漏洞原語
CVE-2021-39793頁權(quán)限錯(cuò)誤篡改 只讀映射到用戶進(jìn)程的物理頁
CVE-2021-28664頁權(quán)限錯(cuò)誤篡改 只讀映射到用戶進(jìn)程的物理頁
CVE-2021-28663GPU MMU 操作異常物理頁 UAF
CVE-2023-4211條件競(jìng)爭(zhēng) UAFSLUB 對(duì)象 UAF
CVE-2023-48409整數(shù)溢出堆溢出
CVE-2023-26083內(nèi)核地址泄露內(nèi)核地址泄露
CVE-2022-46395條件競(jìng)爭(zhēng) UAF物理頁 UAF

其中前 3 個(gè)漏洞是管理 GPU 頁表映射時(shí)的漏洞,后面幾個(gè)就是常規(guī)驅(qū)動(dòng)漏洞類型

?

CVE-2021-28664

分析代碼下載:https://armkeil.blob.core.windows.net/developer/Files/downloads/mali-drivers/kernel/mali-bifrost-gpu/BX304L01B-SW-99002-r19p0-01rel0.tar

先以最簡(jiǎn)單的漏洞開始講起,這個(gè)漏洞算是 Mali 第一個(gè)出名的漏洞了,同期出道的還有 CVE-2021-28664,這個(gè)漏洞是由 Project Zero 捕獲的在野利用,該漏洞的 Patch 如下

 static struct kbase_va_region *kbase_mem_from_user_buffer(
                struct kbase_context *kctx, unsigned long address,
                unsigned long size, u64 *va_pages, u64 *flags)
 {
[...]
+       int write;
[...]
+       write = reg->flags & (KBASE_REG_CPU_WR | KBASE_REG_GPU_WR);
+
 #if KERNEL_VERSION(4, 6, 0) > LINUX_VERSION_CODE
        faulted_pages = get_user_pages(current, current->mm, address, *va_pages,
 #if KERNEL_VERSION(4, 4, 168) <= LINUX_VERSION_CODE && \
 KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE
-                       reg->flags & KBASE_REG_CPU_WR ? FOLL_WRITE : 0,
-                       pages, NULL);
+                       write ? FOLL_WRITE : 0, pages, NULL);
 #else
-                       reg->flags & KBASE_REG_CPU_WR, 0, pages, NULL);
+                       write, 0, pages, NULL);
 #endif
 #elif KERNEL_VERSION(4, 9, 0) > LINUX_VERSION_CODE
        faulted_pages = get_user_pages(address, *va_pages,
-                       reg->flags & KBASE_REG_CPU_WR, 0, pages, NULL);
+                       write, 0, pages, NULL);
 #else
        faulted_pages = get_user_pages(address, *va_pages,
-                       reg->flags & KBASE_REG_CPU_WR ? FOLL_WRITE : 0,
-                       pages, NULL);
+                       write ? FOLL_WRITE : 0, pages, NULL);
 #endif

Patch 的關(guān)鍵點(diǎn)在于將 get_user_pages 參數(shù)中的 reg->flags & KBASE_REG_CPU_WR 換成了 reg->flags & (KBASE_REG_CPU_WR | KBASE_REG_GPU_WR) ,這兩個(gè)標(biāo)記的作用如下:

  • KBASE_REG_CPU_WR:表示 reg 能夠已寫權(quán)限映射到用戶態(tài)進(jìn)程
  • KBASE_REG_GPU_WR: 表示 reg 能夠已寫權(quán)限映射到 GPU

reg 的類型為 struct kbase_va_region , MALI 驅(qū)動(dòng)中使用 kbase_va_region 管理物理內(nèi)存,包括物理內(nèi)存的申請(qǐng)/釋放、GPU/CPU 頁表映射管理等。

圖中的關(guān)鍵要素如下:

  • kbase_va_region 中 cpu_alloc 和 gpu_alloc 指向 kbase_mem_phy_alloc ,表示該 reg 擁有的物理頁,且大部分場(chǎng)景下 cpu_alloc = gpu_alloc
  • kbase_va_region 的 flags 字段控制驅(qū)動(dòng)映射 reg 時(shí)的權(quán)限,假如 flags 為 KBASE_REG_CPU_WR 表示該 reg 能夠被 CPU 映射為可寫權(quán)限,如果沒有該 flag 則不允許將 reg 以可寫模式映射到 CPU 進(jìn)程,確保無法進(jìn)程修改這些物理頁

核心觀點(diǎn):驅(qū)動(dòng)利用 kbase_va_region 表示一組物理內(nèi)存,這組物理內(nèi)存可以被 CPU 上的用戶進(jìn)程和 GPU 分別映射,映射的權(quán)限由 reg->flags 字段控制.

回到漏洞本身,其調(diào)用路徑中的關(guān)鍵代碼如下:

  • kbase_api_mem_import

    1. u64 flags = import->in.flags;

    2. kbase_mem_import(kctx, import->in.type, u64_to_user_ptr(import->in.phandle), import->in.padding, &import->out.gpu_va, &import->out.va_pages, &flags);

      1. copy_from_user(&user_buffer, phandle

      2. uptr = u64_to_user_ptr(user_buffer.ptr);

      3. kbase_mem_from_user_buffer(kctx, (unsigned long)uptr, user_buffer.length, va_pages, flags)

        1. struct kbase_va_region *reg = kbase_alloc_free_region(rbtree, 0, *va_pages, zone);
        2. kbase_update_region_flags(kctx, reg, *flags// 根據(jù)用戶態(tài)提供的 flags 設(shè)置 reg->flags
        3. faulted_pages = get_user_pages(address, *va_pages, reg->flags & KBASE_REG_GPU_WR, 0, pages, NULL);

漏洞在于傳遞 get_user_pages 參數(shù)是只考慮了 KBASE_REG_GPU_WR 情況,沒有考慮 KBASE_REG_CPU_WR,當(dāng) reg->flags 為 KBASE_REG_CPU_WR 時(shí) get_user_pages 的第三個(gè)參數(shù)為 0

/*
 * This is the same as get_user_pages_remote(), just with a
 * less-flexible calling convention where we assume that the task
 * and mm being operated on are the current task's and don't allow
 * passing of a locked parameter.  We also obviously don't pass
 * FOLL_REMOTE in here.
 */
long get_user_pages(unsigned long start, unsigned long nr_pages,
		unsigned int gup_flags, struct page **pages,
		struct vm_area_struct **vmas)
{
	return __get_user_pages_locked(current, current->mm, start, nr_pages,
				       pages, vmas, NULL, false,
				       gup_flags | FOLL_TOUCH);
}

get_user_pages 的作用的是根據(jù)用戶進(jìn)程提供的 va (start)遍歷進(jìn)程頁表,返回的是 va 對(duì)應(yīng)物理地址對(duì)應(yīng)的 page 結(jié)構(gòu)體指針,結(jié)果保存到 pages 數(shù)組中。

即根據(jù) task_struct->mm 找到進(jìn)程頁表,遍歷頁表獲取物理地址

其中如果 gup_flags 為 1,表示獲取 va 對(duì)應(yīng) page 后會(huì)寫入 page 對(duì)應(yīng)的物理頁,然后在 get_user_pages 內(nèi)部需要對(duì)只讀頁面和 COW 頁面做額外處理,避免這些特殊 va 對(duì)應(yīng)的物理頁被修改導(dǎo)致非預(yù)期行為。

  • 如果 vma 為只讀,API 返回錯(cuò)誤碼
  • VA 的映射為 COW 頁,在 API 內(nèi)會(huì)完成寫時(shí)拷貝,并返回新分配的 page

當(dāng) gup_flags 為 0 時(shí)則直接返回頁表遍歷的結(jié)果(P0)

對(duì)于這個(gè)漏洞而言,我們可以創(chuàng)建一個(gè) reg->flags 為 KBASE_REG_CPU_WR 的 kbase_va_region,再導(dǎo)入頁面時(shí)就可以獲取進(jìn)程中任意 va 對(duì)應(yīng) page 到 kbase_va_region,最后再將其以可寫權(quán)限映射到用戶態(tài)進(jìn)程,這樣就可以實(shí)現(xiàn)篡改進(jìn)程中任意只讀映射對(duì)應(yīng)的物理頁。

這一原語要進(jìn)一步利用需要依賴操作系統(tǒng)的機(jī)制,首先介紹最簡(jiǎn)單的一種利用方式,Linux 內(nèi)核在處理磁盤中的文件系統(tǒng)時(shí),會(huì)對(duì)從磁盤中讀取的物理頁做緩存來加速文件訪問的性能,同時(shí)減少重復(fù)文件物理頁,減少開銷

如果所示:

  • 當(dāng)進(jìn)程嘗試讀取物理頁時(shí),比如只讀權(quán)限 mmap ,內(nèi)核會(huì)搜索 page cache 如果找到就直接返回,否則就從磁盤中加載物理頁到 Page Cache 中,然后返回
  • 如果是寫則會(huì)有對(duì)應(yīng)的 flush cache 機(jī)制

具體來說,當(dāng)兩個(gè)進(jìn)程同時(shí)以只讀權(quán)限 mmap libc.so 文件時(shí),這兩個(gè)進(jìn)程的 VA 會(huì)指向同一個(gè)物理頁

這樣當(dāng)我們利用漏洞修改 Page Cache 中的物理頁后,其他進(jìn)程也會(huì)受到影響,因?yàn)槎际怯成涞耐粔K物理地址,因此攻擊思路就來了:

  • 只讀映射 libc.so 利用漏洞篡改其在 Page Cache 中物理頁,在其中注入 shellcode,等高權(quán)限進(jìn)程調(diào)用時(shí)就能提權(quán)
  • 類似的手法修改 /etc/passwd 完成提權(quán)

除了修改文件系統(tǒng)的 Page Cache 外,在 Android 平臺(tái)上還有一個(gè)非常好的目標(biāo),binder 驅(qū)動(dòng)會(huì)往用戶態(tài)進(jìn)程映射只讀 page,里面的數(shù)據(jù)結(jié)構(gòu)為 flat_binder_object,binder_transaction_buffer_release 里面會(huì)使用 flat_binder_object->handle,相關(guān)代碼如下:

首先通過 binder_get_node 查找 node,然后會(huì)調(diào)用 binder_put_node 減少 node 的引用計(jì)數(shù),當(dāng) node 引用為0時(shí)會(huì)釋放 node。

由于 flat_binder_object 所在物理頁用戶態(tài)無法修改,所以可以保證這個(gè)流程的正確性,當(dāng)我們只讀物理頁寫漏洞篡改 flat_binder_object->handle 指向另一個(gè) node 時(shí),觸發(fā) binder_transaction_buffer_release 就能導(dǎo)致 node 引用計(jì)數(shù)不平衡

最后可以將漏洞轉(zhuǎn)換為 binder_node 的UAF,然后采用 CVE-2019-2205 的利用方式進(jìn)行漏洞利用即可。

此外類似的漏洞在 2016 年就已經(jīng)出現(xiàn)在高通 GPU 驅(qū)動(dòng)中,CVE-2016-2067

同樣的業(yè)務(wù)場(chǎng)景,也意味著同類型的漏洞也可能會(huì)產(chǎn)生

?

CVE-2021-28663

該漏洞是 Mali 驅(qū)動(dòng)在管理 GPU 物理頁映射時(shí)導(dǎo)致的物理頁 UAF 漏洞,為了能夠理解該漏洞,首先需要對(duì) Mali 驅(qū)動(dòng)的相關(guān)代碼有所了解,上節(jié)提到 Mali 使用 kbase_va_region 對(duì)象表示物理內(nèi)存資源,然后 CPU 用戶進(jìn)程 和 GPU 可以按需映射,對(duì)物理內(nèi)存進(jìn)行訪問。

kbase_va_region 的創(chuàng)建位于 kbase_api_mem_alloc 接口,其關(guān)鍵代碼如下:

  • kbase_api_mem_alloc

    • kbase_mem_alloc(kctx, alloc->in.va_pages, alloc->in.commit_pages, alloc->in.extent, &flags, &gpu_va);

      1. reg = kbase_alloc_free_region(rbtree, 0, va_pages, zone); // 分配reg

      2. kbase_reg_prepare_native(reg, kctx, base_mem_group_id_get(*flags))

        1. reg->cpu_alloc = kbase_alloc_create(kctx, reg->nr_pages, KBASE_MEM_TYPE_NATIVE, group_id);
        2. reg->gpu_alloc = kbase_mem_phy_alloc_get(reg->cpu_alloc);
      3. kbase_alloc_phy_pages(reg, va_pages, commit_pages) // 為 reg 分配物理內(nèi)存

      4. if *flags & BASE_MEM_SAME_VA

        • kctx->pending_regions[cookie_nr] = reg;
        • cpu_addr = vm_mmap(kctx->filp, 0, va_map, prot, MAP_SHARED, cookie); // 映射物理內(nèi)存到 GPU 和 CPU 頁表
      5. else

        • kbase_gpu_mmap(kctx, reg, 0, va_pages, 1) // 映射物理內(nèi)存到 GPU 頁表

          • 編輯 GPU 頁表插入映射
          • atomic_inc(&alloc->gpu_mappings); // 增加 gpu_mappings 記錄其被 GPU 的引用情況

對(duì)于 BASE_MEM_SAME_VA 情況驅(qū)動(dòng)會(huì)做特殊處理,SAME_VA 的意思是在映射 reg 時(shí),GPU 和 CPU 的虛擬地址是一樣的,這樣可能是為了便于數(shù)據(jù)傳遞時(shí),之間進(jìn)行指針傳遞。

如果沒有設(shè)置 BASE_MEM_SAME_VA 則會(huì)之間將物理內(nèi)存映射到 GPU,否則就會(huì)通過 vm_mmap --> kbase_mmap --> kbasep_reg_mmap 將物理內(nèi)存以同樣的 VA 映射到 GPU 和 CPU 側(cè)。

兩者均是使用 kbase_gpu_mmap 將 reg 對(duì)應(yīng)的物理內(nèi)存映射到 GPU 的頁表中.

kbase_va_region 的釋放位于 kbase_api_mem_free 接口,其關(guān)鍵代碼如下:

  • kbase_api_mem_free

    • reg = kbase_region_tracker_find_region_base_address(kctx, gpu_addr);

    • err = kbase_mem_free_region(kctx, reg);

      • kbase_gpu_munmap(kctx, reg); // 刪除 GPU 映射

      • kbase_free_alloced_region(reg);

        1. kbase_mem_phy_alloc_put(reg->cpu_alloc);
        2. kbase_mem_phy_alloc_put(reg->gpu_alloc);
        3. kbase_va_region_alloc_put(kctx, reg);

這個(gè)的大體邏輯是先根據(jù) gpu_addr 找到 reg,然后釋放 reg 和 reg->xx_alloc 的引用,對(duì)于這種復(fù)雜的對(duì)象管理,可以先按照正常流程分析下對(duì)象之間的關(guān)系, kbase_va_region 中與生命周期相關(guān)的字段如下:

上圖表示的是 kbase_api_mem_alloc 創(chuàng)建非 SAME_VA 內(nèi)存的場(chǎng)景,kbase_gpu_mmap 執(zhí)行后會(huì)對(duì) gpu_mappings 加一,然后通過 kbase_api_mem_free 釋放時(shí),會(huì)將 kbase_va_region 和 kbase_mem_phy_alloc 的引用計(jì)數(shù)減成0,從而釋放兩個(gè)對(duì)象

如果是 SAME_VA 的情況如下,區(qū)別在于 SAME_VA 內(nèi)存在 kbase_api_mem_alloc 中會(huì)調(diào)用 vm_mmap 把 reg 同時(shí)映射到 CPU 和 GPU 側(cè),這就需要增加對(duì)應(yīng)的引用計(jì)數(shù)(va_refcnt、kref、gpu_mappings),然后在 munmap 進(jìn)程 VA 時(shí),減少對(duì)應(yīng)的引用計(jì)數(shù)

對(duì)驅(qū)動(dòng)的對(duì)象管理有大概的認(rèn)知后,具體看下漏洞相關(guān)的兩個(gè)接口 kbase_api_mem_alias 和 kbase_api_mem_flags_change,分別利用的功能:

  • kbase_api_mem_alias:創(chuàng)建別名映射,即新分配一個(gè) reg 與其他已有的 reg 共享 kbase_mem_phy_alloc
  • kbase_api_mem_flags_change:釋放 kbase_mem_phy_alloc 中的物理頁

kbase_api_mem_alias 的關(guān)鍵代碼如下:

  • kbase_mem_alias

    1. reg = kbase_alloc_free_region(&kctx->reg_rbtree_same, 0, *num_pages, KBASE_REG_ZONE_SAME_VA);
    2. reg->gpu_alloc = kbase_alloc_create(kctx, 0, KBASE_MEM_TYPE_ALIAS,
    3. reg->cpu_alloc = kbase_mem_phy_alloc_get(reg->gpu_alloc);
    4. aliasing_reg = kbase_region_tracker_find_region_base_address( kctx, (ai[i].handle.basep.handle >> PAGE_SHIFT) << PAGE_SHIFT);
    5. alloc = aliasing_reg->gpu_alloc;
    6. reg->gpu_alloc->imported.alias.aliased[i].alloc = kbase_mem_phy_alloc_get(alloc);
    7. kctx->pending_regions[gpu_va] = reg;

主要是增加了 alloc 的引用計(jì)數(shù) (kref),然后將其放入 kctx->pending_regions,之后進(jìn)程再通過 mmap 完成 CPU 和 GPU 映射 (kbase_context_mmap

if (reg->gpu_alloc->type == KBASE_MEM_TYPE_ALIAS) {
	u64 const stride = alloc->imported.alias.stride;
	for (i = 0; i < alloc->imported.alias.nents; i++) {  // 映射 aliased 中的各個(gè) alloc 并增加 gpu_mappings
		if (alloc->imported.alias.aliased[i].alloc) {
			err = kbase_mmu_insert_pages(kctx->kbdev,
					&kctx->mmu,
					reg->start_pfn + (i * stride),
					alloc->imported.alias.aliased[i].alloc->pages + alloc->imported.alias.aliased[i].offset,
					alloc->imported.alias.aliased[i].length,
					reg->flags & gwt_mask,
					kctx->as_nr,
					group_id);
			kbase_mem_phy_alloc_gpu_mapped(alloc->imported.alias.aliased[i].alloc);
		}
	}

創(chuàng)建別名映射進(jìn)程調(diào)用 mmap 前后, reg 對(duì)象相關(guān)引用情況如下:

在 kbase_api_mem_alias 里面增加 aliased[i]->kref 確保其在使用過程中不會(huì)被釋放,然后 kbase_mmap 映射內(nèi)存時(shí)增加 aliased[i]->gpu_mappings 記錄其被 GPU 映射的次數(shù),同時(shí)增加 reg->va_refcnt 記錄其被 CPU 映射的次數(shù),這個(gè)流程是沒有問題的,通過引用計(jì)數(shù)確保 aliased 中的對(duì)象不會(huì)釋放。

問題就出在 kbase_api_mem_flags_change 能在不釋放 alloc 時(shí)釋放其中的物理頁:

  • kbase_api_mem_flags_change

    • kbase_mem_flags_change

      1. reg = kbase_region_tracker_find_region_base_address(kctx, gpu_addr);
      2. 校驗(yàn) atomic_read(&reg->cpu_alloc->gpu_mappings) > 1
      3. kbase_mem_evictable_make(reg->gpu_alloc); // 釋放 alloc 中的物理頁

kbase_api_mem_flags_change 可以利用 kbase_mem_evictable_make 將 gpu_alloc 放到驅(qū)動(dòng)自己管理的鏈表中(kctx->evict_list)當(dāng)內(nèi)核指向 shrink 操作時(shí)驅(qū)動(dòng)會(huì)釋放該鏈表上掛的所有 gpu_alloc。

  • kbase_mem_evictable_make

    1. kbase_mem_shrink_cpu_mapping(kctx, gpu_alloc->reg, 0, gpu_alloc->nents); // 移除 CPU 映射
    2. list_add(&gpu_alloc->evict_node, &kctx->evict_list); // 加到鏈表中

shrink 時(shí)釋放 kbase_mem_phy_alloc 物理頁的代碼:

  • kbase_mem_evictable_reclaim_scan_objects

    1. kbase_mem_shrink_gpu_mapping(kctx, alloc->reg, 0, alloc->nents); // 刪除 GPU 頁表項(xiàng)

      • kbase_mmu_teardown_pages(kctx->kbdev, &kctx->mmu, reg->start_pfn + new_pages, delta, kctx->as_nr);
    2. kbase_free_phy_pages_helper(alloc, alloc->evicted); // 釋放物理頁

kbase_mem_flags_change 在調(diào)用 kbase_mem_evictable_make 前會(huì)校驗(yàn) gpu_mappings ,大概意思是如果這個(gè) reg 被 GPU 多次映射了就不能執(zhí)行物理內(nèi)存釋放操作,但是回到 alias 的流程,在 kbase_api_mem_alias 結(jié)束后,aliased 數(shù)組中的 gpu_mappings 還是 1

此時(shí)調(diào)用 kbase_mem_flags_change 將 aliased[i] 放到 kctx->evict_list,此時(shí) alloc->pages 里面的值沒有變化

然后再調(diào)用 mmap 映射 kbase_mem_alias 創(chuàng)建的 reg 將 aliased[i] 中的物理頁(alloc->pages)映射到 GPU 側(cè),假設(shè)為映射的 VA 為 ALIAS_VA

最后觸發(fā) shrink 機(jī)制,釋放 aliased[i] 中的物理頁,之后 ALIAS_VA 還指向已經(jīng)釋放的物理頁,導(dǎo)致物理頁 UAF.

再次回顧漏洞根因,漏洞是驅(qū)動(dòng)在建立 別名映射時(shí)對(duì) gpu_mappings 的管理不當(dāng),結(jié)合 kbase_api_mem_flags_change 釋放物理頁的邏輯,達(dá)成物理頁 UAF,這種漏洞的挖掘個(gè)人理解需要先分析內(nèi)存對(duì)象(堆、物理內(nèi)存)的生命周期,然后組合各個(gè) API 的時(shí)序看是否會(huì)產(chǎn)生非預(yù)期行為,重點(diǎn)還是對(duì)象的釋放、申請(qǐng)、復(fù)制等邏輯。

物理頁 UAF 的漏洞利用技術(shù)目前已經(jīng)比較成熟,這里列幾個(gè)常用的方式:

  • 篡改進(jìn)程頁表:通過 fork + mmap 大量分配進(jìn)程頁表占位釋放的物理頁,然后通過 GPU 修改頁表實(shí)現(xiàn)任意物理內(nèi)存讀寫
  • 篡改 GPU 頁表:通過 GPU 驅(qū)動(dòng)接口分配 GPU 頁表占位釋放的物理頁,然后通過 GPU 修改頁表實(shí)現(xiàn)任意物理內(nèi)存讀寫
  • 篡改內(nèi)核對(duì)象:通過噴射內(nèi)核對(duì)象(比如 task_struct、cred)占位,然后 GPU 修改對(duì)象實(shí)現(xiàn)利用

?

CVE-2022-46395

前面兩個(gè)漏洞的利用路徑大概是:發(fā)現(xiàn)一個(gè)新漏洞,挖掘一種新漏洞利用方式完成利用,本節(jié)這個(gè)漏洞則是將漏洞轉(zhuǎn)換為 CVE-2021-28663 ,因?yàn)?28663 的能力確實(shí)太強(qiáng)大了,物理頁 UAF 的利用簡(jiǎn)單、直接,目前堆上的漏洞利用也逐步往物理頁UAF 轉(zhuǎn)換(Dirty Pagetable

漏洞是一個(gè)條件競(jìng)爭(zhēng)漏洞,kbase_vmap_prot 后其他線程可以釋放 mapped_evt 對(duì)應(yīng)的物理頁

static int kbasep_write_soft_event_status(
        struct kbase_context *kctx, u64 evt, unsigned char new_status)
{
    ...
    mapped_evt = kbase_vmap_prot(kctx, evt, sizeof(*mapped_evt),
                     KBASE_REG_CPU_WR, &map);
    //Race window start
    if (!mapped_evt)                  
        return -EFAULT;
    *mapped_evt = new_status;
    //Race window end
    kbase_vunmap(kctx, &map);
    return 0;
}

為了擴(kuò)大 race 的時(shí)間窗,作者利用 timerfd 時(shí)鐘中斷技術(shù)

  migrate_to_cpu(0);   //<------- pin this task to a cpu
  int tfd = timerfd_create(CLOCK_MONOTONIC, 0);   //<----- creates timerfd
  //Adds epoll watchers
  int epfds[NR_EPFDS];
  for (int i=0; i<NR_EPFDS; i++)
    epfds[i] = epoll_create1(0);
  for (int i=0; i<NR_EPFDS; i++) {
    struct epoll_event ev = { .events = EPOLLIN };
    epoll_ctl(epfd[i], EPOLL_CTL_ADD, fd, &ev);
  }  
  
  timerfd_settime(tfd, TFD_TIMER_ABSTIME, ...);  //<----- schedule tfd to be available at a later time
  ioctl(mali_fd, KBASE_IOCTL_SOFT_EVENT_UPDATE,...); //<---- tfd becomes available and interrupts this ioctl  

大致思路就是在 kbase_vmap_prot 和 *mapped_evt 之間出發(fā)時(shí)鐘中斷,從而擴(kuò)大時(shí)間窗,在兩步之間釋放 mapped_evt 對(duì)應(yīng)的物理頁,就能夠達(dá)到物理頁 UAF 的能力。

mapped_evt 在頁內(nèi)的偏移可控,寫的內(nèi)容為 0 或者 1,總結(jié)下來漏洞的原語是物理內(nèi)存 UAF 寫,寫的值只能 0 或者 1

static inline struct kbase_mem_phy_alloc *kbase_alloc_create(
        struct kbase_context *kctx, size_t nr_pages,
        enum kbase_memory_type type, int group_id)
{
    ...
    size_t alloc_size = sizeof(*alloc) + sizeof(*alloc->pages) * nr_pages;
    ...
    /* Allocate based on the size to reduce internal fragmentation of vmem */
    if (alloc_size > KBASE_MEM_PHY_ALLOC_LARGE_THRESHOLD)
        alloc = vzalloc(alloc_size);
    else
        alloc = kzalloc(alloc_size, GFP_KERNEL);
    ...
}

kbase_alloc_create 分配 kbase_mem_phy_alloc 時(shí)會(huì)調(diào)用 vzalloc 分配內(nèi)存,vzalloc 的邏輯是根據(jù)大小計(jì)算分配的物理頁數(shù)目,然后逐次調(diào)用 alloc_page 分配物理頁,利用這個(gè)邏輯可以比較快速的占位剛剛釋放的物理頁(slab cross cache 時(shí)間相對(duì)較長(zhǎng))

根據(jù)之前的漏洞分析,我們知道 gpu_mappings 控制的物理頁的釋放,如果通過 UAF 將其修改為 0 或者 1,就能提前釋放一個(gè)被別名映射的 kbase_mem_phy_alloc 中的物理頁,導(dǎo)致物理頁UAF

struct kbase_mem_phy_alloc {
	struct kref           kref;
	atomic_t              gpu_mappings;
	size_t                nents;
	struct tagged_addr    *pages;
	struct list_head      mappings;

實(shí)現(xiàn)無限制的物理頁 UAF 讀寫后,就是常規(guī)的漏洞利用流程了。這個(gè)漏洞利用的核心是利用 GPU 驅(qū)動(dòng)的物理內(nèi)存管理結(jié)構(gòu),將一個(gè)受限的 UAF 寫轉(zhuǎn)化為 不受限的 物理頁 UAF.

?

利用非GPU漏洞攻擊 GPU

前面的案例都是利用 GPU 自身漏洞,這個(gè)案例則是將其他驅(qū)動(dòng)、模塊漏洞(攝像頭驅(qū)動(dòng)的 堆溢出漏洞) 的漏洞 轉(zhuǎn)換為 GPU 漏洞,進(jìn)而實(shí)現(xiàn)物理頁 UAF 漏洞,核心思路與 CVE-2022-46395 一致,就是篡改 kbase_mem_phy_alloc 的 gpu_mappings 為 0,構(gòu)造物理頁 UAF

static inline struct kbase_mem_phy_alloc *kbase_alloc_create(
        struct kbase_context *kctx, size_t nr_pages,
        enum kbase_memory_type type, int group_id)
{
    ...
    size_t alloc_size = sizeof(*alloc) + sizeof(*alloc->pages) * nr_pages;
    ...
    alloc = kzalloc(alloc_size, GFP_KERNEL);
    ...
}

一個(gè)比較有意思的點(diǎn)是研究員發(fā)現(xiàn)及時(shí)安卓?jī)?nèi)核啟用了 MTE,仍然有 50% 的概率能夠完成溢出而不被檢測(cè),且及時(shí) MTE 檢測(cè)到溢出,也不會(huì)導(dǎo)致內(nèi)核 Panic,只是殺掉用戶進(jìn)程,這樣就給了攻擊者無限嘗試的能力,相當(dāng)于 Bypass 了 MTE.

總結(jié)

從 CVE-2021-28663/CVE-2021-28664 開始研究人員逐漸重視并投入到 GPU 驅(qū)動(dòng)安全領(lǐng)域,從一開始的挖掘 GPU 特有漏洞,到后面逐步將各種通用漏洞往 GPU 漏洞上轉(zhuǎn)換,核心原因在于 GPU 驅(qū)動(dòng)本身的能力太強(qiáng)大了,只要能夠控制 GPU硬件的頁表,就能實(shí)現(xiàn)任意物理頁的讀寫,而且由于是獨(dú)立的硬件,可以直接 Bypass 掉 CPU 側(cè)的安全特性(比如 KNOX、PAC、MTE),Patch 內(nèi)核代碼。

GPU 安全研究還帶來了另一個(gè)漏洞利用方向,GPU 驅(qū)動(dòng)由于要管理物理內(nèi)存,所以容易出現(xiàn)物理內(nèi)存 UAF,物理 UAF 的利用手段被發(fā)掘后,大家發(fā)現(xiàn)這個(gè)原語實(shí)在太強(qiáng)大了,后面涌現(xiàn)了很多將不同漏洞轉(zhuǎn)換為物理頁UAF的技術(shù),比如 Dirty Pagetable、USMA、 pipe_buffer->page 指針劫持等。

從 GPU 攻擊的路徑來看,也可以了解到一點(diǎn),即漏洞的修復(fù)并不代表漏洞生命的結(jié)束,如果一個(gè)漏洞的原語過于強(qiáng)大、好用,就可以考慮將其他漏洞往這上面轉(zhuǎn)換,從而復(fù)用歷史的漏洞利用經(jīng)驗(yàn)。

?

參考鏈接

轉(zhuǎn)自博客園,作者h(yuǎn)ac425


該文章在 2024/12/16 9:37:05 編輯過
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點(diǎn)晴ERP是一款針對(duì)中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國內(nèi)大量中小企業(yè)的青睞。
點(diǎn)晴PMS碼頭管理系統(tǒng)主要針對(duì)港口碼頭集裝箱與散貨日常運(yùn)作、調(diào)度、堆場(chǎng)、車隊(duì)、財(cái)務(wù)費(fèi)用、相關(guān)報(bào)表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點(diǎn),圍繞調(diào)度、堆場(chǎng)作業(yè)而開發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點(diǎn)晴WMS倉儲(chǔ)管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購管理,倉儲(chǔ)管理,倉庫管理,保質(zhì)期管理,貨位管理,庫位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號(hào)管理軟件。
點(diǎn)晴免費(fèi)OA是一款軟件和通用服務(wù)都免費(fèi),不限功能、不限時(shí)間、不限用戶的免費(fèi)OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved

黄频国产免费高清视频,久久不卡精品中文字幕一区,激情五月天AV电影在线观看,欧美国产韩国日本一区二区
亚洲人成在线网站精品 | 欧美日韩中文字幕久久久不卡 | 日本天天做夜夜做 | 五月天在线精品电影 | 中文字幕欧美久久久 | 亚洲中文制服丝袜欧美精品 |