「Buggy Security Guidance from Apple」這篇再次說明了 C 語言因為 undefined behavior 而讓 coding 需要注意的事情變多...
這段 code:
size_t bytes = n * m; if (n > 0 && m > 0 && SIZE_MAX/n >= m) { ... /* allocate "bytes" space */ }
問題在於 C 語言裡對 overflow 並沒有定義行為 (undefined behavior),所以 size_t bytes = n * m;
這段 code 並不保證當 overflow 後 bytes 的值會是多少,於是就很容易造成各種意外。
原文有提到更多資訊,以及提出的解法,這邊就不提了...
你的 summary 下得不正確啊. XD
正確的說法是 C 語言中 signed integer overflow 是未定義行為, compiler 允許做任何它想要的處理, 包括 rm -fr /, 這比不保證 overflow 的值嚴重多了, 後者至少保證 compiler 必須維持某種程度的 sanity, 在上面那段 code 是完全沒有問題的.
實務上上面那段 code 會發生的事情是 compiler 看見 bytes = n * m 之後, 它就可以安心地斷定 n * m <= SIZE_MAX, 因為如果超過的話是 undefined behavior, 它本來就想幹嘛都可以. 因為斷定了 n * m 0, 因而 compiler 可以再次證明出 SIZE_MAX / n >= m 永遠成立, 所以 if condition 可以簡化成 (n > 0 && m > 0 && true), 也就是你原本想檢查的東西沒有檢查到.
這個 bug 其實跟前幾年 Linux kernel 某個 null check 被 optimize 掉的 bug 有異曲同工之處: http://lwn.net/Articles/342330/
看不出來哪裡不正確?
Programmer 會把
size_t bytes
放在外側的問題就是打算要用到bytes
的值做判斷,而這變成 undefined behavior。上面那段 code 沒有用到 bytes 的值啊. 問題就是就算你沒用到它的值, 它還是會讓 compiler 做出預期外的判斷.
我的 summary:
我還是沒看出來我哪邊有寫錯。這個推論是對的。
問題不在值是多少, 就算你完全沒用到它的值還是會造成各種意外. 換言之就算你這樣寫:
(void)(n * m);
if (n > 0 && m > 0 && SIZE_MAX/n >= m) {
Compiler 還是有權做出預期外的最佳化.
會寫出
size_t bytes
放在外面的 code 就是打算要用他,不然幹嘛放在外面。Programmer 耍豬頭的可能性比 undefined behavior 造成 rm -rf / 高多了。
所以我才會說:
不盡然, 在 C89/C90 的時候還不允許即地宣告, 所以很有可能會寫出這樣的 code:
void *safe_alloc(size_t n, size_t m) {
size_t total_size = n * m;
if (n > 0 && SIZE_MAX/n < m)
return 0;
return malloc(total_size);
}
我上面引的 Linux kernel bug 就是這種錯誤的典型範例.
你這是稻草人論證。