C++理論式執行端通道的開發人員指引C++ Developer Guidance for Speculative Execution Side Channels

本文包含可協助識別及緩和風險的理論式執行端通道中的硬體弱點的開發人員指導方針C++軟體。This article contains guidance for developers to assist with identifying and mitigating speculative execution side channel hardware vulnerabilities in C++ software. 這些弱點可以將機密資訊洩漏跨越信任界限,而且可能影響支援推測性、 次序不對的指示執行的處理器上執行的軟體。These vulnerabilities can disclose sensitive information across trust boundaries and can affect software that runs on processors that support speculative, out-of-order execution of instructions. 弱點的這個類別是 2018 年 1 月日中所述的第一個和其他背景資訊和指引可在Microsoft 資訊安全摘要報告This class of vulnerabilities was first described in January, 2018 and additional background and guidance can be found in Microsoft's security advisory.

這篇文章所提供的指引與相關弱點所代表的類別:The guidance provided by this article is related to the classes of vulnerabilities represented by:

  1. CVE-2017-5753,也稱為 Spectre 變異 1。CVE-2017-5753, also known as Spectre variant 1. 因為發生條件式分支 misprediction 的推測性執行而可能發生的側邊通道有關這個硬體的弱點可能會類別。This hardware vulnerability class is related to side channels that can arise due to speculative execution that occurs as a result of a conditional branch misprediction. Microsoft C++ Visual Studio 2017 (從 15.5.5 版開始) 中的編譯器支援/QspectreCVE 2017-5753 的相關參數,可提供一組有限的可能有弱點的程式碼撰寫模式的編譯時期緩和措施。The Microsoft C++ compiler in Visual Studio 2017 (starting with version 15.5.5) includes support for the /Qspectre switch which provides a compile-time mitigation for a limited set of potentially vulnerable coding patterns related to CVE-2017-5753. /Qspectre參數也會適用於 Visual Studio 2015 Update 3,透過KB 4338871The /Qspectre switch is also available in Visual Studio 2015 Update 3 through KB 4338871. 文件/Qspectre旗標提供有關其效果與使用方式的詳細資訊。The documentation for the /Qspectre flag provides more information on its effects and usage.

  2. CVE-2018年-3639,也稱為推測性存放區略過 (SSB)CVE-2018-3639, also known as Speculative Store Bypass (SSB). 可能發生因為相依的存放區,因為記憶體存取 misprediction 預先載入的推測性執行的側邊通道被與這個硬體的弱點可能會類別。This hardware vulnerability class is related to side channels that can arise due to speculative execution of a load ahead of a dependent store as a result of a memory access misprediction.

推測性執行端通道弱點的可存取簡介,請參閱標題為 「 簡報案例的 Spectre 與 Meltdown所探索到這些問題研究小組的其中一個。An accessible introduction to speculative execution side channel vulnerabilities can be found in the presentation titled The Case of Spectre and Meltdown by one of the research teams that discovered these issues.

理論式執行端通道硬體弱點是什麼?What are Speculative Execution Side Channel hardware vulnerabilities?

新型 Cpu 提供的效能較高程度的理論和出的順序執行指令的使用。Modern CPUs provide higher degrees of performance by making use of speculative and out-of-order execution of instructions. 比方說,此移轉作業往往是預測的目標分支 (條件式和間接) 可讓 CPU 開始推測執行指示預測的分支目標,以避免延遲,直到實際分支目標判斷已解決。For example, this is often accomplished by predicting the target of branches (conditional and indirect) which enables the CPU to begin speculatively executing instructions at the predicted branch target, thus avoiding a stall until the actual branch target is resolved. CPU 稍後發現 misprediction 發生,就不會捨棄所有推測計算的機器狀態。In the event that the CPU later discovers that a misprediction occurred, all of the machine state that was computed speculatively is discarded. 這可確保有錯估的推測沒有在架構上看到效果。This ensures that there are no architecturally visible effects of the mispredicted speculation.

雖然推測性執行不會影響在架構上可見的狀態,它會將剩餘的追蹤保留非架構的狀態,例如 CPU 所使用的各種快取。While speculative execution does not affect the architecturally visible state, it can leave residual traces in non-architectural state, such as the various caches that are used by the CPU. 就這些殘差追蹤的側邊通道弱點可能會導致大量的推測性執行。It is these residual traces of speculative execution that can give rise to side channel vulnerabilities. 若要進一步了解,請考慮下列程式碼片段提供 CVE-2017-5753 (界限檢查略過) 的範例:To better understand this, consider the following code fragment which provides an example of CVE-2017-5753 (Bounds Check Bypass):

// A pointer to a shared memory region of size 1MB (256 * 4096)
unsigned char *shared_buffer;

unsigned char ReadByte(unsigned char *buffer, unsigned int buffer_size, unsigned int untrusted_index) {
    if (untrusted_index < buffer_size) {
        unsigned char value = buffer[untrusted_index];
        return shared_buffer[value * 4096];
    }
}

