英特爾最新終端故障漏洞的前世今生,還是亂序執行的鍋

現代微處理器緩存的簡史

現代計算機微處理器是複雜的機器,為了榨乾處理器的每一分算力,CPU做了很多的優化架構和設計。最早的計算機是相對簡單的機器。他們按照既定的順序執行用戶提供的程序,精確地遵循每個二進制編碼指令,提供可預測的程序流程和結果。這在計算的早期階段運行良好,很大程度上是因為計算機的不同部分以相似的速度運行。例如,處理器(CPU)並不比連接到它的存儲器芯片快很多。這樣,處理器只需簡單地從程序需要的內存加載數據。

隨著時間的推移,計算機不同組件的相對性能發生了翻天覆地的變化。微處理器在頻率方面以莫斯定理的速度飛速增加,從每秒處理幾千條指令到數百萬條,最終每秒數十億條指令。與此同時,可以安裝在單個芯片上的晶體管數量(面積)也幾何數的增加,從數千個增加到今天高端處理器上的數十億個晶體管。這需要越來越複雜的設計。然而,雖然計算機的其他部分也在高速發展(內存芯片從MB到幾十GB),但相對遠遠的落後於處理器的發展。由於這種性能差異導致的直接後果就是外部存儲器吞吐量成為了系統處理的瓶頸,處理器經常要等待數百,數千或數百萬的數據處理"週期"。

為了解決這個瓶頸,業界在採用了在處理中增加高速緩存的架構。高速緩存中存儲了最近使用的數據的拷貝,更接近處理器的功能單元(執行計算的部分)。數據值存儲在較慢的外部存儲器中,當它們由程序第一次到加載時,會在在處理器高速緩存中拷貝一份作為緩存下來。實際上,現代處理器(幾乎)從不直接對存儲在外部存儲器中的數據進行操作,都是直接從緩存中讀取數據。處理器的高速緩存也做了多層架構,其中最高級別(L1)最接近處理器的"核心",而較低級別(L2,L3等)則離的更遠些。每一級的存儲都有不同的功能和大小。

英特爾最新終端故障漏洞的前世今生,還是亂序執行的鍋

在數字電子產品中,人們常說你可以"小而快"或"大而慢",但很少能同時具有這兩種特徵。現代處理器中的L1數據高速緩存僅為32kB,而靠近外部存儲器的L3(有時稱為LLC,最後一級高速緩存)可能是32MB甚至更高。高速緩存的組成根據需求而定,一般來說都遵循包容性設計,即相同數據的拷貝將包含在高速緩存的多個級別內,具體取決於上最近一次使用的時間。處理器內的替換算法將清理L1高速緩存的較少使用的條目,使其在保持L2或L3內的拷貝的同時加載新數據。因此,最近使用的數據必須從內存中加載是很少見的,但是在需要時它可能會從更大,更慢,更低級別的緩存(L2,L3)中調入到L1。

英特爾最新終端故障漏洞的前世今生,還是亂序執行的鍋

緩存是多個單獨的計算核心,線程和程序之間的共享資源。現代處理器芯片往往包含多個內核。其中每個內核都像傳統的單處理器計算機一樣。每個程序可以執行單個程序或運行同一程序的多個線程,並與共享內存一起運行。每個核心都有自己的L1緩存,也可能有自己的L2緩存,而較大的L3通常被處理器中的所有內核共享。然後,處理器採用稱為高速緩存"一致性"的技術,使每個核心的存儲器位置的內部拷貝與存儲在另一個核心的高速緩存中的拷貝做同步。這是通過內核跟蹤內存的所有權並在每次更新內存時在內部消息機制來實現。

