核心排程

核心排程支援允許使用者空間定義可以共享一個核心的任務組。這些組可以用於安全用例(一組任務不信任另一組),或用於效能用例(某些工作負載可能受益於在同一核心上執行,因為它們不需要共享核心的相同硬體資源,或者如果它們確實共享硬體資源需求,則可能偏好不同的核心)。本文件僅描述安全用例。

安全用例

跨 HT 攻擊涉及攻擊者和受害者在同一核心的不同超執行緒 (Hyper Thread) 上執行。MDS 和 L1TF 就是此類攻擊的例子。完全緩解跨 HT 攻擊的唯一方法是停用超執行緒 (HT)。核心排程是一種排程器功能,可以緩解某些(並非所有)跨 HT 攻擊。它透過確保只有使用者指定的受信任組中的任務才能共享核心來允許安全地開啟 HT。這種核心共享的增加也可以提高效能,但不能保證效能總是會提高,儘管在許多實際工作負載中情況確實如此。理論上,核心排程的目標是至少與停用超執行緒時的效能一樣好。實際上,這在大多數情況下是如此,但並非總是如此:因為核心中 2 個或更多 CPU 之間的排程決策同步涉及額外的開銷——尤其是在系統負載較輕時。當 total_threads <= N_CPUS/2 時,額外的開銷可能導致核心排程的效能比停用 SMT 時更差,其中 N_CPUS 是 CPU 的總數。請務必始終測量您的工作負載效能。

用法

透過 CONFIG_SCHED_CORE 配置選項啟用核心排程支援。使用此功能,使用者空間定義可以在同一核心上共同排程的任務組。核心排程器使用此資訊來確保不在同一組中的任務絕不會同時在一個核心上執行,同時盡最大努力滿足系統的排程要求。

核心排程可以透過 PR_SCHED_CORE prctl 介面啟用。此介面支援建立核心排程組,以及任務加入和退出已建立的組。

#include <sys/prctl.h>

int prctl(int option, unsigned long arg2, unsigned long arg3,
        unsigned long arg4, unsigned long arg5);
選項

PR_SCHED_CORE

arg2

操作命令,必須是以下之一

  • PR_SCHED_CORE_GET -- 獲取 pid 的 core_sched cookie。

  • PR_SCHED_CORE_CREATE -- 為 pid 建立一個新的唯一 cookie。

  • PR_SCHED_CORE_SHARE_TO -- 將 core_sched cookie 推送給 pid

  • PR_SCHED_CORE_SHARE_FROM -- 從 pid 拉取 core_sched cookie。

arg3

操作所應用的 pid 任務。

arg4

操作所應用的 pid_type。它是以 PR_SCHED_CORE_SCOPE_ 為字首的宏常量之一。例如,如果 arg4 是 PR_SCHED_CORE_SCOPE_THREAD_GROUP,則此命令的操作將針對 pid 任務組中的所有任務執行。

arg5

指向一個無符號長長整型的使用者空間指標,用於儲存由 PR_SCHED_CORE_GET 命令返回的 cookie。對於所有其他命令,應為 0。

為了使程序能夠將 cookie 推送到或從程序拉取 cookie,它需要對該程序具有 ptrace 訪問模式:PTRACE_MODE_READ_REALCREDS

構建任務層次結構

構建共享 cookie 從而共享核心的執行緒/程序層次結構的最簡單方法是依賴於核心排程 cookie 在 fork/clone 和 exec 之間繼承的事實,從而為“初始”指令碼/可執行檔案/守護程序設定 cookie 將使所有派生的子程序都位於同一核心排程組中。

設計/實現

每個被標記的任務都在核心內部分配一個 cookie。正如用法中提到的,具有相同 cookie 值的任務被認為是相互信任並共享一個核心的。

基本思想是,每次排程事件都嘗試為核心的所有同級選擇任務,以便在任何時間點,核心上執行的所有選定任務都是受信任的(相同的 cookie)。核心執行緒被認為是受信任的。空閒任務被視為特殊,因為它信任一切,一切也信任它。

在核心的任何同級上發生 schedule() 事件期間,如果同級有任務排隊,則選擇該同級核心上優先順序最高的任務並將其分配給呼叫 schedule() 的同級。對於核心中其餘的同級,如果它們各自的執行佇列中有可執行的任務,則選擇具有相同 cookie 的優先順序最高的任務。如果同 cookie 的任務不可用,則選擇空閒任務。空閒任務是全域性受信任的。

一旦為核心中的所有同級選擇了任務,就會向那些選擇了新任務的同級傳送 IPI。同級在收到 IPI 後將立即切換到新任務。如果為同級選擇了空閒任務,則認為該同級處於強制空閒狀態。即,它可能在其執行佇列中有任務要執行,但它仍然必須執行空閒。更多內容將在下一節中介紹。