在此範例中,ReadByte是提供該緩衝區的緩衝區、 緩衝區大小和索引。In this example, ReadByte is supplied a buffer, a buffer size, and an index into that buffer. 索引參數,所指定untrusted_index,提供的無特殊權限的內容,例如非系統管理程序。The index parameter, as specified by untrusted_index, is supplied by a less privileged context, such as a non-administrative process. 如果untrusted_index是小於buffer_size,則該索引處的字元讀取buffer到共用記憶體所參考的區域用來索引和shared_bufferIf untrusted_index is less than buffer_size, then the character at that index is read from buffer and used to index into a shared region of memory referred to by shared_buffer.

從架構的觀點而言,此程式碼序列是完全安全必定untrusted_index一定會小於buffer_sizeFrom an architectural perspective, this code sequence is perfectly safe as it is guaranteed that untrusted_index will always be less than buffer_size. 不過,有風險的理論式執行時,就可以在 CPU 會錯估的條件式分支和執行在 if 的主體陳述式,即使untrusted_index大於或等於buffer_sizeHowever, in the presence of speculative execution, it is possible that the CPU will mispredict the conditional branch and execute the body of the if statement even when untrusted_index is greater than or equal to buffer_size. 如此一來,CPU 可以推測讀取一個位元組,它的界限buffer(這可能是密碼),接著可以使用該位元組的值來計算透過後續載入位址shared_bufferAs a consequence of this, the CPU may speculatively read a byte from beyond the bounds of buffer (which could be a secret) and could then use that byte value to compute the address of a subsequent load through shared_buffer.

雖然 CPU 最終將會偵測此 misprediction,剩餘的副作用可能會處於顯示資訊超出範圍,從已讀取的位元組值的 CPU 快取bufferWhile the CPU will eventually detect this misprediction, residual side effects may be left in the CPU cache that reveal information about the byte value that was read out of bounds from buffer. 這些副作用可以偵測到較低的系統上執行的方式快速探查的特殊權限的內容中的每個快取行shared_buffer存取。These side effects can be detected by a less privileged context running on the system by probing how quickly each cache line in shared_buffer is accessed. 若要這麼做可採取的步驟如下:The steps that can be taken to accomplish this are:

  1. 叫用ReadByte多次untrusted_index正在小於buffer_sizeInvoke ReadByte multiple times with untrusted_index being less than buffer_size. 發動攻擊的內容可能會導致叫用的犧牲者內容ReadByte(例如,透過 RPC),例如分支預測器是訓練是不被視為untrusted_index是小於buffer_sizeThe attacking context can cause the victim context to invoke ReadByte (e.g. via RPC) such that the branch predictor is trained to be not-taken as untrusted_index is less than buffer_size.

  2. 排清中的所有快取行shared_bufferFlush all cache lines in shared_buffer. 發動攻擊的內容必須排清所有記憶體所參考的共用區域中的快取行shared_bufferThe attacking context must flush all of the cache lines in the shared region of memory referred to by shared_buffer. 因為共用記憶體區域,這非常簡單,例如使用內建函式即可_mm_clflushSince the memory region is shared, this is straightforward and can be accomplished using intrinsics such as _mm_clflush.

  3. 叫用ReadByte具有untrusted_index大於buffer_sizeInvoke ReadByte with untrusted_index being greater than buffer_size. 發動攻擊的內容會導致叫用的犧牲者內容ReadByte使其不正確預測將不會採取的分支。The attacking context causes the victim context to invoke ReadByte such that it incorrectly predicts that the branch will not be taken. 此處理器以推測執行在 if 的主體含有區塊的原因untrusted_index大於buffer_size,因此導致超出範圍的讀取權限的bufferThis causes the processor to speculatively execute the body of the if block with untrusted_index being greater than buffer_size, thus leading to an out-of-bounds read of buffer. 因此,shared_buffer使用讀取超出範圍,因此會造成 CPU 所應載入的個別快取行的可能密碼值會編製索引。Consequently, shared_buffer is indexed using a potentially secret value that was read out-of-bounds, thus causing the respective cache line to be loaded by the CPU.

  4. 讀取中的每個快取行shared_buffer若要查看最快的速度存取哪一個Read each cache line in shared_buffer to see which is accessed most quickly. 發動攻擊的內容可以讀取中的每個快取行shared_buffer和偵測的載入速度比其他的快取行。The attacking context can read each cache line in shared_buffer and detect the cache line that loads significantly faster than the others. 這是可能已上線步驟 3 所快取行。This is the cache line that is likely to have been brought in by step 3. 因為在此範例中的位元組值和快取行之間沒有 1:1 關聯性,這可讓攻擊者能夠推斷實際的值超出範圍讀取的位元組。Since there is a 1:1 relationship between byte value and cache line in this example, this allows the attacker to infer the actual value of the byte that was read out-of-bounds.

上述步驟將提供範例,使用稱為排清 + 重新載入,搭配 CVE 2017-5753 的執行個體的全部功能的技術。The above steps provide an example of using a technique known as FLUSH+RELOAD in conjunction with exploiting an instance of CVE-2017-5753.

哪些軟體情況可能會受到影響?What software scenarios can be impacted?