緩存的共享可以極大地提高性能、方便處理,但是這也一種潛在的漏洞根源。之前爆發的 "幽靈"和"熔斷"漏洞就是利用了緩存作為潛在的"旁路"漏洞,通過這些漏洞獲取系統的信息。在旁路分析中,數據不是直接訪問的,而是由於觀察系統的某些屬性而推斷得出的。在高速緩存的情況下,高速緩存存儲器之間的性能的相對差異,以及比處理器慢得多的內存是使用高速緩存架構的初衷開。不幸的是,可以通過測量訪問時間的相對差異來利確定一個存儲器位置是否位於高速緩存中。如果它包含在緩存中,則最近由於某些其他處理器活動而訪問它。作為示例,處理器在推測性執行期間加載的數據將改變高速緩存狀態。

虛擬和物理內存尋址

現代處理器高速緩存通常使用虛擬和物理存儲器地址的組合來引用數據。物理地址用於訪問主存儲器。從概念上講,你可以將計算機中的外部存儲器DIMM或DDR芯片視為從地址零開始的一個巨大的值數組,並繼續向上直到內存耗盡。物理內存的數量因機器而異,從典型筆記本電腦的8GB到現代高端服務器機器中的數百GB甚至更高。在早期,程序員必須明確跟蹤每個物理內存位置中包含的內容,並避免與使用內存的其他應用程序發生衝突。

現代的OS都使用虛擬內存的架構。虛擬內存意味著操作系統能夠為每個應用程序提供其自己視圖的對外隔離的內存存儲,對位置和大小等沒有限制也無需關注。每次程序需要訪問內存時,地址由稱為存儲器管理單元(MMU)的處理器內的特殊硬件轉換。 MMU與操作系統(OS)協同工作,操作系統創建和管理一組頁面表,將虛擬內存地址轉換為物理地址。物理內存被分成很小的塊,稱為頁面,大小通常為4KB。頁表包含這些頁面的索引,這樣一個4KB大小的虛擬地址範圍將轉換為4KB的物理範圍。作為進一步的優化,頁表本質上是分層的,其中單個地址通過幾個表的"遍歷"序列被解碼,直到它被完全翻譯。

英特爾最新終端故障漏洞的前世今生,還是亂序執行的鍋

MMU包含可以讀取甚至更新操作系統管理的頁表的特殊硬件。這些包括遍歷表格遍歷過程的頁面表"walker",以及可以更新頁表條目以指示最近訪問的數據的其他硬件。操作系統使用後者跟蹤哪些數據可以暫時"分頁"或"交換"到磁盤(如果最近沒有使用過)。頁表條目可以標記為"不存在",這意味著任何訪問相關地址的嘗試都會觸發稱為"頁面錯誤"的特殊條件,該信號指示操作系統採取行動。這樣,操作系統可以攔截嘗試訪問先前已被換出到磁盤的數據,將其數據從新讀取到內存中,恢復應用程序訪問。

從性能角度來看,頁表遍歷可能很耗時。操作系統管理的頁表存在於真實的物理內存中,讀取時必須將其加載到處理器中。遍歷一個頁表可能需要很多這樣的內存訪問,如果每次都發生這種訪問會非常慢。因此,處理器不會每次都從內存讀取,而是將這些表遍歷的結果緩存在單獨的處理器結構中,稱為轉換後備緩衝區(TLB)。因此,最近使用的轉換可以更快地解析為物理地址,因為處理器只需要搜索TLB。如果條目不存在,則處理器將執行更慢的內存頁面遍歷並更新TLB條目。這就是最近TLBleed漏洞的另一種針對TLB的無關攻擊的利用前提。

當程序讀取或寫入存儲器時,首先訪問通過最高級別(L1)數據高速緩存,次級緩存也叫虛擬索引,物理標記(VIPT)。這意味著緩存使用部分虛擬和部分物理地址來查找內存位置。當檢查負載的虛擬地址時,處理器將同時搜索TLB以進行虛擬到物理頁面轉換,同時開始通過使用單個頁面內的偏移來搜索高速緩存以尋找可能的匹配條目。因此,在幾乎每個現代處理器中,從虛擬存儲器位置讀取的過程都非常複雜。這種常見的設計優化解釋了為什麼L1數據高速緩存在具有4KB頁面大小的處理器上通常為32KB,因為它們受到單個頁面中可用的偏移位數的限制,開始高速緩存搜索的。

