歡迎光臨
每天分享高質量文章

KPTI補丁分析

1 KPTI概述

KPTI(Kernel PageTable Isolation)全稱核心頁表隔離。KPTI是由KAISER補丁修改而來。之前,行程地址空間被分成了核心地址空間和使用者地址空間。其中核心地址空間對映到了整個物理地址空間,而使用者地址空間只能對映到指定的物理地址空間。核心地址空間和使用者地址空間共用一個頁全域性目錄表(PGD表示行程的整個地址空間),meltdown漏洞就恰恰利用了這一點。攻擊者在非法訪問核心地址和CPU處理異常的時間視窗,透過訪存微指令獲取核心資料。為了徹底防止使用者程式獲取核心資料,可以令核心地址空間和使用者地址空間使用兩組頁表集(也就是使用兩個PGD)。

圖1 修改後的行程地址空間

2 問題

當然事情並沒有那麼簡單,有兩個問題:

問題1: X86架構中,在背景關係切換的間隙(註意是間隙)記憶體中的一部分需要對核心空間和使用者空間都是有效的,也就是說在切換CR3之前核心就要開始工作了。 

問題2:修改CR3時,CPU會沖刷TLB,從而帶來很大的效能問題

3 KPTI實現機制

在KAISER的論文中針對這兩個問題,提出了以下解決方案

3.1 影子地址空間(Shadow Address Spaces)

KPTI中每個行程有兩個地址空間,第一個地址空間只能在核心態下訪問,可以建立到內核和使用者的對映(不過使用者空間受SMAP和SMEP保護,具體可查詢Intel手冊)。第二個地址空間被稱為影子地址空間,只包含使用者空間。不過由於涉及到背景關係切換,所以在影子地址空間中必須包含部分核心地址,用來建立到中斷入口和出口的對映。

當中斷在使用者態發生時,就涉及到切換CR3暫存器,從影子地址空間切換到使用者態的地址空間。中斷上半部的要求是盡可能的快,從而切換CR3這個操作也要求盡可能的快。為了達到這個目的,KAISER中將核心空間的PGD和使用者空間的PGD連續的放置在一個8KB的記憶體空間中。這段空間必須是8K對齊的,這樣將CR3的切換操作轉換為將CR3值的第13位(由低到高)的置位或清零操作,提高了CR3切換的速度。

使用者空間和核心空間的PGD分佈示意圖

3.2 核心空間的最小對映

上文提到,在從影子地址空間切換到核心地址空間的過程中,為了使得核心在CR3切換之前就能夠開始工作,影子地址空間必須包含部分核心地址空間。

如下圖所示,陰影處就是在陷入核心態過程中,需要對映的核心資料和程式碼。圖a 是常規OS的行程的地址空間。圖b和圖c是頁表隔離後的行程地址空間,兩者的區別再與是否使用了SMAP和SMEP機制。

那麼如何確定影子地址空間應該對映那些核心資料呢?由於中斷可能發生在使用者態,所以應該包含中斷向量表(IDT),中斷棧,中斷向量。另外核心棧,GDT和TSS也應該對映到影子地址空間。

4 效能與開銷(performance and overhead)

4.1 TLB

在intel手冊中提到,線性地址的高位被稱為頁號(page number),低位被稱為頁偏移(page offset, 如果頁大小是4K則是低12位)。物理地址的高位被稱為頁框(page frame)。

TLB用於加速從線性地址到物理地址的轉換,本質上還是一種快取。TLB使用頁號來獲取線性地址所對應的頁的基地址。TLB中的每一項包含以下內容:

  1. 頁號對應頁的物理地址

  2. 頁的訪問許可權(R/W,U/S )

  3. 頁屬性(dirty flag,memory type)

    

圖4-1  基於TLB的訪存過程

一個處理器可能包含不同型別的TLB,比如專用於取指令的TLB和用於資料訪問的TLB