使用類似的程序的安全軟體開發安全性開發生命週期(SDL) 通常需要開發人員識別存在於其應用程式的信任界限。Developing secure software using a process like the Security Development Lifecycle (SDL) typically requires developers to identify the trust boundaries that exist in their application. 信任界限存在於其中的應用程式可能會與低信任內容,例如在系統上的另一個處理序或在核心模式裝置驅動程式的情況下非系統管理使用者模式程序所提供的資料互動的地方。A trust boundary exists in places where an application may interact with data provided by a less-trusted context, such as another process on the system or a non-administrative user mode process in the case of a kernel-mode device driver. 涉及推測性執行端通道弱點的新類別是與許多現有的軟體安全性模型,找出程式碼,並且在裝置上的資料中的信任界限。The new class of vulnerabilities involving speculative execution side channels is relevant to many of the trust boundaries in existing software security models that isolate code and data on a device.

下表提供開發人員可能要考慮發生這些弱點的軟體安全性模型的摘要:The following table provides a summary of the software security models where developers may need to be concerned about these vulnerabilities occurring:

信任界限Trust boundary 描述Description
虛擬機器的界限Virtual machine boundary 隔離在個別的虛擬機器,接收來自另一部虛擬機器不受信任的資料中的工作負載的應用程式可能會有風險。Applications that isolate workloads in separate virtual machines that receive untrusted data from another virtual machine may be at risk.
核心界限Kernel boundary 接收來自非管理使用者模式程序不受信任的資料的核心模式裝置驅動程式可能會有風險。A kernel-mode device driver that receives untrusted data from a non-administrative user mode process may be at risk.
處理序界限Process boundary 接收來自本機系統上執行的另一個處理序的未受信任的資料,例如透過遠端程序呼叫 (RPC)、 共用的記憶體或其他處理序間通訊 (IPC) 機制可能會有風險的應用程式。An application that receives untrusted data from another process running on the local system, such as through a Remote Procedure Call (RPC), shared memory, or other Inter-Process Communication (IPC) mechanisms may be at risk.
Enclave 界限Enclave boundary 接收從 enclave 之外的不受信任的資料的安全飛地 (例如 Intel SGX) 內執行的應用程式可能會有風險。An application that executes within a secure enclave (such as Intel SGX) that receives untrusted data from outside of the enclave may be at risk.
語言界限Language boundary 解譯的應用程式或 Just-In-Time (JIT) 編譯並執行不受信任的程式碼撰寫的較高層級的語言可能會有風險。An application that interprets or Just-In-Time (JIT) compiles and executes untrusted code written in a higher-level language may be at risk.

有受攻擊面公開至上述任一種信任界限應該檢閱受攻擊面,以識別和減緩的推測性執行端通道弱點的可能執行個體上的程式碼的應用程式。Applications that have attack surface exposed to any of the above trust boundaries should review code on the attack surface to identify and mitigate possible instances of speculative execution side channel vulnerabilities. 請注意不具有已公開至遠端的受攻擊面,例如遠端網路通訊協定,信任界限示範可處於推測性執行端通道弱點的風險之中。It should be noted that trust boundaries exposed to remote attack surfaces, such as remote network protocols, have not been demonstrated to be at risk to speculative execution side channel vulnerabilities.

可能有弱點的程式碼撰寫模式Potentially vulnerable coding patterns

由於多個程式碼撰寫模式,推測性執行端通道弱點可能會發生。Speculative execution side channel vulnerabilities can arise as a consequence of multiple coding patterns. 本章節描述可能有弱點的程式碼撰寫模式,並提供範例,針對每個,但應該識別這些佈景主題的變化可能存在。This section describes potentially vulnerable coding patterns and provides examples for each, but it should be recognized that variations on these themes may exist. 因此,建議開發人員將需要這些模式做為範例,而不是所有可能有弱點的程式碼撰寫模式的完整清單。As such, developers are advised to take these patterns as examples and not as an exhaustive list of all potentially vulnerable coding patterns. 現在可存在於軟體的記憶體安全弱點的相同的類別可能也會沿著推測性存在,且超出範圍的順序執行,包括但不是限於緩衝區溢位,路徑陣列的存取,未初始化的記憶體使用型別產生混淆,依此類推。The same classes of memory safety vulnerabilities that can exist in software today may also exist along speculative and out-of-order paths of execution, including but not limited to buffer overruns, out-of-bounds array accesses, uninitialized memory use, type confusion, and so on. 攻擊者可以利用記憶體安全性弱點,沿著架構路徑中使用的相同原始物件可能也適用於推測性的路徑。The same primitives that attackers can use to exploit memory safety vulnerabilities along architectural paths may also apply to speculative paths.

一般情況下,可以控制或受到信任度較低的內容中的資料上操作的條件運算式時,可能會發生推測性執行端通道相關條件式分支 misprediction。In general, speculative execution side channels related to conditional branch misprediction can arise when a conditional expression operates on data that can be controlled or influenced by a less-trusted context. 比方說,這可能包括用於條件運算式ifforwhileswitch,或三元陳述式。For example, this can include conditional expressions used in if, for, while, switch, or ternary statements. 針對每個這些陳述式,編譯器可能會產生 CPU 可能會預測在執行階段的分支目標的條件式分支。For each of these statements, the compiler may generate a conditional branch that the CPU may then predict the branch target for at runtime.

