執行人: Booker-Chen
專題解說影片
回顧第一次作業提到的 dudect,詳述其原理,並重現相關實驗,從而歸納出判斷給定的程式碼是否屬於常數時間的實作。應彙整其他學員的成果,例如:
為什麼需要知道程式碼的執行是否為常數時間 ?
從資訊安全、密碼學,和處理器的行為來探討。
在 解讀計算機編碼 中提到若一段程式碼的運行不是常數時間的話,就可能會被發起時序攻擊 (timing attack)。
abs
有分支 (branch) 的絕對值函式
該函式當遇到負數時會比遇到正數時多執行 -value
這道敘述,
針對 32 位元沒有分支 (branchless) 的絕對值函式
當 x
> 0
,mask
= 0x00000000
,(x ^ mask)
= x
,x - mask
= x
。
當 x
< 0
,mask
= 0xFFFFFFFF
,(x ^ mask)
= ~x
,~x - mask
= ~x + 1
= -x
在 Linux manual page 提到關於 memcmp
的描述
Do not use memcmp() to compare confidential data, such as cryptographic secrets, because the CPU time required for the comparison depends on the contents of the addresses compared, this function is subject to timing-based side-channel attacks. In such cases, a function that performs comparisons indeterministic time, depending only on n (the quantity of bytes compared) is required.
從這段描述中可以得知 memcmp
的運行並不是常數時間,因為當它在比較的過程中一發現兩者不同,就會直接回傳值了。
常數時間的 memcmp
專注於程式碼本身,不要列出填空的 X, Y
收到,已更改
解析 res = (res & (((diff - 1) & ~diff) >> 8)) | diff
diff
的範圍為 255 ~ -255,即 0x000000FF
~ 0x00000000
,0xFFFFFFFF
~ 0xFFFFFF01
。
當 diff
= 0
((diff - 1) & ~diff)
= 0xFFFFFFFF & 0xFFFFFFFF
= 0xFFFFFFFF
0xFFFFFFFF >> 8
= 0xFFFFFFFF
res & 0xFFFFFFFF
= res
res | diff
= res | 0
= res
res
= res
當 diff
!= 0
(((diff - 1) & ~diff) >> 8)
= 0
res & 0
= 0
0 | diff
= diff
res
= diff
解析 ((res - 1) >> 8) + (res >> 8) + 1
若 res
> 0
(res - 1) >> 8
= 0
res >> 8
= 0
0 + 0 + 1
= 1
若 res
< 0
(res - 1) >> 8
= 0xFFFFFFFF
res >> 8
= 0xFFFFFFFF
0xFFFFFFFF + 0xFFFFFFFF + 1
= -1
若 res
= 0
(res - 1) >> 8
= 0xFFFFFFFF
res >> 8
= 0
0xFFFFFFFF + 0 + 1
= 0
其他常數時間的 memcmp
實作
研讀論文,應彙整其他學員的成果,詳述其原理,提及相關數學和統計學議題。
網路上有很多其他常數時間的檢測工具,像是 ctgrind 和 ctverif,那為什麼我們要使用 dudect 呢 ? 因為前面提到的這些檢測工具需要模擬硬體設備,然而要模擬出一個完整並且正確的硬體設備並不是一件容易的事。反觀 dudect 則是透過 leakage detection tests,在目標平台上對一段程式碼做執行時間的統計分析,如此一來就巧妙的規避需要模擬硬體的問題。
此部分的程式碼解釋採用的是最接近論文發表日期 2017 年 5 月 27 日的 commit 5237cc0 這個版本。
我們要重複地測量一個函式在兩個不同 class 的 input data 下的執行時間。
Classes definition: 論文中採用的是 fix-vs-random,fixed class 是用來做 corner-case processing。
Cycle counter: 現今的 CPU 提供 cycle counter 讓我們可以用來精準的測量出執行時間。
在 cpucycles.c 中有 cpucycles
這個函式,其功能為回傳 processor 的 time-stamp counter 的值。
rdtsc 這道指令會把 time-stamp counter 存進 EDX:EAX 這兩個暫存器中,透過宣告兩個無號整數 hi
、lo
,將 EDX、EAX 的值讀取出來,再透過將 hi
左移 32 位並與 lo
做 bitwise OR 之後,回傳 time-stamp counter 的值。
prepare_input
。一開始使用 randombytes
這個函式使 input data 全部為 random class,接下來再透過 classes[i] = randombit();
這行程式碼將該次量測的 input data 透過隨機的方式選擇使用 fix class 或是 random class。注意書寫規範:
lab0-c 採用的程式碼開發風格: CONTRIBUTING.md
已修正
在開始執行 statistical analysis 之前,我們可以採用一些 post-processing 的方法。
percentile
函式和 fixture.c 中的 prepare_percentile
函式設定閾值。注意書寫規範:
已修正
prepare_percentiles
函式會將執行時間的陣列、一個 0 ~ 1 的值 (此處為函數 )、執行時間的陣列長度傳給 percentile
函式,並依據其回傳的值設為百分位數。
percentile
函式會依照 prepare_percentiles
傳過來的值做計算後把相應位置的值回傳回去。
關於閾值的選取是否合理 ?
對函數 做分析,DUDECT_NUMBER_PERCENTILES
作者設為 100
。
i | |
---|---|
0 | 0.06696700846 |
1 | 0.1294494367 |
2 | 0.1877476036 |
3 | 0.2421417167 |
… | … |
49 | 0.96875 |
50 | 0.970842719 |
51 | 0.9727952949 |
… | … |
97 | 0.9988782243 |
98 | 0.9989533462 |
99 | 0.9990234375 |
可以看到在閾值的選取上,後 50 筆資料會集中選取後 3% 的執行時間,但是越長的執行時間就代表中間可能遇到與測量目標無關的事件。
For a second-order univariate test the mean of the mean-free squared traces is actually the variance () of the original traces.
對應到 fixture.c 中的 update_statistics
函式
我們要使用 statistical test 嘗試推翻虛無假說 "the two timing distributions are equal"。
這篇論文在 III. Result -> A. Implement -> (b) Statistical test 中說明使用的 statistical test 是 Welch's t-test 搭配 Welford's online algorithm 做使用。
Welch's t-test 是 Student's t-test 的一種,適用於兩個獨立樣本具有相同或不相同的樣本數以及異質變異數的情況下。
Student's t-test 是一種虛無假說測試,當測試樣本在虛無假說下遵從 Student's t-distribution 就可以使用。
Student's t-distribution,,是一個連續的機率分佈函數 (如下圖)。
的分佈型態由參數 決定, 時 就是 standard Cauchy distribution ,當 時, 就是 standard Normal distribution。
,即為自由度 (Degrees of freedom),關於自由度的描述 :
In general, the degrees of freedom of an estimate of a parameter are equal to the number of independent scores that go into the estimate minus the number of parameters used as intermediate steps in the estimation of the parameter itself.
舉例來說,量測 個獨立點的變異數,獨立量測數值的個數為 ,而 intermediate step 使用的參數 (在這裡就是平均數) 只有一個,所以自由度為 。
Student's t-distribution 的機率分佈函數可以寫成 :
當 且為偶數時,
當 且為奇數時,
這個機率分佈函數是對稱的,而且長的很像平均數為 0,變異數為 1 的常態分佈,只是頂端矮了一點,兩邊高了一點。
當 為 30 時就已經很接近常態分佈了(如下圖,紅線為 t-distribution,藍線為 normal distribution,綠線從中間依序往上為 = 1, 2, 3, 5, 10)。
在 ttest.c 中有 t_push
和 t_compute
這兩個函式可以用做計算 Welch's t-test 的 t 值。
透過 Welford's online algorithm 可以求得樣本平均 、有偏樣本變異數 (biased sample variance) 、無偏樣本變異數 (unbiased sample variance)
這裡使用 Welford's online algorithm 有兩個好處
t_push
函式計算出平均 以及 。
計算
計算
注意書寫規範:
已修正
t_compute
函式透過 t_push
函式得出的 計算出無偏樣本變異數 ,進而求出 t 值。
注意書寫規範:
已修正
dudect 判斷一段程式碼是否為常數時間的依據為比較兩個不同 class 的 timing distribution。所以我們需要知道兩個不同 class 執行時間的平均值和變異數。然而在 prepare_input
這個函式採用了 randomly chosen class,這樣會使得兩個獨立樣本具有相同或者不相同的樣本數,再加上我們無從得知母體的變異數以及兩個樣本的變異數可能相同或不同,所以選擇使用 Welch's t-test。
作者在論文的 IV. DISCUSSION -> (e) 提到我們到底要做多少次的量測 ?
當一個 leakage detection test 在 個樣本的量測下無法推翻虛無假說,那我們不能進一步的推斷在 個樣本下也會有一樣的結果。
設定一個確切的量測數量是一個評估者的職責。當有證明足以推翻這個量測數量,比方說量測數量可以更多,那評估的結果也會跟著變動。
leakage detection test 並不會直接告訴你這段程式碼一定會或一定不會有 timing leakage,而是用統計的結果告訴你這段程式碼 “可能” 會有 timing leakage。
在閱讀論文以及重現實驗的過程中,發現作者對於閾值以及執行次數的設定上沒有到很嚴謹以及有些地方沒有很清楚。
t_threshold_bananas
) 就絕對不是常數時間。關於這些提問我在 dudect 的 GitHub 發了一個 discussion。
選定原有程式和新增測試案例,重現常數時間判定的實驗。
這篇論文是在 2017 年 5 月 27 日發表的,所以該論文的實驗應該是使用最接近發表日期的 5237cc0 這個 commit 版本。
避免在虛擬機器上重現實驗。
設定一個長度為 16-bytes 的字串 s
,該字串用來當作正確密碼。
我們要檢測函式 memcmp
在比較兩個字串的操作上是否為常數時間。
注意書寫規範:
已修正
fix class 設定為 s
,即正確密碼 (白箱測試)。
用 fix class 和 random class 各執行 do_one_computation
10000 遍,將範圍介在 percentiles[0]
和 percentiles[99]
之間的執行時間繪製成累積分布函數圖。
t 值設定為 4.5,超過 4.5 表示 fix class 和 random class 兩者的 timing distribution 有極大的可能不同。
number_measurements
設置為 1e6
,這個值為每一次測試需要執行 do_one_computation
的次數。
下圖為執行 120 秒後,t 值的變化趨勢,每條線代表不同的百分位數。
同測試一,但 fix class 設定為 16 個位元皆為 0
的陣列 (黑箱測試)。
下圖為執行 120 秒後,t 值的變化趨勢,每條線代表不同的百分位數。
下圖為執行 600 秒後,t 值的變化趨勢,每條線代表不同的百分位數。
fix class 設定為 16 個位元皆為 0
的陣列,memcmp
在位元組比較的過程中,不會一發現兩者相異馬上回傳結果,而是會逐位元組比較完指定長度之後再回傳結果。
注意書寫規範:
已修正
下圖為執行 120 秒後,t 值的變化趨勢,每條線代表不同的百分位數。