切換CR3時,CPU會隱式的沖刷TLB。TLB的miss penalty可以達到10 – 100 個 時鐘週期(clock cycles)。記憶體中的一些頁(比如共享庫)的一些頁是由所有的行程共享的。這些頁由頁表項的全域性位(G)來標示。共享頁並不會參與TLB的隱式沖刷。

有兩種方法防止資料的洩露,第一種需要衝刷整個TLB,而第二種則是禁用頁表項的全域性位。

透過PCID的使用可以緩解由於沖刷TLB帶來的效能問題。

4.2 Process-Context Identifiers(PCID)

PCID全稱行程背景關係標示符,CR4暫存器的PCIDE位表示是否啟用CPU的PCID功能。PCIDE=1表示啟用PCID。啟用之後,CR3(頁目基址暫存器)的低12位用來儲存PCID。每個行程都有一個PCID,當未啟用PCID時,CR3的低12位為全0(000H)。

Intel手冊對於TLB失效的行為作出了很詳細的解釋,在使用mov指令修改CR3時會使TLB失效(mov to CR3),具體行為如下:

  1. 如果CR4.PCIDE = 0(表示未啟用PCID),CPU會使所有與PCID 000H關聯的TLB快取項(TLB entry)失效,除了全域性頁。

  2. 如果CR4.PCIDE = 1(啟用PCID),並且源運算元的第63位=0,源運算元的0-11位為指定的PCID。那麼CPU會使所有與指定PCID關聯的TLB快取項失效。TLB中與其他PCID關聯的TLB快取項並不會失效。

  3. 如果CR4.PCIDE=1,並且源運算元的第63位=1,CPU不會對TLB做任何的失效操作。

5 程式碼分析

我們選取linux4.15版本作為演示,說明KPTI補丁的核心中的分佈 
這是4.15版本和PTI(pagetable isolation)有關的diff stat. 可以看到共涉及到45個檔案的修改,插入了1636行程式碼,刪除202行程式碼。

增加程式碼行數的前三名是

  1. mm/pti.c

  2. arch/x86/include/asm/tlbflush.h

  3. arch/x86/entry/calling.h

5.1 arch/x86/mm/pti.c

pti.c是補丁新增的檔案. 其中的入口函式是pti_init(), 該函式在init/main.c中的mm_init()函式中呼叫。這個檔案中的函式總共分為兩種,第一種類似pti_clone_user_shared(),將內核的頁表項複製到使用者空間。第二種類似pti_user_pagetable_walk_p4d(unsigned long address),根據引數中的虛擬地址,得到該地址相應的頁表項指標。

  1. void __init pti_init(void)

  2. {

  3.    if (!static_cpu_has(X86_FEATURE_PTI))

  4.        return;

  5.    pr_info("enabled\n");

  6.    pti_clone_user_shared();

  7.    pti_clone_entry_text();

  8.    pti_setup_espfix64();

  9.    pti_setup_vsyscall();

  10. }

5.2 arch/x86/include/asm/tlbflush.h

該檔案包含一系列的有關TLB flush的函式 
在KPTI中並不僅僅使用PCID,由於核心中的行程地址空間標示符必須從0開始。所以ASID是地址空間真正的標示符。又因為補丁中行程的地址空間有兩個部分,所以我們需要兩個PCID。kPCID核心空間使用的標示符。uPCID使用者空間使用的標示符。

  1. * ASID  - [0, TLB_NR_DYN_ASIDS-1]

  2. *         the canonical identifier for an mm

  3. *

  4. * kPCID - [1, TLB_NR_DYN_ASIDS]

  5. *         the value we write into the PCID part of CR3; corresponds to the

  6. *         ASID+1, because PCID 0 is special.

  7. *

  8. * uPCID - [2048 + 1, 2048 + TLB_NR_DYN_ASIDS]

  9. *         for KPTI each mm has two address spaces and thus needs two

  10. *         PCID values, but we can still do with a single ASID denomination

  11. *         for each mm. Corresponds to kPCID + 2048.

  1. #define CR3_HW_ASID_BITS        12

  2. # define PTI_CONSUMED_PCID_BITS 1

  3. /*

  4. * 6 because 6 should be plenty and struct tlb_state will fit in two cache

  5. * lines.

  6. */

  7. #define TLB_NR_DYN_ASIDS    6