對於每個範例中,插入註解與片語"投機屏障 」 開發人員可能會造成屏障,以緩和措施。For each example, a comment with the phrase "SPECULATION BARRIER" is inserted where a developer could introduce a barrier as a mitigation. 這是一節中詳細討論上緩和措施。This is discussed in more detail in the section on mitigations.

超出範圍的理論式載入Speculative out-of-bounds load

這個類別的程式碼模式,牽涉到通往推測超出範圍的條件式分支 misprediction 記憶體存取。This category of coding patterns involves a conditional branch misprediction that leads to a speculative out-of-bounds memory access.

超出範圍的陣列載入饋送負載Array out-of-bounds load feeding a load

此程式碼撰寫模式是 CVE-2017-5753 (界限檢查略過) 原本描述易受攻擊程式碼撰寫模式。This coding pattern is the originally described vulnerable coding pattern for CVE-2017-5753 (Bounds Check Bypass). 這篇文章的背景一節說明此模式在詳細資料。The background section of this article explains this pattern in detail.

// A pointer to a shared memory region of size 1MB (256 * 4096)
unsigned char *shared_buffer;

unsigned char ReadByte(unsigned char *buffer, unsigned int buffer_size, unsigned int untrusted_index) {
    if (untrusted_index < buffer_size) {
        // SPECULATION BARRIER
        unsigned char value = buffer[untrusted_index];
        return shared_buffer[value * 4096];
    }
}

同樣地,陣列超出範圍時,負載可能會發生在超過其終止迴圈搭配條件因為 misprediction。Similarly, an array out-of-bounds load may occur in conjunction with a loop that exceeds its terminating condition due to a misprediction. 在此範例中,條件式分支相關聯x < buffer_size運算式可能會錯估,並推測執行主體for迴圈時x大於或等於buffer_size,因此產生的推測性超出範圍載入。In this example, the conditional branch associated with the x < buffer_size expression may mispredict and speculatively execute the body of the for loop when x is greater than or equal to buffer_size, thus resulting in a speculative out-of-bounds load.

// A pointer to a shared memory region of size 1MB (256 * 4096)
unsigned char *shared_buffer;

unsigned char ReadBytes(unsigned char *buffer, unsigned int buffer_size) {
    for (unsigned int x = 0; x < buffer_size; x++) {
        // SPECULATION BARRIER
        unsigned char value = buffer[x];
        return shared_buffer[value * 4096];
    }
}

超出範圍的陣列載入饋送間接的分支Array out-of-bounds load feeding an indirect branch

此程式碼撰寫模式牽涉到其中的條件式分支 misprediction 可能會導致案例然後因而間接分支至的目標位址的函式指標的陣列存取超出範圍讀取超出範圍。This coding pattern involves the case where a conditional branch misprediction can lead to an out-of-bounds access to an array of function pointers which then leads to an indirect branch to the target address that was read out-of-bounds. 下列程式碼片段提供範例,示範這項功能。The following snippet provides an example that demonstrates this.

在此範例中,不受信任的訊息識別項會提供透過 DispatchMessageuntrusted_message_id參數。In this example, an untrusted message identifier is provided to DispatchMessage through the untrusted_message_id parameter. 如果untrusted_message_id是小於MAX_MESSAGE_ID,則它會使用對函式指標的陣列索引,以及分支到對應的分支目標。If untrusted_message_id is less than MAX_MESSAGE_ID, then it is used to index into an array of function pointers and branch to the corresponding branch target. 此程式碼是在架構上,安全,但如果 CPU 錯估數的條件式分支,可能會造成DispatchTable檢索untrusted_message_id當其值為大於或等於MAX_MESSAGE_ID,而因此導致超出範圍的存取。This code is safe architecturally, but if the CPU mispredicts the conditional branch, it could result in DispatchTable being indexed by untrusted_message_id when its value is greater than or equal to MAX_MESSAGE_ID, thus leading to an out-of-bounds access. 這可能會導致從衍生 překračuje meze 陣列,其中可能會導致資訊洩漏,根據推測執行的程式碼分支目標位址的推測性執行。This could result in speculative execution from a branch target address that is derived beyond the bounds of the array which could lead to information disclosure depending on the code that is executed speculatively.

#define MAX_MESSAGE_ID 16

typedef void (*MESSAGE_ROUTINE)(unsigned char *buffer, unsigned int buffer_size);

const MESSAGE_ROUTINE DispatchTable[MAX_MESSAGE_ID];

void DispatchMessage(unsigned int untrusted_message_id, unsigned char *buffer, unsigned int buffer_size) {
    if (untrusted_message_id < MAX_MESSAGE_ID) {
        // SPECULATION BARRIER
        DispatchTable[untrusted_message_id](buffer, buffer_size);
    }
}

隨著與在超出範圍的陣列的案例負載提供另一個的負載,這種狀況可能也會發生與超出其結束的條件,因為 misprediction 迴圈搭配使用。As with the case of an array out-of-bounds load feeding another load, this condition may also arise in conjunction with a loop that exceeds its terminating condition due to a misprediction.

