檢查大小時要注意的問題

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 的值會是多少,於是就很容易造成各種意外。

原文有提到更多資訊,以及提出的解法,這邊就不提了...

8 thoughts on “檢查大小時要注意的問題”

  1. 你的 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/

  2. 看不出來哪裡不正確?

    Programmer 會把 size_t bytes 放在外側的問題就是打算要用到 bytes 的值做判斷,而這變成 undefined behavior。

  3. 上面那段 code 沒有用到 bytes 的值啊. 問題就是就算你沒用到它的值, 它還是會讓 compiler 做出預期外的判斷.

  4. 我的 summary:

    所以 size_t bytes = n * m; 這段 code 並不保證當 overflow 後 bytes 的值會是多少,於是就很容易造成各種意外。

    我還是沒看出來我哪邊有寫錯。這個推論是對的。

  5. 問題不在值是多少, 就算你完全沒用到它的值還是會造成各種意外. 換言之就算你這樣寫:

    (void)(n * m);
    if (n > 0 && m > 0 && SIZE_MAX/n >= m) {

    Compiler 還是有權做出預期外的最佳化.

  6. 會寫出 size_t bytes 放在外面的 code 就是打算要用他,不然幹嘛放在外面。

    Programmer 耍豬頭的可能性比 undefined behavior 造成 rm -rf / 高多了。

    所以我才會說:

    所以 size_t bytes = n * m; 這段 code 並不保證當 overflow 後 bytes 的值會是多少,於是就很容易造成各種意外。

  7. 不盡然, 在 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 就是這種錯誤的典型範例.

Leave a Reply

Your email address will not be published. Required fields are marked *