Linux中國

Intel 設計缺陷背後的原因是什麼?

(本文發表於 1 月份)最近 Windows 和 Linux 都發送了重大安全更新,為防範這個尚未完全公開的問題,在最壞的情況下,它可能會導致性能下降多達一半。

在過去的幾周,Linux 內核陸續打了幾個補丁。Microsoft 自 11 月份開始也內部測試了 Windows 更新,並且它預計在下周二的例行補丁中將這個改進推送到主流 Windows 構建版中。Microsoft 的 Azure 也在下周的維護窗口中做好了安排,而 Amazon 的 AWS 也安排在周五對相關的設施進行維護。

自從 Linux 第一個補丁 (參見 KPTI:內核頁表隔離的當前的發展) 明確描繪了出現的錯誤以後。雖然 Linux 和 Windows 基於不同的考慮,對此持有不同的看法,但是這兩個操作系統 —— 當然還有其它的 x86 操作系統,比如 FreeBSD 和 macOS — 對系統內存的處理採用了相同的方式,因為對於操作系統在這一部分特性是與底層的處理器高度耦合的。

保持地址跟蹤

在一個系統中的每個內存位元組都是隱性編碼的,這些編碼數字是每個位元組的地址。早期的操作系統使用物理內存地址,但是,物理內存地址由於各種原因,它並不很合適。例如,在地址中經常會有空隙,並且(尤其是 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 的)允許對 Ring 3 代碼進行推測運行並寫入到 Ring 0 內存中的處理器上。處理器並不完全阻止這種寫入,但是推測運行輕微擾亂了處理器狀態,因為,為了查明目標位置是否可寫入,某些數據已經被載入到緩存和 TLB 中。這又意味著一些操作可能快幾個周期,或者慢幾個周期,這取決於它們所需要的數據是否仍然在緩存中。除此之外,Intel 的處理器還有一些特殊的功能,比如,在 Skylake 處理器上引入的軟體保護擴展(SGX)指令,它改變了一點點訪問內存的方式。同樣的,處理器仍然是保護 Ring 0 的內存不被來自 Ring 3 的程序所訪問,但是同樣的,它的緩存和其它內部狀態已經發生了變化,產生了可測量的差異。

我們至今仍然並不知道具體的情況,到底有多少內核的內存信息泄露給了用戶程序,或者信息泄露的情況有多容易發生。以及有哪些 Intel 處理器會受到影響?也或者並不完全清楚,但是,有跡象表明每個 Intel 晶元都使用了推測運行(是自 1995 年 Pentium Pro 以來的所有主流處理器嗎?),它們都可能會因此而泄露信息。

這個問題第一次被披露是由來自 奧地利的 Graz Technical University 的研究者。他們披露的信息表明這個問題已經足夠破壞內核模式地址空間布局隨機化(內核 ASLR,或稱 KASLR)。ASLR 是防範 緩衝區溢出 漏洞利用的最後一道防線。啟用 ASLR 之後,程序和它們的數據被置於隨機的內存地址中,它將使一些安全漏洞利用更加困難。KASLR 將這種隨機化應用到內核中,這樣就使內核的數據(包括頁面表)和代碼也隨機化分布。

Graz 的研究者開發了 KAISER,一組防範這個問題的 Linux 內核補丁。

如果這個問題正好使 ASLR 的隨機化被破壞了,這或許將成為一個巨大的災難。ASLR 是一個非常強大的保護措施,但是它並不是完美的,這意味著對於黑客來說將是一個很大的障礙,一個無法逾越的障礙。整個行業對此的反應是 —— Windows 和 Linux 都有一個非常重要的變化,秘密開發 —— 這表明不僅是 ASLR 被破壞了,而且從內核泄露出信息的更普遍的技術被開發出來了。確實是這樣的,研究者已經 在 Twitter 上發布信息,他們已經可以隨意泄露和讀取內核數據了。另一種可能是,漏洞可能被用於從虛擬機中「越獄」,並可能會危及 hypervisor。

Windows 和 Linux 選擇的解決方案是非常相似的,將 KAISER 分為兩個區域:內核頁面表的條目不再是由每個進程共享。在 Linux 中,這被稱為內核頁面表隔離(KPTI)。

應用補丁後,內存地址仍然被一分為二:這樣使內核的那一半幾乎是空的。當然它並不是非常的空,因為一些內核片斷需要永久映射,不論進程是運行在 Ring 3 還是 Ring 0 中,它都幾乎是空的。這意味著如果惡意用戶程序嘗試去探測內核內存以及泄露信息,它將會失敗 —— 因為那裡幾乎沒有信息。而真正的內核頁面中只有當內核自身運行的時刻它才能被用到。

這樣做就破壞了最初將地址空間分割的理由。現在,每次切換到用戶程序時,TLB 需要實時去清除與內核頁面表相關的所有條目,這樣就失去了啟用分割帶來的性能提升。

影響的具體大小取決於工作負載。每當一個程序被調入到內核 —— 從磁碟讀入、發送數據到網路、打開一個文件等等 —— 這種調用的成本可能會增加一點點,因為它強制 TLB 清除了緩存並實時載入內核頁面表。不使用內核的程序可能會觀測到 2 - 3 個百分點的性能影響 —— 這裡仍然有一些開銷,因為內核仍然是偶爾會運行去處理一些事情,比如多任務等等。

但是大量調用進入到內核的工作負載將觀測到很大的性能損失。在一個基準測試中,一個除了調入到內核之外什麼都不做的程序,觀察到 它的性能下降大約為 50%;換句話說就是,打補丁後每次對內核的調用的時間要比不打補丁調用內核的時間增加一倍。基準測試使用的 Linux 的網路迴環(loopback)也觀測到一個很大的影響,比如,在 Postgres 的基準測試中大約是 17%。真實的資料庫負載使用了實時網路可能觀測到的影響要低一些,因為使用實時網路時,內核調用的開銷基本是使用真實網路的開銷。

雖然對 Intel 系統的影響是眾所周知的,但是它們可能並不是唯一受影響的。其它的一些平台,比如 SPARC 和 IBM 的 S390,是不受這個問題影響的,因為它們的處理器的內存管理並不需要分割地址空間和共享內核頁面表;在這些平台上的操作系統一直就是將它們的內核頁面表從用戶模式中隔離出來的。但是其它的,比如 ARM,可能就沒有這麼幸運了;適用於 ARM Linux 的類似補丁 正在開發中。

PETER BRIGHT 是 Ars 的一位技術編輯。他涉及微軟、編程及軟體開發、Web 技術和瀏覽器、以及安全方面。它居住在紐約的布魯克林。

via: https://arstechnica.com/gadgets/2018/01/whats-behind-the-intel-design-flaw-forcing-numerous-patches/

作者:PETER BRIGHT 譯者:qhwdw 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國