超出範圍的陣列儲存饋送間接的分支Array out-of-bounds store feeding an indirect branch

雖然先前範例所示範的方式推測超出範圍負載可能會影響間接分支目標,也很可能超出範圍儲存修改的間接分支目標,例如函式指標或寄件地址。While the previous example showed how a speculative out-of-bounds load can influence an indirect branch target, it is also possible for an out-of-bounds store to modify an indirect branch target, such as a function pointer or a return address. 這可能會導致推測性執行來自攻擊者指定的位址。This can potentially lead to speculative execution from an attacker-specified address.

在此範例中,不受信任的索引會傳遞untrusted_index參數。In this example, an untrusted index is passed through the untrusted_index parameter. 如果untrusted_index的項目計數少於pointers陣列 (256 個項目),則會在將提供的指標值ptr寫入pointers陣列。If untrusted_index is less than the element count of the pointers array (256 elements), then the provided pointer value in ptr is written to the pointers array. 此程式碼是在架構上,安全,但如果 CPU 錯估數的條件式分支,可能會造成ptr推測寫入 překračuje meze 堆疊配置pointers陣列。This code is safe architecturally, but if the CPU mispredicts the conditional branch, it could result in ptr being speculatively written beyond the bounds of the stack-allocated pointers array. 這可能會導致推測性損毀的回覆地址WriteSlotThis could lead to speculative corruption of the return address for WriteSlot. 如果攻擊者可以控制的值ptr,它們可能會導致從任意的推測性執行處理時WriteSlot推測性的路徑傳回。If an attacker can control the value of ptr, they may be able to cause speculative execution from an arbitrary address when WriteSlot returns along the speculative path.

unsigned char WriteSlot(unsigned int untrusted_index, void *ptr) {
    void *pointers[256];
    if (untrusted_index < 256) {
        // SPECULATION BARRIER
        pointers[untrusted_index] = ptr;
    }
}

同樣地,如果函式指標名為區域變數func配置在堆疊上,則它可能可以推測修改地址,func指的是當條件式分支 misprediction,就會發生。Similarly, if a function pointer local variable named func were allocated on the stack, then it may be possible to speculatively modify the address that func refers to when the conditional branch misprediction occurs. 透過呼叫的函式指標時,這可能導致從任意地址的推測性執行。This could result in speculative execution from an arbitrary address when the function pointer is called through.

unsigned char WriteSlot(unsigned int untrusted_index, void *ptr) {
    void *pointers[256];
    void (*func)() = &callback;
    if (untrusted_index < 256) {
        // SPECULATION BARRIER
        pointers[untrusted_index] = ptr;
    }
    func();
}

請注意這兩個範例涉及推測性修改的堆疊配置間接分支指標。It should be noted that both of these examples involve speculative modification of stack-allocated indirect branch pointers. 可以推測性修改全域變數、 堆積配置的記憶體,和甚至是唯讀記憶體一些 Cpu 上也可能會發生。It is possible that speculative modification could also occur for global variables, heap-allocated memory, and even read-only memory on some CPUs. 堆疊配置的記憶體,MicrosoftC++編譯器已經採取步驟以讓它更難推測修改堆疊配置間接分支目標,例如透過重新排列本機變數,使得緩衝區會放在相鄰的安全性cookie 的一部分/GS編譯器的安全性功能。For stack-allocated memory, the Microsoft C++ compiler already takes steps to make it more difficult to speculatively modify stack-allocated indirect branch targets, such as by reordering local variables such that buffers are placed adjacent to a security cookie as part of the /GS compiler security feature.

理論式的型別混淆Speculative type confusion

此類別會處理程式碼可能會導致大量推測性的型別混淆的模式。This category deals with coding patterns that can give rise to a speculative type confusion. 發生這種情況的風險的理論式執行期間使用非架構的路徑是正確類型來存取記憶體時。This occurs when memory is accessed using an incorrect type along a non-architectural path during speculative execution. 條件式分支 misprediction 和推測性存放區略過可能會導致推測性的類型產生混淆。Both conditional branch misprediction and speculative store bypass can potentially lead to a speculative type confusion.

推測性存放區的許可,這可能會發生在案例中,編譯器會重複使用多個類型的變數的堆疊位置。For speculative store bypass, this could occur in scenarios where a compiler reuses a stack location for variables of multiple types. 這是因為的架構類型的變數存放區A可能會略過,因此可允許的型別負載A推測執行之前已指派變數。This is because the architectural store of a variable of type A may be bypassed, thus allowing the load of type A to speculatively execute before the variable is assigned. 如果先前儲存的變數是不同的類型,這就可以建立理論式類型造成混淆的條件。If the previously stored variable is of a different type, then this can create the conditions for a speculative type confusion.

條件式分支 misprediction,如下列程式碼片段會用來描述不同的條件,讓理論式的型別混淆面對。For conditional branch misprediction, the following code snippet will be used to describe different conditions that speculative type confusion can give rise to.

enum TypeName {
    Type1,
    Type2
};

class CBaseType {
public:
    CBaseType(TypeName type) : type(type) {}
    TypeName type;
};

