# 2018q1 第 11 週測驗題 (上) ### 測驗 `1` 考慮以下程式碼: ```clike #include <stdio.h> struct empty { }; int main() { printf("%d\n", sizeof(void)); printf("%d\n", sizeof(struct empty)); } ``` 在 gcc 編譯時指定 `-std=c99` (參照 C99 規範) 輸出為以下兩行: ``` 1 0 ``` 從以下選項中挑出最接近的描述。 注意:同樣的程式碼經過 C 和 C++ 編譯器產生的執行檔有截然不同的輸出 ==作答區== A = ? * `(a)` 這程式之所以能夠通過編譯,是因為 [GNU extensions](https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html); * `(b)` C99/C11 規格書對此已有正面描述,執行結果符合預期; * `(c)` 會有上述執行結果,顯示 gcc 編譯器內部的實作錯誤; :::success 延伸題目: 1. 在 C99 規格書和 gcc 手冊找到對應的描述,並充分解釋。透過 gcc 編譯時,加上選項 `-pedantic` 2. 承上,推測規格書和手冊為何要如此設計; 3. 嘗試用 C++ 編譯器 (如 g++ 和 clang++) 編譯上述程式,並且參照 C++ 規格書解釋為何如此; ::: --- ### 測驗 `2` 考慮以下程式碼: ```Clike= #include <inttypes.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { char *p, *q; uintptr_t pv, qv; { char a = 3; p = &a; pv = (uintptr_t) p; } { char b = 4; q = &b; qv = (uintptr_t) q; } if (p != q) { printf("%p is different from %p\n", (void *) p, (void *) q); printf("%" PRIxPTR " is not the same as %" PRIxPTR "\n", pv, qv); } else { printf("Surprise!\n"); } return 0; } ``` 這段程式碼在 macOS 和 GNU/Linux 有著不同的執行輸出: - [ ] macOS 17.5.0 / x86_64 平台上用 clang-902.0.39.1 編譯,得到以下執行輸出 ``` 0x7ffee37fbabf is different from 0x7ffee37fbabe 7ffee37fbabf is not the same as 7ffee37fbabe ``` - [ ] Ubuntu Linux 17.10 / x86-64 平台用 gcc-7.2.0 編譯得到以下執行輸出 ``` Surprise! ``` 從以下選項中挑出最接近的描述 ==作答區== B = ? * `(a)` 純粹只是 C 編譯器行為的落差,實務上不需特別留意; * `(b)` `char a` 和 `char b` 這兩個物件的生命週期僅分別在第 11 行和第 16 行有效,一旦超出 scope (作用範圍),C 語言編譯器就會主動釋放物件,而透過 `&` (value-of) 運算子得到的地址自然就不再有效; * `(c)` `char a` 和 `char b` 這兩個物件的生命週期僅分別在第 11 行和第 16 行有效,一旦超出 scope (作用範圍) 仍操作物件的話,會導致 undefined behaviour; * `(d)` gcc 的實作不符合 C99/C11 規格描述; 提示: 1. 在資訊安全的漏洞回報中,[CWE-416: Use After Free](https://cwe.mitre.org/data/definitions/416.html) 提及 dangling pointer 的影響 2. 在 C11 規格書中 `6.2.4:2` 節提及物件的生命週期: > The lifetime of an object is the portion of program execution during which storage is guaranteed to be reserved for it. An object exists, has a constant address, and retains its last-stored value throughout its lifetime. If an object is referred to outside of its lifetime, the behavior is undefined. The value of a pointer becomes indeterminate when the object it points to (or just past) reaches the end of its lifetime. :::success 延伸問題: 1. 從 C99/C11 規格和編譯器行為,解釋上述程式的運作,並找到真實世界中類似的案例,最好是 Linux 核心程式碼相關 2. 上方程式的 `PRIxPTR` 為何?在哪裡定義? ::: --- ### 測驗 `3` 考慮以下 cmov (contional move) 指令的 C 程式實作: ```Clike void *cmoveq(int c, void *p1, void *p2) { void *r = p1; if (!c) r = p2; return r; } ``` 在 Intel Pentium Pro (也稱為 [P6 微架構](https://en.wikipedia.org/wiki/P6_(microarchitecture))) 後引入新指令 `cmoveq`,於是上面的 C 程式等價於以下 x86_64 組合語言輸出: ``` cmoveq: testl %edi, %edi cmoveq %rdx, %rsi movq %rsi, %rax retq ``` 關鍵不僅在於程式碼的密度 (code density),更在於少了分支,這對於密碼學的實作非常關鍵,依據 cryptocoding.net 的建議 [Avoid branchings controlled by secret data](https://cryptocoding.net/index.php/Coding_rules#Avoid_branchings_controlled_by_secret_data),這可避免 timing attack。請改寫上述 C 程式,使其得以在常數時間內完成 cmoveq 等價功能。欲改寫的新程式碼如下: ```Clike #include <stdint.h> void *cmoveq(int c, void *p1, void *p2) { uintptr_t mask = -(!!c); return (void *) (((uintptr_t) C1) | ((uintptr_t) C2)); } ``` 請補完上述 `C1` 和 `C2` 程式碼。 ==作答區== C1 = ? * `(a)` p1 | mask * `(b)` p1 ^ mask * `(c)` p1 & mask * `(d)` p1 | ~mask * `(e)` p1 ^ ~mask * `(f)` p1 & ~mask C2 = ? * `(a)` p2 | mask * `(b)` p2 ^ mask * `(c)` p2 & mask * `(d)` p2 | ~mask * `(e)` p2 ^ ~mask * `(f)` p2 & ~mask 參考資料: * [New P6 OpCodes: CMOV](http://www.rcollins.org/p6/opcodes/CMOV.html) * [Compilers and constant-time code](http://www.reparaz.net/oscar/misc/cmov.html) * [Linus Torvalds 針對 cmov 的討論](http://yarchive.net/comp/linux/cmov.html) * [Why Constant-Time Crypto?](https://www.bearssl.org/constanttime.html) * [Binary Search *eliminates* Branch Mispredictions](https://www.pvk.ca/Blog/2012/07/03/binary-search-star-eliminates-star-branch-mispredictions/) 現在的編譯器最佳化已能夠分析特定程式模式,並以硬體指令替代,如 clang/llvm 可將上述 cmov 程式轉為對應包含 cmov 指令的組合語言,實驗方式為: ```shell $ clang -S -Ofast -fomit-frame-pointer -fno-asynchronous-unwind-tables cmov.c ``` 檢查編譯器產生的 `cmov.s`,確認裡頭包含 `cmoveq` 指令 :::success 延伸題目: 1. 詳述 Timing attack 並且舉出和 Linux/Android 有關的真實案例; 2. 參照 [clmul](https://hackmd.io/s/HkQfalnpe),舉出密碼學裡頭 constant-time crypto 的實作,並佐以現代 Intel 處理器的加速指令作為探討對象,需要有演算法和效能分析; 3. 研讀 [Linus Torvalds 針對 cmov 的討論](http://yarchive.net/comp/linux/cmov.html) 和 [Branchless Conditionals (Compiler Optimization Technique)](http://www.blueraja.com/blog/285/branchless-conditionals-compiler-optimization-technique),之後再解讀 [Linux Kernel Memory Barrier](https://www.kernel.org/doc/Documentation/memory-barriers.txt) 和 cmov 指令相關的段落 :::