推測執行¶
本文件解釋了推測執行的潛在影響,以及如何使用通用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];
}
}