class CType1 : public CBaseType {
public:
    CType1() : CBaseType(Type1) {}
    char field1[256];
    unsigned char field2;
};

class CType2 : public CBaseType {
public:
    CType2() : CBaseType(Type2) {}
    void (*dispatch_routine)();
    unsigned char field2;
};

// A pointer to a shared memory region of size 1MB (256 * 4096)
unsigned char *shared_buffer;

unsigned char ProcessType(CBaseType *obj)
{
    if (obj->type == Type1) {
        // SPECULATION BARRIER
        CType1 *obj1 = static_cast<CType1 *>(obj);

        unsigned char value = obj1->field2;

        return shared_buffer[value * 4096];
    }
    else if (obj->type == Type2) {
        // SPECULATION BARRIER
        CType2 *obj2 = static_cast<CType2 *>(obj);

        obj2->dispatch_routine();

        return obj2->field2;
    }
}

理論式的型別混淆導致超出範圍載入Speculative type confusion leading to an out-of-bounds load

此程式碼撰寫模式牽涉到其中的推測型別混淆可能會導致案例超出範圍或載入的值,摘要後續載入位址的類型產生混淆的欄位存取。This coding pattern involves the case where a speculative type confusion can result in an out-of-bounds or type-confused field access where the loaded value feeds a subsequent load address. 這是類似於陣列超出範圍的程式碼撰寫模式,但透過如上所示,撰寫程式碼順序的替代方法,它會顯示。This is similar to the array out-of-bounds coding pattern but it is manifested through an alternative coding sequence as shown above. 在此範例中,發動攻擊的內容可能會導致執行犧牲者內容ProcessType類型的物件使用多次CType1(type欄位等於Type1)。In this example, an attacking context could cause the victim context to execute ProcessType multiple times with an object of type CType1 (type field is equal to Type1). 這會造成影響訓練第一個條件式分支if預測不採用的陳述式。This will have the effect of training the conditional branch for the first if statement to predict not taken. 發動攻擊的內容可能會犧牲者內容執行然後造成ProcessType型別的物件與CType2The attacking context can then cause the victim context to execute ProcessType with an object of type CType2. 這會導致推測性的型別混淆如果條件式分支的前if錯估數陳述式,並執行的主體if陳述式,因此轉型別的物件CType2CType1This can result in a speculative type confusion if the conditional branch for the first if statement mispredicts and executes the body of the if statement, thus casting an object of type CType2 to CType1. 由於CType2小於CType1,記憶體存取CType1::field2會導致推測超出範圍載入的資料可能是祕密。Since CType2 is smaller than CType1, the memory access to CType1::field2 will result in a speculative out-of-bounds load of data that may be secret. 此值之後會用來從載入shared_buffer可以建立可預見的副作用,就如同陣列範例與先前說明的超出範圍。This value is then used in a load from shared_buffer which can create observable side effects, as with the array out-of-bounds example described previously.

理論式的型別混淆導致間接的分支Speculative type confusion leading to an indirect branch

此程式碼撰寫模式牽涉到其中的推測型別混淆可能會導致不安全的間接分支理論式執行期間的情況。This coding pattern involves the case where a speculative type confusion can result in an unsafe indirect branch during speculative execution. 在此範例中,發動攻擊的內容可能會導致執行犧牲者內容ProcessType類型的物件使用多次CType2(type欄位等於Type2)。In this example, an attacking context could cause the victim context to execute ProcessType multiple times with an object of type CType2 (type field is equal to Type2). 這會造成影響訓練第一個條件式分支if要採取的陳述式和else if不採取的陳述式。This will have the effect of training the conditional branch for the first if statement to be taken and the else if statement to be not taken. 發動攻擊的內容可能會犧牲者內容執行然後造成ProcessType型別的物件與CType1The attacking context can then cause the victim context to execute ProcessType with an object of type CType1. 這會導致推測性的型別混淆如果條件式分支的第一個if陳述式來預測採取和else if陳述式來預測不採用,因此執行主體else if和轉型類型的物件CType1CType2This can result in a speculative type confusion if the conditional branch for the first if statement predicts taken and the else if statement predicts not taken, thus executing the body of the else if and casting an object of type CType1 to CType2. 由於CType2::dispatch_routine欄位與重疊char陣列CType1::field1,這可能導致推測性的間接分支到非預期的分支目標。Since the CType2::dispatch_routine field overlaps with the char array CType1::field1, this could result in a speculative indirect branch to an unintended branch target. 如果發動攻擊的內容可以控制中的位元組值CType1::field1陣列,可能會讓它們能夠控制分支目標位址。If the attacking context can control the byte values in the CType1::field1 array, they may be able to control the branch target address.

理論式未初始化的使用Speculative uninitialized use

這個類別的程式碼模式,牽涉到推測性執行可能會存取未初始化的記憶體並使用它來摘要後續載入或間接的分支。This category of coding patterns involves scenarios where speculative execution may access uninitialized memory and use it to feed a subsequent load or indirect branch. 這些是可利用來攻擊的程式碼撰寫模式,讓攻擊者必須能夠控制或有意義地影響它們的使用,而沒有它正在使用中的內容所要初始化的記憶體內容。For these coding patterns to be exploitable, an attacker needs to be able to control or meaningfully influence the contents of the memory that is used without being initialized by the context that it is being used in.

