作者 | Peter Bright
譯者 | qhwdw ? ? ? ? ? 共計翻譯:80 篇 貢獻時間:119 天
我們知道有問題,但是並不知道問題的詳細情況。
(本文發表於 1 月份)最近 Windows 和 Linux 都發送了重大安全更新,為防範這個尚未完全公開的問題,在最壞的情況下,它可能會導致效能下降多達一半。
在過去的幾周,Linux 核心陸續打了幾個補丁。Microsoft 自 11 月份開始也內部測試了 Windows 更新[1],並且它預計在下週二的例行補丁中將這個改進推送到主流 Windows 構建版中。Microsoft 的 Azure 也在下週的維護視窗中做好了安排,而 Amazon 的 AWS 也安排在週五對相關的設施進行維護。
自從 Linux 第一個補丁 (參見 KPTI:核心頁表隔離的當前的發展[2]) 明確描繪了出現的錯誤以後。雖然 Linux 和 Windows 基於不同的考慮,對此持有不同的看法,但是這兩個作業系統 —— 當然還有其它的 x86 作業系統,比如 FreeBSD 和 macOS[3] — 對系統記憶體的處理採用了相同的方式,因為對於作業系統在這一部分特性是與底層的處理器高度耦合的。
保持地址跟蹤
在一個系統中的每個記憶體位元組都是隱性編碼的,這些編碼數字是每個位元組的地址。早期的作業系統使用物理記憶體地址,但是,物理記憶體地址由於各種原因,它並不很合適。例如,在地址中經常會有空隙,並且(尤其是 32 位的系統上)物理地址很難操作,需要 36 位數字,甚至更多。
因此,現在作業系統完全依賴一個叫虛擬記憶體的概念。虛擬記憶體系統允許程式和核心一起在一個簡單、清晰、統一的環境中各自去操作。而不是使用空隙和其它奇怪的東西的物理記憶體,每個程式和核心自身都使用虛擬地址去訪問記憶體。這些虛擬地址是連續的 —— 不用擔心有空隙 —— 並且合適的大小也更便於操作。32 位的程式僅可以看到 32 位的地址,而不用管物理地址是 36 位還是更多位。
雖然虛擬地址對每個軟體幾乎是透明的,但是,處理器最終還是需要知道虛擬地址取用的物理地址是哪個。因此,有一個虛擬地址到物理地址的對映,它儲存在一個被稱為頁面表的資料結構中。作業系統構建頁面表,使用一個由處理器決定的佈局,並且處理器和作業系統在虛擬地址和物理地址之間進行轉換時就需要用到頁面表。
這個對映過程是非常重要的,它也是現代作業系統和處理器的重要基礎,處理器有專用的快取 — Translation Lookaside Buffer(簡稱 TLB)—— 它儲存了一定數量的虛擬地址到物理地址的對映,這樣就不需要每次都使用全部頁面。
虛擬記憶體的使用為我們提供了很多除了簡單定址之外的有用的特性。其中最主要的是,每個程式都有了自己獨立的一組虛擬地址,有了它自己的一組虛擬地址到物理地址的對映。這就是用於提供“記憶體保護”的關鍵技術,一個程式不能破壞或者篡改其它程式使用的記憶體,因為其它程式的記憶體並不在它的地址對映範圍之內。
由於每個行程使用一個單獨的對映,因此每個程式也就有了一個額外的頁面表,這就使得 TLB 快取很擁擠。TLB 並不大 —— 一般情況下總共可以容納幾百個對映 —— 而系統使用的頁面表越多,TLB 能夠包含的任何特定的虛擬地址到物理地址的對映就越少。
一半一半
為了更好地使用 TLB,每個主流的作業系統都將虛擬地址範圍一分為二。一半用於程式;另一半用於核心。當行程切換時,僅有一半的頁面表條目發生變化 —— 僅屬於程式的那一半。內核的那一半是每個程式公用的(因為只有一個核心)並且因此它可以為每個行程使用相同的頁面表對映。這對 TLB 的幫助非常大;雖然它仍然會丟棄屬於行程的那一半記憶體地址對映;但是它還保持著另一半屬於內核的對映。
這種設計並不是一成不變的。在 Linux 上做了一項工作,使它可以為一個 32 位的行程提供整個地址範圍,而不用在核心頁面表和每個行程之間共享。雖然這樣為程式提供了更多的地址空間,但這是以犧牲效能為代價的,因為每次核心程式碼需要執行時,TLB 重新載入內核的頁面表條目。因此,這種方法並沒有廣泛應用到 x86 的系統上。
在內核和每個程式之間分割虛擬地址的這種做法的一個負面影響是,記憶體保護被削弱了。如果內核有它自己的一組頁面表和虛擬地址,它將在不同的程式之間提供相同的保護;核心記憶體將是簡單的不可見。但是使用地址分割之後,使用者程式和核心使用了相同的地址範圍,並且從原理上來說,一個使用者程式有可能去讀寫核心記憶體。
為避免這種明顯不好的情況,處理器和虛擬地址系統有一個 “Ring” 或者 “樣式”的概念。x86 處理器有許多 Ring,但是對於這個問題,僅有兩個是相關的:“user” (Ring 3)和 “supervisor”(ring 0)。當執行普通的使用者程式時,處理器將置為使用者樣式 (Ring 3)。當執行核心程式碼時,處理器將處於 Ring 0 —— supervisor 樣式,也稱為核心樣式。
這些 Ring 也用於從使用者程式中保護核心記憶體。頁面表並不僅僅有虛擬地址到物理地址的對映;它也包含關於這些地址的元資料,包含哪個 Ring 可能訪問哪個地址的資訊。核心頁面表條目被標記為僅有 Ring 0 可以訪問;程式的條目被標記為任何 Ring 都可以訪問。如果一個處於 Ring 3 中的行程去嘗試訪問標記為 Ring 0 的記憶體,處理器將阻止這個訪問並生成一個意外錯誤資訊。執行在 Ring 3 中的使用者程式不能得到內核以及執行在 Ring 0 記憶體中的任何東西。
至少理論上是這樣的。大量的補丁和更新表明,這個地方已經被突破了。這就是最大的謎團所在。
Ring 間遷移
這就是我們所知道的。每個現代處理器都執行一定數量的推測執行。例如,給一些指令,讓兩個數加起來,然後將結果儲存在記憶體中,在查明記憶體中的標的是否可訪問和可寫入之前,一個處理器可能已經推測性地做了加法。在一些常見案例中,在地址可寫入的地方,處理器節省了一些時間,因為它以並行方式計算出記憶體中的標的是什麼。如果它發現標的位置不可寫入 —— 例如,一個程式嘗試去寫入到一個沒有對映的地址或壓根就不存在的物理位置 —— 然後它將產生一個意外錯誤,而推測執行就白做了。
Intel 處理器,尤其是(雖然不是 AMD 的[4])允許對 Ring 3 程式碼進行推測執行並寫入到 Ring 0 記憶體中的處理器上。處理器並不完全阻止這種寫入,但是推測執行輕微擾亂了處理器狀態,因為,為了查明標的位置是否可寫入,某些資料已經被載入到快取和 TLB 中。這又意味著一些操作可能快幾個週期,或者慢幾個週期,這取決於它們所需要的資料是否仍然在快取中。除此之外,Intel 的處理器還有一些特殊的功能,比如,在 Skylake 處理器上引入的軟體保護擴充套件(SGX)指令,它改變了一點點訪問記憶體的方式。同樣的,處理器仍然是保護 Ring 0 的記憶體不被來自 Ring 3 的程式所訪問,但是同樣的,它的快取和其它內部狀態已經發生了變化,產生了可測量的差異。
我們至今仍然並不知道具體的情況,到底有多少內核的記憶體資訊洩露給了使用者程式,或者資訊洩露的情況有多容易發生。以及有哪些 Intel 處理器會受到影響?也或者並不完全清楚,但是,有跡象表明每個 Intel 晶片都使用了推測執行(是自 1995 年 Pentium Pro 以來的所有主流處理器嗎?),它們都可能會因此而洩露資訊。
這個問題第一次被披露是由來自 奧地利的 Graz Technical University[5] 的研究者。他們披露的資訊表明這個問題已經足夠破壞核心樣式地址空間佈局隨機化(核心 ASLR,或稱 KASLR)。ASLR 是防範 緩衝區上限溢位[6] 漏洞利用的最後一道防線。啟用 ASLR 之後,程式和它們的資料被置於隨機的記憶體地址中,它將使一些安全漏洞利用更加困難。KASLR 將這種隨機化應用到核心中,這樣就使內核的資料(包括頁面表)和程式碼也隨機化分佈。
Graz 的研究者開發了 KAISER[7],一組防範這個問題的 Linux 核心補丁。
如果這個問題正好使 ASLR 的隨機化被破壞了,這或許將成為一個巨大的災難。ASLR 是一個非常強大的保護措施,但是它並不是完美的,這意味著對於駭客來說將是一個很大的障礙,一個無法逾越的障礙。整個行業對此的反應是 —— Windows 和 Linux 都有一個非常重要的變化,秘密開發 —— 這表明不僅是 ASLR 被破壞了,而且從核心洩露出資訊的更普遍的技術被開發出來了。確實是這樣的,研究者已經 在 Twitter 上釋出資訊[8],他們已經可以隨意洩露和讀取核心資料了。另一種可能是,漏洞可能被用於從虛擬機器中“越獄”,並可能會危及 hypervisor。
Windows 和 Linux 選擇的解決方案是非常相似的,將 KAISER 分為兩個區域:核心頁面表的條目不再是由每個行程共享。在 Linux 中,這被稱為核心頁面表隔離(KPTI)。
應用補丁後,記憶體地址仍然被一分為二:這樣使內核的那一半幾乎是空的。當然它並不是非常的空,因為一些核心片斷需要永久對映,不論行程是執行在 Ring 3 還是 Ring 0 中,它都幾乎是空的。這意味著如果惡意使用者程式嘗試去探測核心記憶體以及洩露資訊,它將會失敗 —— 因為那裡幾乎沒有資訊。而真正的核心頁面中只有當核心自身執行的時刻它才能被用到。
這樣做就破壞了最初將地址空間分割的理由。現在,每次切換到使用者程式時,TLB 需要實時去清除與核心頁面表相關的所有條目,這樣就失去了啟用分割帶來的效能提升。
影響的具體大小取決於工作負載。每當一個程式被調入到核心 —— 從磁碟讀入、傳送資料到網路、開啟一個檔案等等 —— 這種呼叫的成本可能會增加一點點,因為它強制 TLB 清除了快取並實時載入核心頁面表。不使用內核的程式可能會觀測到 2 – 3 個百分點的效能影響 —— 這裡仍然有一些開銷,因為核心仍然是偶爾會執行去處理一些事情,比如多工等等。
但是大量呼叫進入到內核的工作負載將觀測到很大的效能損失。在一個基準測試中,一個除了調入到核心之外什麼都不做的程式,觀察到 它的效能下降大約為 50%[9];換句話說就是,打補丁後每次對內核的呼叫的時間要比不打補丁呼叫內核的時間增加一倍。基準測試使用的 Linux 的網路迴環(loopback)也觀測到一個很大的影響,比如,在 Postgres 的基準測試中大約是 17%[10]。真實的資料庫負載使用了實時網路可能觀測到的影響要低一些,因為使用實時網路時,核心呼叫的開銷基本是使用真實網路的開銷。
雖然對 Intel 系統的影響是眾所周知的,但是它們可能並不是唯一受影響的。其它的一些平臺,比如 SPARC 和 IBM 的 S390,是不受這個問題影響的,因為它們的處理器的記憶體管理並不需要分割地址空間和共享核心頁面表;在這些平臺上的作業系統一直就是將它們的核心頁面表從使用者樣式中隔離出來的。但是其它的,比如 ARM,可能就沒有這麼幸運了;適用於 ARM Linux 的類似補丁[11] 正在開發中。
PETER BRIGHT[12] 是 Ars 的一位技術編輯。他涉及微軟、程式設計及軟體開發、Web 技術和瀏覽器、以及安全方面。它居住在紐約的布魯克林。
via: https://arstechnica.com/gadgets/2018/01/whats-behind-the-intel-design-flaw-forcing-numerous-patches/
作者:PETER BRIGHT[14] 譯者:qhwdw 校對:wxy
本文由 LCTT 原創編譯,Linux中國 榮譽推出