Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Defense Against Undefined Behavior -未定義動作に対する防衛術-

Cranberries
December 08, 2018
1.2k

Defense Against Undefined Behavior -未定義動作に対する防衛術-

未定義動作に関する基礎的情報とその避け方の共有

Cranberries

December 08, 2018
Tweet

Transcript

  1. Defense Against
    Undefined Behavior
    未定義動作に対する防衛術
    発表をご覧になった方々へのお詫び:
    Sequence Point・Sequence-beforeの解説内容に不備がありました。
    間違いの訂正に関して赤字で修正・加筆を行っています。
    クイズの解説スライドも追加しているのでご査収下さい。
    Sequence PointはC++03までのルールでした。
    C++11からはSequence-beforeで説明されますです。
    大変申し訳ありません。

    View Slide

  2. Rust 1.31.0 RELEASE!!
    Rust 2018 Edition Stable!!
    使っていきましょう!!
    Non-lexical lifetimes!!

    View Slide

  3. 自己紹介
     神:いなむのみたま@mitama_rs
     CADDi株式会社に雇用されている
     CADを解析にC++17を使っている
     ラーメンが好き
     けん玉が得意

    View Slide

  4. Undefined Behavior 101
    未定義動作入門
     Defined Behavior
     規格書が明示的に一つの動作を定めている
     Implementation Defined Behavior
     複数の動作からコンパイラが動作を選べる
     どの動作をするかドキュメントに明記されなければならない
     Unspecified Behavior
     複数の動作のどの動作をしても良いことになっている
     Undefined Behavior(UB)
     完全に何も決まっていない
     何が起こってもおかしくない

    View Slide

  5. Why Study Undefined Behavior?
    なぜ未定義動作を学ぶのか?
     UBはプログラム全体を未定義にする
     UBが起こったときにはコンパイラは何をしてもいいことに起因する
     例えば、UBを放置した結果6ヶ月後にレアケースでバグる
     または、ツールチェインをかえたらエラーになって動かないなど
     UBによるバグの修正には時間を要しがち
     髪の毛も抜ける
     髪の毛を守るためにUBを避ける技術を学ぼう!

    View Slide

  6. Defined Undefined Behavior
    未定義動作だと定義されているもの
     ヌルポインタのデリファレンス
     配列のレンジ外アクセス
     未初期化の変数の使用
     異なるポインタ型を介したオブジェクトへのアクセス(Strict Aliasing)
     デストラクト済みの変数へのアクセス
     副作用のない無限ループ
     レースコンディション
     整数型のサイズを超えたシフト演算
     コンストラクタ・デストラクタからの純粋仮想関数の呼び出し
     整数型のゼロ割り
     整数型のオーバーフロー
     他多数. . .

    View Slide

  7. Really easy explanation of UB
    よく分かる未定義動作の解説
    C++ 料理

    View Slide

  8. Cooking and UB
    料理と未定義動作
     例えば塩と砂糖を間違えると…
     料理全体が未定義に!
     C++も同じ
     規格書が定めていることを書くことが重要
     規格書が定めていないものはすべて未定義

    View Slide

  9. Recipe and Standard
    レシピと規格書
     料理はレシピが規定し、C++プログラムは規格書が規定する
    材料
    完成品
    レシピ
    コード
    挙動
    規格書

    View Slide

  10. Quiz
    突然ですがクイズです
    varA = 5;
    varA = ++varA + 2;
    ↑varA = ???
    varB = 3;
    varB = varB++ + 2;
    ↑varB = ???

    View Slide

  11. Correct answer
    正解はこちら
    varA = 5;
    varA = ++varA + 2; // C++03, undefined behavior
    varA == 8; // C++11, defined
    varB = 3;
    varB = varB++ + 2; // C++03, undefined behavior
    varB == 5; // C++11, undefined behavior
    // C++17, defined

    View Slide

  12. Side Effects, Sequence and UB
    副作用とシーケンスと未定義動作
     同一のオブジェクトに対する2つの副作用があり、
     それらの処理順が定められていない場合、
     未定義動作になる
     (§1.9.15, C++11 / §4.6.17, C++17)
    x++ + ++x; // crazy expression

    View Slide

  13. Side Effects
    副作用
     式がある状態(変数など)を変更すると、副作用が生じる
     雑に言うと、副作用は値を返す以外の何かである

    View Slide

  14. What happened with C++11 and C++17?
    C++11とC++17で何が変わった?
     C++11:Squence point rules(C++03)の終わり, Sequence-before rules(C++11)の誕生
     C++17: Sequence-before rulesの大幅な追加
     ※C++11でSequence point rules自体の刷新が行われていました

    View Slide

  15. Sequence point rules
    (until C++11)
     すべての式の副作用が完了し
     次の式が評価されていないコード内の場所
     通常は式の最後
     full-expressionの途中はよくわからん状態
     C++03のルール、C++11からはSequence-before rules

    View Slide

  16. Sequence-before and Sequence-after
    (from C++11)
    副作用完了点の定義
    なんらかの式AとBがあるとき
     式Aは式Bの前にシーケンスすることができ、これは式Bが式Aの後にシーケンス
    されることと同じである
     不確定なシーケンスは、ある式が他の式の前にシーケンスされているところで
    発生し、最初に起こるものは不明である
     AがBより前にシーケンスされず、BがAより前にシーケンスされない場合、式A
    およびBの評価はシーケンスされない(重複し得る)

    View Slide

  17. In other words
    つまり
     C++11以降では
     ある式に式Aと式Bが含まれているとき
     AとBの評価順が決まっているものと
     そうでないものがある
     一意に評価順が定まらない場合
     AとBに同一のオブジェクトへの副作用が含まれていたら未定義動作になる
     Unsequencedと呼ばれる状態

    View Slide

  18. C++11:諸々の評価順序の規定
     あらゆるオペレーターのオペランドの値の評価(ただし副作用を除く) はオペ
    レーターの結果の評価より先に評価される(ただし副作用を除く)
     A + B という式ではAとBが評価されてから結果が評価される
     built-in assignmentがSequence-before rulesで順序規定された
     A = B という式では式Bの評価(副作用を除く)が完了してから代入される
     Built-in 前置++(--)の副作用はSequence-before rulesで順序規定された
     ++AがA = A + 1に置き換えられるという事実による暗黙のルール
     上記2つの順序規定より自明
     ++Aの副作用は++Aの評価のあと完了する!
     副作用の評価全然定まってねえ
     式に副作用が2つあったとき、たいてい未定義の国へ直行

    View Slide

  19. C++17:諸々の副作用の評価順序規定
     In a subscript expression E1[E2], every value computation and side-effect of
    E1 is sequenced before every value computation and side effect of E2
     In a shift operator expression E1<>E2, every value computation
    and side-effect of E1 is sequenced before every value computation and side
    effect of E2
     In every simple assignment expression E1=E2 and every compound assignment
    expression [email protected]=E2, every value computation and side-effect of E2 is
    sequenced before every value computation and side effect of E1
     etc...
     いい感じに副作用が完了するようになった!

    View Slide

  20. C++17:関数の引数評価順序
     Until C++17: unspecified
     C++17:引数の評価は、関数呼び出しの前に「順序付けされる」
    f(g(h(var)));
     関数に引数は呼び出し前に評価されるので
     fより前にg、gより前にhが評価される
     fの評価時にはgの副作用が完了しており
     gの評価時にはhの副作用が完了している

    View Slide

  21. ただし、引数の評価順序はunspecified
    int var;
    func( var = hoge(), huga(var) ); // ???
    huga(var);
    var = hoge();
    func();
    var = hoge();
    huga(var1);
    func();
    どちらにもなりえる

    View Slide

  22. さきほどのクイズのポイント
     同一変数の副作用の評価にSequence-before ruleが成立するか?

    View Slide

  23. さきほどのクイズ解説
    varB = 3;
    varB = varB++ + 2;
    C++11
    1. ++varAの副作用はvarAの評価の後評価される
    2. ++varA + 2 は結果より先にオペランドが評価される
    3. varA = ++varA + 2 は ++varA + 2 が先に評価される
    4. 代入で副作用が発生するが、 1. 2. 3. により代入前にインクリメントによる副作用
    が終了する
    5. varAの副作用はすべてsequenced
    6. よってDefined Behavior
    Q.E.D.

    View Slide

  24. さきほどのクイズ解説
    varA = 5;
    varA = ++varA + 2;
    C++11
    1. varA++の結果はその副作用よりも前に評価される
    2. varA++ + 2 は結果より先にオペランドが評価される(副作用を除く)
    3. varA = varA++ + 2 は varA++ + 2 が先に評価される(副作用を除く)
    4. 代入で副作用が発生するが、varA++の副作用が代入前に完了確定していない
    5. varAの副作用がunsequenced
    6. よって、Undefined Behavior
    Q.E.D.

    View Slide

  25. さきほどのクイズ解説
    varA = 5;
    varA = ++varA + 2;
    C++17
    1. varA++の結果はその副作用よりも前に評価される
    2. varA++ + 2 は結果より先にオペランドが評価される(副作用を除く)
    3. varA = varA++ + 2 は varA++ + 2 が先に評価され、
    varA++ + 2 の副作用はすべて完了する
    4. 代入で副作用が発生するが、 1. 2. 3. により代入前にインクリメントによる
    副作用が終了する
    5. varAの副作用はすべてsequenced
    6. よって、Defined Behavior
    Q.E.D.

    View Slide

  26. Avoiding Undefined Behavior
    未定義動作を避けるために

    View Slide

  27. General
    基本だけど重要なこと
     規格書を読んだり
     cppreference.comやcpprefjpのようなサイトを読んだり
     コードレビューをしたり
     コーナーケースでテストしたり
     静的解析ツールを使ったり
     コンパイラの警告耳を傾ける
     -Wall –Wextra (-pedantic)
    とにかく、これらをサボらないこと

    View Slide

  28. Using Sanitizers
    サニタイザーの利用
     clang
     Address Sanitizer
     Memory Sanitizer
     Undefined Behavior Sanitizer
     Thread Sanitizer
     gcc
     Address Sanitizer
     Undefined Behavior Sanitizer

    View Slide

  29. Try Multiple Compilers
    複数のコンパイラの利用
     処理系によってUBが発生する可能性がある
     複数のコンパイラでの確認は有効
     新しいコンパイラほど賢い
     一度最新のコンパイラでコンパイルしてみる価値はある

    View Slide

  30. Undefined Behavior C++17 Compiler Warnings
    C++17対応コンパイラの未定義動作の警告
     GCC 7.3, GCC 8.2
     operation on 'varB' may be undefined [-Wsequence-point]
     clang 6.0, clang 7.0
     unsequenced modification and access to 'varB' [-Wunsequenced]

    View Slide

  31. Using Online Compilers
    オンラインコンパイラの利用
     WandboxやCompiler Explorerなど
     複数のコンパイラで気軽にコンパイルできる!
     いろいろなエラーが見られて楽しい!
     最新の機能を試せる!

    View Slide

  32. Try –Wlifetime on Compiler Explorer
    Compiler Explorerで-Wlifetimeを試す

    View Slide

  33. Wandboxを使ってCIを回してみた
     あー全部のコンパイラでテスト回すの面倒だなー
     WandboxにAPIあるじゃん!?
     テストコードをWandboxに送ってコンパイル&実行
     Wandboxの間違った使い方かもしれん

    View Slide

  34. CBT
     C++コードをinclude解析し
     まとめてWandboxに送り
     実行結果を表示できる
     CLI
     これを使ってテストを送りつけるぞ!

    View Slide

  35. Conclusion
    まとめ
     UBが起こったあとのプログラムはすべてが未定義と思え
     私はこの状態を未定義の国のアリスと呼んでいる
     規格書に明記されてないものはすべて未定義
     未定義動作をクリティカルなバグとして扱いましょう

    View Slide

  36. References
    参考資料
     https://www.copperspice.com/pdf/Undefined-Behavior-CppCon-2018.pdf
     ISO/IEC 14882:2011 Programming Languages -- C++
     ISO/IEC 14882:2017 Programming Languages -- C++
     http://d.hatena.ne.jp/yohhoy/20170104/p1(yohhoyの日記)
     ついでにお世話になったツール・サイト
     Compiler Explorer(https://godbolt.org/)
     Radare2(https://github.com/radare/radare2)

    View Slide

  37. 補足
    Q&A

    View Slide

  38. UBサニタイザしたら安心できる?
    そもそも何してるの?
     UBサニタイザが通したからといって信用は禁物
     UBサニタイザは大量の簡単なヒューリック

    View Slide

  39. 逆アセしてみる
     サニタイザが何してるのか気になったので逆アセしてみた
     まずサニタイザに引っかかります

    View Slide

  40. radare2でバイナリを解析する
     https://github.com/radare/radare2
    バイナリをr2でaa(analyze all)して
    ヒントアドレス付近を逆アセで確認。
    ひたすらチェックが差し込まれてる。
    このようなコードがいたるところに。
    これがUBSanがやっていること。
    年々賢くなっているとはいえ、
    チェック漏れもあるかも?

    View Slide