理論式未初始化的使用,導致超出範圍載入Speculative uninitialized use leading to an out-of-bounds load

理論式未初始化的使用可能會導致超出範圍使用的攻擊者所控制的值來載入。A speculative uninitialized use can potentially lead to an out-of-bounds load using an attacker controlled value. 在範例中的值以下index指派trusted_index架構的所有路徑上並trusted_index皆必須小於或等於buffer_sizeIn the example below, the value of index is assigned trusted_index on all architectural paths and trusted_index is assumed to be less than or equal to buffer_size. 不過,根據編譯器所產生的程式碼,可能會推測性存放區略過可能會發生,可從負載buffer[index]和預先指派,以執行相依運算式indexHowever, depending on the code produced by the compiler, it is possible that a speculative store bypass may occur that allows the load from buffer[index] and dependent expressions to execute ahead of the assignment to index. 如果發生這種情況,未初始化的值,如index用作位移buffer這可讓攻擊者讀取超出範圍的敏感資訊,並透過側邊通道透過相依的負載傳達這shared_buffer.If this occurs, an uninitialized value for index will be used as the offset into buffer which could enable an attacker to read sensitive information out-of-bounds and convey this through a side channel through the dependent load of shared_buffer.

// A pointer to a shared memory region of size 1MB (256 * 4096)
unsigned char *shared_buffer;

void InitializeIndex(unsigned int trusted_index, unsigned int *index) {
    *index = trusted_index;
}

unsigned char ReadByte(unsigned char *buffer, unsigned int buffer_size, unsigned int trusted_index) {
    unsigned int index;

    InitializeIndex(trusted_index, &index); // not inlined

    // SPECULATION BARRIER
    unsigned char value = buffer[index];
    return shared_buffer[value * 4096];
}

理論式未初始化的使用,導致間接的分支Speculative uninitialized use leading to an indirect branch

理論式未初始化的使用可能會導致間接的分支,攻擊者控制分支目標。A speculative uninitialized use can potentially lead to an indirect branch where the branch target is controlled by an attacker. 在下列範例中,routine指派給DefaultMessageRoutine1或是DefaultMessageRoutine的值而定modeIn the example below, routine is assigned to either DefaultMessageRoutine1 or DefaultMessageRoutine depending on the value of mode. 在架構上的路徑,這會導致routine一定間接的分支之前初始化。On the architectural path, this will result in routine always being initialized ahead of the indirect branch. 不過,編譯器所產生的程式碼,根據推測的存放區略過可能發生,可讓透過間接的分支routine推測執行預先指派給routineHowever, depending on the code produced by the compiler, a speculative store bypass may occur that allows the indirect branch through routine to be speculatively executed ahead of the assignment to routine. 如果發生這種情況,攻擊者可以推測執行任意的地址,假設攻擊者可以影響或控制的未初始化的值routineIf this occurs, an attacker may be able to speculatively execute from an arbitrary address, assuming the attacker can influence or control the uninitialized value of routine.

#define MAX_MESSAGE_ID 16

typedef void (*MESSAGE_ROUTINE)(unsigned char *buffer, unsigned int buffer_size);

const MESSAGE_ROUTINE DispatchTable[MAX_MESSAGE_ID];
extern unsigned int mode;

void InitializeRoutine(MESSAGE_ROUTINE *routine) {
    if (mode == 1) {
        *routine = &DefaultMessageRoutine1;
    }
    else {
        *routine = &DefaultMessageRoutine;
    }
}

void DispatchMessage(unsigned int untrusted_message_id, unsigned char *buffer, unsigned int buffer_size) {
    MESSAGE_ROUTINE routine;

    InitializeRoutine(&routine); // not inlined

    // SPECULATION BARRIER
    routine(buffer, buffer_size);
}

緩和選項Mitigation options

對原始程式碼進行變更,就可以減輕風險的理論式執行端通道弱點。Speculative execution side channel vulnerabilities can be mitigated by making changes to source code. 這些變更可能是這類緩和的弱點,特定執行個體,藉由新增投機屏障,或藉由將機密資訊存取推測性應用程式的設計變更執行。These changes can involve mitigating specific instances of a vulnerability, such as by adding a speculation barrier, or by making changes to the design of an application to make sensitive information inaccessible to speculative execution.

透過手動檢測投機屏障Speculation barrier via manual instrumentation

A投機屏障用手動方式插入由開發人員若要避免繼續進行非架構路徑的推測性執行。A speculation barrier can be manually inserted by a developer to prevent speculative execution from proceeding along a non-architectural path. 比方說,開發人員可以插入投機屏障危險的程式碼撰寫模式之前的條件式區塊,在區塊開頭 (之後的條件式分支) 內或之前的考量是第一次載入。For example, a developer can insert a speculation barrier before a dangerous coding pattern in the body of a conditional block, either at the beginning of the block (after the conditional branch) or before the first load that is of concern. 這將導致無法執行非架構路徑的危險的程式碼,透過序列化執行條件式分支 misprediction。This will prevent a conditional branch misprediction from executing the dangerous code on a non-architectural path by serializing execution. 硬體架構的推測屏障順序不同下, 表所述:The speculation barrier sequence differs by hardware architecture as described by the following table:

架構Architecture 投機屏障 CVE 2017-5753 的內建函式Speculation barrier intrinsic for CVE-2017-5753 內建的 CVE-2018年-3639 投機屏障Speculation barrier intrinsic for CVE-2018-3639
x86/x64x86/x64 _mm_lfence()_mm_lfence() _mm_lfence()_mm_lfence()
ARMARM 目前無法使用not currently available __dsb(0)__dsb(0)
ARM64ARM64 目前無法使用not currently available __dsb(0)__dsb(0)

例如,下列程式碼模式,可以減輕使用_mm_lfence如果內建函式,如下所示。For example, the following code pattern can be mitigated by using the _mm_lfence intrinsic as shown below.

// A pointer to a shared memory region of size 1MB (256 * 4096)
unsigned char *shared_buffer;

unsigned char ReadByte(unsigned char *buffer, unsigned int buffer_size, unsigned int untrusted_index) {
    if (untrusted_index < buffer_size) {
        _mm_lfence();
        unsigned char value = buffer[untrusted_index];
        return shared_buffer[value * 4096];
    }
}

透過編譯器階段檢測投機屏障Speculation barrier via compiler-time instrumentation

Microsoft C++ Visual Studio 2017 (從 15.5.5 版開始) 中的編譯器支援/Qspectre參數,可自動插入投機屏障的一組有限的可能有弱點的程式碼撰寫模式與CVE-2017-5753。The Microsoft C++ compiler in Visual Studio 2017 (starting with version 15.5.5) includes support for the /Qspectre switch which automatically inserts a speculation barrier for a limited set of potentially vulnerable coding patterns related to CVE-2017-5753. 文件/Qspectre旗標提供有關其效果與使用方式的詳細資訊。The documentation for the /Qspectre flag provides more information on its effects and usage. 請務必請注意,這個旗標未涵蓋所有可能有弱點的程式碼撰寫模式,因此開發人員不應依賴它完整緩和這種弱點類別。It is important to note that this flag does not cover all of the potentially vulnerable coding patterns and as such developers should not rely on it as a comprehensive mitigation for this class of vulnerabilities.

遮罩陣列索引Masking array indices

在位置推測超出範圍載入的情況下可能會發生,陣列索引可以強受到架構和非架構路徑上加入邏輯,以明確繫結的陣列索引。In cases where a speculative out-of-bounds load may occur, the array index can be strongly bounded on both the architectural and non-architectural path by adding logic to explicitly bound the array index. 例如,如果陣列可以配置為二乘冪對齊的大小,然後簡單遮罩可以導入。For example, if an array can be allocated to a size that is aligned to a power of two, then a simple mask can be introduced. 下列範例,假設所示buffer_size靠二乘冪。This is illustrated in the sample below where it is assumed that buffer_size is aligned to a power of two. 這可確保untrusted_index是一定小於buffer_size,即使發生條件式分支 misprediction 並untrusted_index傳遞的值大於或等於buffer_sizeThis ensures that untrusted_index is always less than buffer_size, even if a conditional branch misprediction occurs and untrusted_index was passed in with a value greater than or equal to buffer_size.

請注意,在此執行之索引遮罩可能遭受推測性存放區根據編譯器所產生的程式碼的略過。It should be noted that the index masking performed here could be subject to speculative store bypass depending on the code that is generated by the compiler.

// A pointer to a shared memory region of size 1MB (256 * 4096)
unsigned char *shared_buffer;

unsigned char ReadByte(unsigned char *buffer, unsigned int buffer_size, unsigned int untrusted_index) {
    if (untrusted_index < buffer_size) {
        untrusted_index &= (buffer_size - 1);
        unsigned char value = buffer[untrusted_index];
        return shared_buffer[value * 4096];
    }
}

從記憶體移除機密資訊Removing sensitive information from memory

可用來減少推測性執行端通道弱點的另一個方法是移除記憶體中的機密資訊。Another technique that can be used to mitigate speculative execution side channel vulnerabilities is to remove sensitive information from memory. 軟體開發人員可以尋找重構應用程式,使機密資訊不能存取理論式執行期間的機會。Software developers can look for opportunities to refactor their application such that sensitive information is not accessible during speculative execution. 這可透過重構來隔離到個別的程序的機密資訊的應用程式的設計。This can be accomplished by refactoring the design of an application to isolate sensitive information into separate processes. 例如,web 瀏覽器應用程式可以嘗試找出與每個 web 來源關聯到不同的處理序,因此導致其中一個處理序無法存取透過推測性執行的跨原始來源資料的資料。For example, a web browser application can attempt to isolate the data associated with each web origin into separate processes, thus preventing one process from being able to access cross-origin data through speculative execution.

另請參閱See also

減少推測性執行旁路攻擊漏洞的指導方針Guidance to mitigate speculative execution side-channel vulnerabilities
減少推測性執行側邊通道攻擊硬體漏洞Mitigating speculative execution side channel hardware vulnerabilities