超執行緒的強制空閒

排程器會盡力尋找相互信任的任務,以確保所有被選擇排程的任務在核心中都具有最高優先順序。然而,有些執行佇列可能包含與核心中最高優先順序任務不相容的任務。為了安全而非公平,如果最高優先順序任務與核心範圍內的最高優先順序任務不被信任,則一個或多個同級可能會被強制選擇較低優先順序的任務。如果一個同級沒有受信任的任務可執行,它將被排程器強制空閒(空閒執行緒被排程執行)。

當選擇最高優先順序任務執行時,會向同級傳送一個重新排程 IPI 以強制其進入空閒狀態。這導致了 4 種需要考慮的情況,具體取決於任一 HT 上執行的是 VM 還是常規使用者模式程序。

       HT1 (attack)            HT2 (victim)
A      idle -> user space      user space -> idle
B      idle -> user space      guest -> idle
C      idle -> guest           user space -> idle
D      idle -> guest           guest -> idle

請注意,為了更好的效能,我們不等待目標 CPU(受害者)進入空閒模式。這是因為 IPI 的傳送會立即將目標 CPU 從使用者空間帶入核心模式,或者在客戶機情況下引起 VMEXIT。充其量,這隻會洩露一些排程器元資料,這些元資料可能不值得保護。在某些架構上,IPI 也有可能接收過晚,但在 x86 的情況下尚未觀察到這種情況。

信任模型

核心排程透過為任務組分配相同的 cookie 值標籤來維護它們之間的信任關係。當系統在核心排程下啟動時,所有任務都被認為是相互信任的。這是因為核心排程器在使用者空間使用上述介面通訊之前沒有關於信任關係的資訊。換句話說,所有任務都具有預設 cookie 值 0,並被認為是系統範圍內的受信任任務。也避免了強制空閒執行 cookie-0 任務的同級。

一旦使用者空間使用上述介面對任務集進行分組,這些組內的任務被認為是相互信任的,但不信任組外的任務。組外的任務也不信任組內的任務。

核心排程的侷限性

核心排程試圖保證只有受信任的任務才能在一個核心上併發執行。但是,可能會存在一小段時間視窗,在此期間,不受信任的任務併發執行,或者核心可能與不受核心信任的任務併發執行。

IPI 處理延遲

核心排程只選擇受信任的任務一起執行。IPI 用於通知同級切換到新任務。但是,在某些架構上(在 x86 上尚未觀察到),接收 IPI 可能存在硬體延遲。這可能導致攻擊者任務在其同級接收 IPI 之前開始在 CPU 上執行。儘管進入使用者模式時會重新整理快取,但同級上的受害者任務可能會在攻擊者開始執行後在快取和微架構緩衝區中填充資料,這可能導致資料洩露。

核心排程未解決的開放性跨 HT 問題

1. 針對 MDS

核心排程無法防護在使用者模式下執行的同級與在核心模式下執行的同級之間的 MDS 攻擊。即使所有同級執行相互信任的任務,當核心代表任務執行程式碼時,它不能信任在同級中執行的程式碼。此類攻擊對於同級 CPU 模式(宿主模式或客戶機模式)的任何組合都可能發生。

2. 針對 L1TF

核心排程無法防止 L1TF 客戶機攻擊者利用客戶機或宿主受害者。這是因為客戶機攻擊者可以製作無效的 PTE,而這些 PTE 不會因為存在漏洞的客戶機核心而反轉。唯一的解決方案是停用 EPT(擴充套件頁表)。

對於 MDS 和 L1TF,如果客戶機 vCPU 配置為不相互信任(透過單獨標記),那麼客戶機到客戶機的攻擊就會消失。或者,這可能是系統管理員策略,將客戶機到客戶機的攻擊視為客戶機問題。

解決這些問題的另一種方法是使系統中每個不受信任的任務都不信任其他所有不受信任的任務。雖然這會降低不受信任任務的並行性,但它仍然可以解決上述問題,同時允許系統程序(受信任任務)共享一個核心。

3. 保護核心 (IRQ, syscall, VMEXIT)

不幸的是,核心排程並不能保護在同級超執行緒上執行的核心上下文免受彼此的攻擊。緩解措施的原型已經發布到 LKML 以解決此問題,但這些視窗是否實際可被利用,以及原型的效能開銷是否值得(更不用說增加的程式碼複雜性),都存在爭議。

其他用例

核心排程的主要用例是在啟用 SMT 的情況下緩解跨 HT 漏洞。此功能還可用於其他用例:

  • 隔離需要整個核心的任務:示例包括即時任務、使用 SIMD 指令的任務等。

  • 批處理排程:需要一起排程的一組任務的要求也可以透過核心排程來實現。一個例子是 VM 的 vCPU。