英特爾處理器則進一步優化了它們如何處理頁表遍歷和"終端故障"(不存在)的過程。在首先回顧現代處理器的隨機性質後,我們將進一步深入研究。如果你已經熟悉"熔斷"漏洞的原理,可以跳過下一節。

亂序和推測執行

與現代計算機平臺的其他部分相比,採用高速緩存實現了更快的微處理器性能改進。學術界和工業界共同努力創造了基礎架構的創新,例如亂序(OoO)和推測執行。隨著晶體管數量的增加和處理器變得越來越複雜,可能的優化進一步推進,但它們仍然建立在OoO架構基礎之上。

在OoO中,處理器在概念上分為"有序"前端和"無序"後端。前端將用戶程序作為輸入。該程序本質上是順序執行的,基於數據值的條件判斷,由代碼塊和偶爾的分支(例如"if"語句)跳轉到其他塊。有序前端將程序中包含的指令發送到無序後端。當調度這些指令進行調度時,在稱為重新排序緩衝器(ROB)的處理器結構內分配條目。 ROB實現了數據依賴性跟蹤,實現了OoO的關鍵創新,只要程序員最終無法區分,指令就可以按任何順序執行。它們只能看到與順序執行架構相同的效果。

ROB有效地用於將有序機器轉換成所謂的"數據流"機器,其中相關指令被迫等待執行直到它們的輸入值準備好。每當包含程序指令的ROB中的條目具有所有可用的相關數據值(例如從存儲器加載)時,它將被髮布給處理器功能單元,並且結果存儲回ROB中以供之後指令使用。隨著ROB中的條目老化(變成"機器中最老的指令"),它們被稱為已"退休"並且可供程序員可見的機器視圖使用。這被稱為"架構可見"狀態,它與從程序的順序執行獲得的狀態相同。以這種方式重新排序指令提供了顯著的加速。我們以下面一個偽代碼為示例:

LOAD R1

LOAD R2

R3 = R1 + R2

R4 = 2

R5 = R4 + 1

R6 = R3 + 1

示例使用字母R來指定稱為寄存器的處理器內部存儲器位置,或更廣泛地指定為GPR(通用寄存器)。通常只有少量寄存器,在Intel x86-64機器的情況下為16。為方便起見,我們將它們編號為R1,R2等,而實際上,它們還有其他名稱,例如RAX,RBX等。

在經典的順序執行架構中,前兩個指令可能導致機器等待("停止"),同時訪問慢速外部存儲器。緩存會加快速度,但即使這兩個值包含在處理器緩存的某個級別內,加載指令完成時仍可能會有一個小的延遲。通過OoO機制處理器將跳過這兩臺指令,雖然指令3取決於前兩個指令(具有"數據依賴性"),但指令4和5是獨立的。實際上,這兩條指令完全不依賴於先前的指令。它們可以隨時執行,甚至不依賴於外部存儲器。他們的結果將存儲在ROB中,直到早先的指令也完成為止,整個程序"退休"。

上一例中的指令編號6稱為數據依賴性。就像指令3如何依賴於加載存儲器位置L1和L2的結果一樣,指令6取決於添加這兩個存儲器位置的結果。現實中的程序往往有很多這樣的依賴項,都通過在ROB中跟蹤。

推測建立在OoO執行的基礎之上。在推測執行中,處理器再次以與編寫程序的順序不同的順序執行指令,但是它也推測程序代碼中的分支之外。

考慮一個程序語句,例如:

if(it_is_raining)

pack_umbrella();

