執行人: Mike1117
解說錄影
rota1001
下方在解讀 t-test 的結果中,使用「落在 1 以下」與「落在 1-2 之間」來描述結果,然而 t 值的大小單獨來看是沒有意義的,你是以什麼自由度與什麼顯著水準下在解讀這個結果的?
Mike1117
在 dudect
的設計中,t_compute()
僅回傳 t-value
本身,並未進一步查表取得 p-value,也未使用自由度進行傳統意義下的假設檢定:
此外,論文中亦提及:
A t value larger than 4.5 provides strong statistical evidence that the distributions are different.
可見作者採用的是經驗性門檻,而非傳統的顯著水準 。
而在原實作中,report()
函式內也直接以 t 值門檻進行判斷:
此處的門檻 10
較論文中的 4.5
更為寬鬆,以求更保守的判斷。
由此可見,dudect
採用的是非傳統統計檢定的流程,不基於某一自由度對應的臨界值,而是直接依據 t 值是否超過門檻來判斷 timing distribution 是否顯著不同,即是否有 Time leakage 的發生,故我此處直接以「落在 1 以下」與「落在 1-2 之間」來描述結果。
作者直接計算並比較 t 值,省略自由度的計算及查表取得 p-value 的步驟,我認為其考量有以下幾點:
dudect
檢測時通常涉及極大的樣本數(數萬至數十萬次測量),當樣本數非常大時, t-distribution 會收斂於標準常態分佈。對於一個遠離中心點的 t 值(如上述提及的門檻 4.5 或 10),其對應的 p-value 會趨近於零,遠小於任何傳統的顯著水準(如 0.05 或 0.01)。因此,額外計算精確的 p-value 並無實質意義,直接比較 t 值本身更為高效。fcu-D0812998
dudect 檢測結果是否會被 CPU cache 和 branch prediction 的雜訊影響?如果會,該如何避免。
Mike1117
dudect
不對底層硬體進行建模,所以勢必會被 CPU cache 和 branch prediction 的雜訊所影響。
但同時, dudect
的每輪量測都會至少收集 10000 筆以上的資料,
根據中央極限定理,在如此大量的資料量下,隨機雜訊會被平均化,最終資料會趨近於常態分佈,如此會稀釋 CPU cache 和 branch prediction 等雜訊的影響。
此外,dudect
會對原始資料和多個 percentile 裁剪後數據(以及二階 centered-product 資料)分別執行 t-test,這會進一步降低極端 outlier 的干擾,從而輸出較為穩定的結果。
重新研讀〈Dude, is my code constant time?〉論文,並運用 lab0-c 給定的程式碼探討常數時間偵測的議題,隨後從 Linux 核心選出若干應為常數時間的程式碼 (如密碼學,需要適度改寫),應用 dudect 來檢測是否屬於常數時間的實作。
詳實紀錄你的認知和疑惑,務必誠實面對自己
在完全理解這篇論文之前,需要先具備以下提及的先備知識
假說檢定是推論統計中用於檢定現有資料是否足以支持特定假設的方法。
欲檢定統計上假設的正確性的為虛無假說(Null hypothesis,記為 )
而相對於虛無假說的其他有關母數之論述是對立假說(Alternative hypothesis,記為 或 )。
一個簡單的例子便是我們在法庭上,會預先假設被告為無罪(此為虛無假說 ),而檢察官需要提供足夠的證據來證明被告有罪(此為對立假說 ),若證據不足,那法官便會判被告無罪(但實際上不一定無罪,僅是證據不足)。
一般情況下所說的 T-test 指的便是由 William Sealy Gosset 以 Student 為筆名所提出的 Student's t-test ,而 Welch’s t-test 則是由 Bernard Lewis Welch 所提出的基於 Student's t-test 的一種變體 t-test 。
由 William Sealy Gosset 於 1908 年提出的運用假設檢定評估一個或兩個母體平均數的工具。
t-test 可能會用於:
類型 | 單樣本 t 檢定 (One-sample t-test) | 兩樣本 t 檢定 (Two-sample t-test) | 成對 t 檢定 (Paired t-test) |
---|---|---|---|
變數數量 | 一 | 二 | 二 |
變數類型 | 連續測量資料 | 連續測量資料 + 群組類別變數 | 連續測量資料 + 配對群組變數 |
檢定目的 | 決定母體平均數是否等於特定值 | 決定兩個不同群組的母體平均數是否相等 | 決定配對測量的平均差是否為 0 |
範例 | 一群人的平均心率是否等於 65 | 兩群人的平均心率是否相等 | 一群人在運動前後的心率差是否為 0 |
母體平均數估計值 | 樣本平均數 | 各群組的樣本平均數 | 配對差異的樣本平均數 |
母體標準差 | 未知,使用樣本標準差 | 未知,使用各群組樣本標準差 | 未知,使用配對差異樣本標準差 |
自由度 | n – 1 | n₁ + n₂ – 2 | n – 1 |
Student's t-test 的一種變體, Student's t-test 要求 Two sample t-test 時兩樣本之變異數要相同,
而 Welch’s t-test 則不要求變異數相等,因此在實務上更穩健,更為泛用。
其公式如下:
其中:
從這個公式我們可以看出, 值會隨著兩組樣本平均值 與 之間的差距增加而變大;同時,若樣本標準差 增加或樣本數 減少 ,則分母會變大,導致 值變小。
這反映出 Welch's t-test 在比較平均數時,會同時考慮變異程度與樣本規模的影響。
Leakage detection
方法的一種,最早由 Gilbert Goodwill 等人於論文 A testing methodology for side channel resistance validation
中提出,是一種將輸入資料的型態分成固定和隨機兩組,並使用統計檢定來判定這兩組資料所產生的輸出是否有顯著差異的方法。
本篇論文基於以上提及的理論基礎,提出了一個簡單、小巧且無需依賴硬體建模的方法檢測某段程式碼能否在指定平台上以常數時間執行的工具,其過程如下:
NUMBER_PERCENTILES
為 100 ,故每次需更新
Reference:
解說為何之前會有偵測的疑慮,又如何運用統計學來分析
可以看到在原始實作中,共將 difference(執行時間) push 進了三種不同的組別,分別為:
ctx->ttest_ctxs[0]
ctx->ttest_ctxs[對應 percentile]
centered-square
,將其 centered-square
push 進 ctx->ttest_ctxs[101]
最後會再計算三組共 102 筆資料的 t 值,檢測是否有 Leakage 發生。
那麼既然已經檢查了原始兩組比對的 t 值,為何還要再分為後面兩組再比對 t 值呢?
先從第二組 percentile 開始看起,
percentiles 意為 百分位數
,參考維基百科:
若將一組數據從小到大排序,並計算相應的累計百分點,則某百分點所對應數據的值,就稱為這百分點的百分位數。
而在原始實作中, percentile 是這樣決定的:
可以看到在執行完第一次量測後,會呼叫 prepare_percentiles(ctx)
來劃分 percentile 。
prepare_percentiles
會先將第一次量測的資料根據執行時間由小到大進行排序,並以
公式 計算目前應的百分位數,最後呼叫 percentile
計算百分位數對應的 index ,取出當前百分位數對應的具體執行時間。
根據公式,0 - 100 組的分位點會如下所示:
可以看到分位點主要集中在 80 - 100 % 的範圍內,論文中提及:
The upper tail may be more influenced by data-independent noise.
大多數的執行時間都會受到系統等因素的影響,進而導致拉長執行時間,最後造成肥尾分布( Fat-tailed distribution ),如果按照傳統線性切分 percentile ,就會導致在較前的百分位捨棄太多筆數的資料,進而導致結果的不穩定,所以作者此處選擇靠後的百分位數切分。
而以經不同 percentile 裁切後的資料做 t-test 是為了縮小離群值所帶來的影響,讓 t-test 更專注在非離群值的細小差異上。
第三組資料則是以 來進行 t-test ,因為兩組資料可能在平均執行時間上沒有顯著差異,但可能在離散程度上存在差異,這個值可以很好的表現資料本身的離散程度。
回到問題本身,為什麼要做多次 t-test?我覺得主要有以下幾個原因:
在重新研讀論文以及 lab0-c 給定的程式碼後,我發現 lab0-c 的 dudect 部分有實作上的錯誤。
可以看到位於 dudect/constant.c
以及 lab0-c/dudect/fixture.c
中的兩個函式 measure()
以及 update_statistics()
,
這兩個函式的作用分別為對特定函式量測其執行所需要的 CPU cycles 以及利用計算出的 CPU cycles 更新 t-test context 。
具體程式碼如下:
可以看到,每次 measure()
都會將函式執行前後的 CPU cycles 存入 before_ticks[i]
以及 after_ticks[i]
,i
的範圍為 DROP_SIZE
~ N_MEASURES - DROP_SIZE - 1
這邊有必要釐清
N_MEASURES
: 根據註解,為 Number of measurements per test
DROP_SIZE
: constant.h
中並未給出具體定義,但根據原實作及論文,可以知道這是每次量測後需丟棄的資料大小為什麼要丟棄一部分資料?根據原實作的註解及論文推測:
/* discard the first few measurements */
此處丟棄的為開始量測後的數筆頭部資料,是為了排除系統處於 warm-up
階段時量測到的可能含有雜訊的資料。
故不難推斷 DROP_SIZE
同樣是為了定義需要丟棄的資料大小,不過相較於原實作只丟棄頭部資料, lab0-c
中的實作設計為還會丟棄尾部的資料。
而現有實作的缺陷非常明顯,正常實作的流程應為:
update_statistics
時丟棄頭尾部資料而目前 lab0-c
的實作為:
DROP_SIZE
個 index ,開始量測後直接將資料存入 DROP_SIZE
~ N_MEASURES - DROP_SIZE
→ 在 update_statistics
時卻存取 10 ~ N_MEASURES
此舉頗有刻舟求劍的意味,很明顯是錯誤的實作,在 update_statistics
中加入 Debug 訊息,將 exec_times[i]
印出後會如下所示:
可以看到不管是頭部亦或是尾部,都會有很多未經量測,exec_time = 0
的資料。
根據前述提及 Welch’s t-test
的公式,這些 zero-filled
的資料經 update_statistics
更新 ctxs
後,會導致平均數 , 的減小,以及 , 的增大,兩者都會導致 t 值變小,進而可能導致 dudect
的 Time Leakage 偵測失敗。
故應該做如下修改:
update_statistics
,使其可以正確丟棄頭/尾的量測資料:如此即可完整量測資料,並在 update_statistics
時正確丟棄頭尾的資料。
也已提交對應的 Pull requests
從 Linux 核心選出若干應為常數時間的程式碼 (如密碼學,需要適度改寫),應用 dudect 來檢測是否屬於常數時間的實作
原函數如下:
__crypto_memneq_generic()
是於 Linux 核心中用來進行 constant-time 比較的一個實作。
主要目的是:在比較兩段記憶體是否相等時,不讓比較過程因為出現分支或資料相關而過早 return ,可以避免被 side-channel attack(例如 timing attack)觀察到敏感資訊。
於此將其改寫為如下形式:
此版本直接採用 byte-by-byte
的實作,先定義一固定的記憶體區塊 fixed
,在 measure 時會以隨機或固定的兩個 class 與 fixed 進行比對,即:
最終結果如下:
可以看到經過多輪 measurements 後, t 值均落在 1 - 2 之間,明顯為 constant time 的實作。
原函式如下:
此函式是 SHA-256 (Secure Hash Algorithm 256-bit) 密碼學雜湊演算法的核心組成部分。其主要作用是實現該演算法所定義的壓縮函式。
在 SHA-256 演算法的 Merkle–Damgård 架構下,任意長度的輸入訊息會先經過預處理(填補與長度附加),然後被分割成一連串 512 bits(64 Bytes)的訊息區塊。sha256_block_generic
的職責就是對單一的 512 bit block 進行處理,將其資訊安全地壓縮並混入一個 256 位元的內部狀態中。
如果這個函式不是 Constant time ,會導致 SHA-256 的實作變為 Not constant time ,進而導致 side-channel attacking 。
將該函式引入 dudect 並補齊相關巨集進行測試,結果如下:
可以看到經過多輪 measurements 後, t 值均落在 1 以下,可以佐證其為 constant time 的實作。