5.3 /arch/x86/entry/calling.h

calling.h 是系統呼叫的入口函式,用於處理系統呼叫時的暫存器儲存操作。系統呼叫涉及到由使用者態到核心態的切換。所以calling.h需要修改。

以下一系列的彙編宏指令涉及到使用者PGD和核心PGD的切換. 下麵我們挑選幾個宏進行說明:

1. SWITCH_TO_KERNEL_CR3

該宏的任務是清楚CR3儲存的PCID,並將CR3的第13置1,從而使其指向核心PGD

  1. .macro SWITCH_TO_KERNEL_CR3 scratch_reg:req

  2.    ALTERNATIVE "jmp .Lend_\@", "", X86_FEATURE_PTI

  3.    mov %cr3, \scratch_reg

  4.    ADJUST_KERNEL_CR3 \scratch_reg

  5.    mov \scratch_reg, %cr3

  6. .Lend_\@:

  7. .endm

2. SWITCH_TO_USER_CR3_NOSTACK 
該宏的任務是根據行程的ASID判斷其TLB是否需要flush, 如果不需要就在CR3中標記為no_flush。隨後將kPCID轉換為uPCID,並使CR3指向使用者PGD。 
這一切都在很短的時間內發生,因為它們只是對CR3暫存器的置位操作。

  1. .macro SWITCH_TO_USER_CR3_NOSTACK scratch_reg:req scratch_reg2:req

  2.    ALTERNATIVE "jmp .Lend_\@", "", X86_FEATURE_PTI

  3.    mov %cr3, \scratch_reg

  4.    ALTERNATIVE "jmp .Lwrcr3_\@", "", X86_FEATURE_PCID

  5.    /*

  6.     * Test if the ASID needs a flush.

  7.     */

  8.    movq    \scratch_reg, \scratch_reg2

  9.    andq    $(0x7FF), \scratch_reg     /* mask ASID */

  10.    bt  \scratch_reg, THIS_CPU_user_pcid_flush_mask

  11.    jnc .Lnoflush_\@

  12.    /* Flush needed, clear the bit */

  13.    btr \scratch_reg, THIS_CPU_user_pcid_flush_mask

  14.    movq    \scratch_reg2, \scratch_reg

  15.    jmp .Lwrcr3_pcid_\@

  16. .Lnoflush_\@:

  17.    movq    \scratch_reg2, \scratch_reg

  18.    SET_NOFLUSH_BIT \scratch_reg

  19. .Lwrcr3_pcid_\@:

  20.    /* Flip the ASID to the user version */

  21.    orq $(PTI_USER_PCID_MASK), \scratch_reg

  22. .Lwrcr3_\@:

  23.    /* Flip the PGD to the user version */

  24.    orq     $(PTI_USER_PGTABLE_MASK), \scratch_reg

  25.    mov \scratch_reg, %cr3

  26. .Lend_\@:

  27. .endm

參考

http://www.wowotech.net/memory_management/tlb-flush.html 

Intel® 64 and IA-32 Architectures Software Developer Manuals 

linux 4.15原始碼 

[1] D. Gruss, M. Lipp, M. Schwarz, R. Fellner, C. Maurice, and S. Mangard, “KASLR is dead: Long live KASLR,” Lect. Notes Comput. Sci. (including Subser. Lect. Notes Artif. Intell. Lect. Notes Bioinformatics), vol. 10379 LNCS, pp. 161–176, 2017.

後記

由於作者水平有限,可能某些方面敘述的不夠準確,望各位讀者海涵。也希望大家能夠來踴躍拍磚。

贊(0)

分享創造快樂

© 2024 知識星球   網站地圖