值"it_is_raining"可能包含在(較慢的)外部存儲器中。因此,處理器能夠在等待分支條件"解決"的同時繼續執行其他有用的工作將是必要的。隨機處理器將基於歷史猜測(預測)分支的方向,而不是中斷(如在經典的,更簡單的設計中)。處理器將繼續執行分支之後的指令,但它將在ROB中標記結果以指示它們是推測性的並且可能需要被丟棄。處理器內的檢查點概念允許推測快速撤消而不會對應用可見,但是一些推測活動的也是有跡可循的。

英特爾最新終端故障漏洞的前世今生,還是亂序執行的鍋

在幽靈(Spectre)漏洞中,處理器分支預測器可以被欺騙(訓練)為預測某種方式。然後,可以推測性地執行代碼並且對處理器高速緩存具有可觀察的影響。如果我們可以在現有代碼中找到合適的"小工具",我們可能會導致故意推測執行通常不應該成為程序流一部分的代碼,例如超出數組的範圍(在Spectre-v1中),然後導致相關指令執行它將改變緩存中的位置,通過它我們可以推斷出我們不應該訪問的數據的值

英特爾終端故障漏洞

由於推測處理已經被廣泛使用,因此像英特爾這樣的處理器供應商做進一步擴展,以便推測處理器內的所有其他可能狀態。這包括在虛擬到物理存儲器地址的轉換期間推測MMU頁面遍歷器的頁表遍歷的結果。

英特爾最新終端故障漏洞的前世今生,還是亂序執行的鍋

英特爾將術語L1 Terminal Fault (終端故障)定義為在頁表遍歷期間發現頁表條目(PTE)"不存在"時出現的情況。這通常是因為操作系統已將頁面置換到磁盤(或尚未要求加載它)並將頁面標記為不存在以便觸發稍後的訪問錯誤。如前所述,交換錯覺允許操作系統提供比機器內物理內存更多的虛擬內存。在不存在頁面的情況下將發生頁面錯誤,然後操作系統可以確定需要從磁盤交換回的內存位置。

操作系統通過使用"不存在"PTE的位來存儲各種內務處理數據,例如包含頁面內容的磁盤上的物理位置。英特爾軟件開發人員手冊(SDM)規定,以這種方式標記為不存在的頁面將忽略所有剩餘的位(除當前位之外),以便操作系統可以使用它們。 Linux,Windows和其他操作系統都將這些PTE位大量用於"不存在"頁面,以支持交換以及各種其他允許的目的。

如前所述,英特爾等處理器使用通用優化,其中虛擬地址轉換與對虛擬索引,物理標記(VIPT)L1數據高速緩存的高速緩存訪​​問並行執行。作為進一步優化,英特爾意識到在最佳情況(關鍵邏輯路徑)下,從內存加載的數據值存在於緩存中,並且存在有效的頁錶轉換。換句話說,頁表不太可能具有標記為"不存在"的條目。因此,現代英特爾處理器會稍微延遲處理對當前位檢查,並將頁表條目(PTE)的內容直接轉發到緩存控制邏輯,同時執行所有其他檢查,包括條目是否有效。猜測漏洞與之前的熔斷(Meltdown)漏洞一樣,ROB被標記為指示是否應該引發"不存在"故障,但同時,處理器將繼續在程序中進一步推測,直到該故障生效。在這個小窗口期間,"不存在"頁面的L1數據高速緩存中存在的任何數據值仍將被轉發到相關指令。如果可以為地址創建(或導致創建)"不存在"頁表條目,並且如果該物理地址當前存在於L1中,則可以使用與熔斷(Meltdown)類似的攻擊來從物理地址讀取數據數據緩存。這被稱為L1終端故障攻擊(L1 Terminal Fault attack)。

