推測執行

本文件解釋了推測執行的潛在影響,以及如何使用通用API以可移植的方式緩解不良影響。


為了提高效能並最大限度地減少平均延遲,許多現代CPU採用推測執行技術,例如分支預測,執行可能會在稍後階段丟棄的工作。

通常,推測執行無法從架構狀態(例如暫存器的內容)中觀察到。但是,在某些情況下,可以觀察到它對微架構狀態的影響,例如快取中是否存在資料。這種狀態可能形成側通道,可以透過觀察來提取秘密資訊。

例如,在存在分支預測的情況下,程式碼中進行推測執行時可能會忽略邊界檢查。 考慮以下程式碼

int load_array(int *array, unsigned int index)
{
        if (index >= MAX_ARRAY_ELEMS)
                return 0;
        else
                return array[index];
}

在 arm64 上,可能被編譯成如下的彙編序列

      CMP     <index>, #MAX_ARRAY_ELEMS
      B.LT    less
      MOV     <returnval>, #0
      RET
less:
      LDR     <returnval>, [<array>, <index>]
      RET

CPU 可能錯誤地預測條件分支,並推測性地載入 array[index],即使 index >= MAX_ARRAY_ELEMS。 此值隨後將被丟棄,但推測性載入可能會影響微架構狀態,這些狀態可以隨後測量。

涉及多個依賴記憶體訪問的更復雜的序列可能導致敏感資訊洩露。 考慮以下程式碼,在之前的示例的基礎上構建

int load_dependent_arrays(int *arr1, int *arr2, int index)
{
        int val1, val2,

        val1 = load_array(arr1, index);
        val2 = load_array(arr2, val1);

        return val2;
}

在推測執行下,對 load_array() 的第一次呼叫可能會返回越界地址的值,而第二次呼叫將影響依賴於該值的微架構狀態。 這可能提供任意讀取原語。

緩解推測執行側通道

核心提供了一個通用API,以確保即使在推測執行下也能遵守邊界檢查。 受基於推測的側通道影響的架構應實現這些原語。

<linux/nospec.h> 中的 array_index_nospec() 輔助函式可用於防止資訊透過側通道洩露。

呼叫 array_index_nospec(index, size) 將返回一個經過清理的索引值,即使在 CPU 推測條件下,該值也被限制在 [0, size) 範圍內。

這可以用來保護之前的 load_array() 示例

int load_array(int *array, unsigned int index)
{
        if (index >= MAX_ARRAY_ELEMS)
                return 0;
        else {
                index = array_index_nospec(index, MAX_ARRAY_ELEMS);
                return array[index];
        }
}