在Linux上,攻擊者可以利用此漏洞並嘗試通過惡意使用mprotec()系統調用來破壞內核或其他應用程序,從而為可能存在於其中的感興趣的物理地址導致"不存在"的頁表條目緩存。如果他們可以欺騙其他應用程序(或內核)加載特定敏感的信息:例如加密密鑰,密碼或其他敏感數據。 則可以使用與熔斷漏洞利用代碼類似的攻擊來提取它。可以通過改變Linux生成"不存在"PTE的方式來減輕這種攻擊,使得某些物理地址位總是在PTE中設置(使用廉價的屏蔽操作),因此處理器仍將轉發"不存在"中的物理地址。但是,除了最極端的情況之外,它將是一個超出填充物理內存範圍的超大的物理地址。

故障影響

針對單機的L1TF攻擊可以通過幾行內核代碼(所有漏洞涉及版本,並且還被提交包含在上游Linux中)來緩解。此緩解措施機會不會有性能影響,需要及時升級系統。

不幸的是,此漏洞還有其他幾個組件。

一個涉及Software Guard Extensions(SGX)。 SGX是一種英特爾技術,也被稱為"安全飛地",用戶可以在其中提供安全的軟件代碼,這些代碼將在一個特殊的受保護"飛地"中運行,這將使該軟件甚至不被操作系統觀察到。 SGX的典型用例是為權限管理,加密和其他軟件提供篡改保護。 Red Hat不提供SGX軟件,該軟件通常由第三方直接擁有和管理。英特爾通過發佈處理器微碼更新來保護SGX,該更新旨在防止它通過L1TF漏洞的"Foreshadow"SGX特定變體受到損害。

L1TF的另一個變體涉及虛擬化用例。在虛擬化部署中,英特爾處理器採用稱為EPT(擴展頁表)的技術,其中頁表由管理程序,在該管理程序下運行的客戶操作系統和硬件共同管理。 EPT取代了一種較舊的純軟件方法,即虛擬機管理程序被迫使用影子頁面表。在較舊的設計中,每次客戶操作系統想要更新其自己的頁表時,管理程序都必須暫停客戶機,更新自己的影子表(由真實硬件使用),然後恢復客戶機。這對於確保guset永遠不會嘗試創建訪問虛擬機管理程序不允許的內存的頁表是必要​​的。

EPT顯著提高了性能,因為客戶機操作系統可以像在裸機上一樣管理自己的頁表。在EPT下,每次內存訪問都被多次翻譯,首先使用來客戶機頁表從客戶虛擬地址到客戶物理地址,然後通過虛擬機管理程序頁面表從客戶物理地址到主機物理地址。此過程允許本機裸機硬件輔助頁面轉換的所有好處,同時仍允許管理程序保持控制,因為它可以安排發生各種暫停,並管理哪些客戶物理內存可用。

當英特爾實施EPT時,它是現有內存頁面架構的擴展。翻譯的額外階段似乎可以簡單地添加到其他步驟之後。這樣處理很簡單方便,但是有一個小問題。在客戶虛擬到物理地址轉換中出現的終端故障情況可導致未翻譯的客戶物理地址被當做主機物理地址,並被讀入L1數據高速緩存。因此,惡意客戶端程序可能創建標記為"不存在"的EPT頁表條目,並且還包含它想要從中讀取的主機物理地址。如果該主機物理地址在L1數據高速緩存中,則它可以直接讀取它。

因此,如果惡意客戶機應用可以導致虛擬機管理程序(或其他客戶機)將秘密加載到L1數據高速緩存中(例如,僅通過在其正常操作期間使用數據值),它可以使用攻擊來提取該數據,類似於Meltdown。

L1TF是虛擬化架構的重大威脅,尤其是那些包含公有和私有虛擬機混合的環境中。幸運的是,L1TF漏洞可以通過適度的系統性能成本得到緩解。

該漏洞利用需要將數據緩存在被攻擊的主機的L1數據高速緩存中,對於攻擊者感興趣的信息,可以在返回到客戶虛擬機之前,對L1進行刷新。執行此刷新並非沒有成本,但是從L2到L1緩存的重新填充很快(僅幾百個週期)並且使用這些技術的處理器中存在的高帶寬內部總線。因此,開銷大約為百分之幾。

只要在受影響的計算機上使用虛擬化,就會自動啟用L1數據緩存刷新緩衝。

併發多線程(超線程)

處理器可以實現稱為併發多線程(SMT)的優化。 SMT由Susan Eggers發明,她今年早些時候因為她對該領域的貢獻而獲得了著名的Eckert-Mauchly獎。 Eggers在20世紀90年代意識到,通過將單個物理處理器內核分成幾個較輕的線程,可以實現更高的程序線程級並行性。 SMT不是複製完整核心的所有資源,而是僅複製使兩個單獨的執行線程同時運行所需的更重要的資源。這個想法是,在同一程序的兩個線程之間可以緊密地共享昂貴的(就晶體管數量而言)高速緩存等資源,因為它們通常對相同的數據上運行。這些線程實際上用於將有用數據提取到其共享緩存中,而不是破壞性地競爭,例如在"生產者 - 消費者"情況下,其中一個線程生成另一個線程使用的數據。

英特爾是SMT的早期商業採用者之一。它們的實現,稱為"超線程"已經生效,導致普通計算機用戶將其計算機中的核心和線程數量作為關鍵特性。在超線程中,單個核心中存在兩個對等線程(兄弟)。每個都是動態調度的,以便使用核心的可用資源,以便為在共享數據上運行的真正線程應用程序實現高達30%的吞吐量提高。同時,努力減少不緊密共享資源的兩個線程之間的無意破壞性爭奪(例如,在高速緩存佔用方面)的影響。

實際上,英特爾超線程的一般情況下非常好,在許多情況下,最終用戶很難區分超線程線程和其他物理內核。在Linux下,這些線程"邏輯處理器"信息(在/ proc /cpuinfo中)幾乎與完整處理器核心相同。兩者不同的唯一區分方法是查看關聯的信息,如輸出的"core id","cpu cores"和"siblings"字段的信息,或者解析此命令的更結構化的命令輸出,例如"lscpu"。當然,Linux調度程序知道不同之處,並且會嘗試不將不相關的線程安排到同一個內核上。儘管如此,在還有許多情況下(例如在HPC應用),不相關的線程之間的競爭可能超過性能提高。

在這些情況下,某些用戶通過BIOS設置禁用超線程。

不跨越不同工作負載拆分超線程線程的概念長期以來一直延伸到虛擬化領域。出於多種原因,長期有意義僅將完整核心(所謂的"核心調度")分配給虛擬機實例。共享單個核心的兩個不同虛擬機在分配底層高速緩存和其他資源時可能會干擾彼此的性能。然而,對於所有可能出現的潛在問題,長期以來一直很容易將線程視為廉價的額外內核。因此,現代虛擬機機的部署中,在同一核心的超線程之間拆分VM是很常見的,而OpenStack等技術通常會默認執行此操作。

雖然這從來都不是一個好主意,但在存在L1TF漏洞的情況下,對整體安全性的影響更更加突出。超線程同時運行("SMT"中的"S"),因此,一個線程可能正在運行管理程序代碼或另一個虛擬機實例,同時對等線程的客戶機執行惡意程序。當在對等線程上進入惡意客戶端時,將刷新L1數據高速緩存,但遺憾的是,不可能阻止它隨後觀察其對等線程執行的高速緩存加載。因此,如果兩個不同的虛擬機同時在同一個核心上運行,則很難保證它們不能相互執行L1TF攻擊並竊取機密。

L1TF對超線程的精確影響取決於具體的使用案例和所使用的虛擬化環境。在某些情況下,公共雲供應商(通常構建專用硬件以協助隔離)可能會採取措施使超線程安全。在其他情況下,例如在具有公有虛擬機的傳統企業環境中,可能需要禁用Intel超線程。由於這種情況因用例而異,從一個環境到另一個環境。


分享到:


相關文章: