none
return時に例外0xc0000409が発生する場合がある

    質問

  • 【現象】
     呼び出された関数から return するタイミングで、プログラムが
     例外コード0xc0000409 で異常終了する現象が発生します。
     (必ず発生する訳ではなく、同じ操作でも発生する場合と発生しない場合があります。)

    【発生OS】
     Windows10 v1511, v1607, v1703
     (他のバージョンは未確認)

    【質問内容】
     例外が発生しているのは、補足で記述しますが、関数を閉じる「}」に対応する
     部分と思われます。
     このような箇所で例外が発生する原因として、どこの情報を破壊していると
     考えられるでしょうか?
     (関数の戻りアドレスかと思いましたが、例外が発生している関数の auto変数などに
      破壊の形跡がなく、関数の戻りアドレスが別のところに格納されているのか
      根本的に別のものが破壊されているか、判断しきれずにいます。)

    【補足】
    例外発生箇所の cod ファイルを抜粋すると、以下の「★」で例外が発生
    しています。

    ; 885  :    return ret;

      01a7b 8b 85 6c f9 ff
        ff       mov     eax, DWORD PTR _ret$[ebp]

    ; 886  : }

      01a81 8b 4d d0         mov     ecx, DWORD PTR __$ArrayPad$[ebp]
      01a84 33 cd            xor     ecx, ebp
      01a86 e8 00 00 00 00   call    @__security_check_cookie@4
    ★01a8b 8b e5            mov     esp, ebp
      01a8d 5d               pop     ebp
      01a8e c3               ret     0

    2018年5月13日 3:14

回答

  • 呼び出し元アドレスが破壊されていることは想像できるのですが、その呼び出し元アドレスがどこに格納されているのか=どこに存在する値を破壊しているのかが不明なので、破壊を行っているコードを特定できない点が問題です。
    関数内のauto変数や関数の引数が破壊されていないということは、呼び出し元アドレスはこれらとは別の領域にあるのでしょうか。
    だとすれば、どのような変数と同じ領域にある=何に対してバッファーオーバーランを起こすと、呼び出し元アドレスが破壊されるのでしょうか?
    誤解されています。バッファオーバーランが発生した瞬間に検出可能であれば、チェック機能などそもそも不要です。Windowsが対象とするプロセッサでは検出できないからこそ、関数終了直前にチェック処理を埋め込んでいるわけです。
    ですので、バッファオーバーランが検出された関数周辺で問題個所を探すのは開発者が行う作業です。
    • 回答としてマーク A_Takada 2018年5月16日 8:37
    2018年5月13日 23:28

すべての返信

  • 0xC0000409はSTATUS_STACK_BUFFER_OVERRUNです。つまり、コンパイルオプション/GS バッファーのセキュリティ チェックで挿入された__security_check_cookieでバッファーオーバーランを検出したことを示しています。

    例外を報告する際、呼び出し元アドレスを参照するわけですが、そのままでは検出を行った__security_check_cookieのアドレスが得られるだけで真の原因個所が不明なままです。そこで__security_check_cookieはcallでなくjmp命令を使うことで呼び出し元アドレスを偽装しています。結果「call @__security_check_cookie@4」の次のアドレスが報告される仕組みです。

    ともあれ、当該関数周辺でバッファーオーバーランが発生しているのは事実ですので、ソースコードを修正することをお勧めします。

    2018年5月13日 5:00
  • 返信、ありがとうございます。

    呼び出し元アドレスが破壊されていることは想像できるのですが、その呼び出し元アドレスがどこに格納されているのか=どこに存在する値を破壊しているのかが不明なので、破壊を行っているコードを特定できない点が問題です。
    関数内のauto変数や関数の引数が破壊されていないということは、呼び出し元アドレスはこれらとは別の領域にあるのでしょうか。
    だとすれば、どのような変数と同じ領域にある=何に対してバッファーオーバーランを起こすと、呼び出し元アドレスが破壊されるのでしょうか?

    2018年5月13日 15:27
  • その呼び出し元アドレスがどこに格納されているのか=どこに存在する値を破壊しているのかが不明

    スタックです。

    関数内のauto変数や関数の引数が破壊されていないということは、呼び出し元アドレスはこれらとは別の領域にあるのでしょうか。

    だとすれば、どのような変数と同じ領域にある=何に対してバッファーオーバーランを起こすと、呼び出し元アドレスが破壊されるのでしょうか?

    多くの場合、そのローカル変数や引数に対して、範囲外の書き込みをしていることが原因です。
    「破壊されていない」をどのように確認されたのかわかりませんが、それらの要素に対して本当に範囲外書き込みを行う可能性がないか、再点検してください。
    (デバッグのウォッチで値が正常であっても、範囲外に書き込んでいる可能性を否定できないので、ソースコードを見て確認する作業となります)
    2018年5月13日 20:51
    モデレータ
  • 呼び出し元アドレスが破壊されていることは想像できるのですが、その呼び出し元アドレスがどこに格納されているのか=どこに存在する値を破壊しているのかが不明なので、破壊を行っているコードを特定できない点が問題です。
    関数内のauto変数や関数の引数が破壊されていないということは、呼び出し元アドレスはこれらとは別の領域にあるのでしょうか。
    だとすれば、どのような変数と同じ領域にある=何に対してバッファーオーバーランを起こすと、呼び出し元アドレスが破壊されるのでしょうか?
    誤解されています。バッファオーバーランが発生した瞬間に検出可能であれば、チェック機能などそもそも不要です。Windowsが対象とするプロセッサでは検出できないからこそ、関数終了直前にチェック処理を埋め込んでいるわけです。
    ですので、バッファオーバーランが検出された関数周辺で問題個所を探すのは開発者が行う作業です。
    • 回答としてマーク A_Takada 2018年5月16日 8:37
    2018年5月13日 23:28
  • Azulean様、佐祐理様 ありがとうございます。

    例外発生箇所から「return に失敗」していると考えているのがそもそも間違いで、
    チェックの結果バッファーオーバーランが発見されたので、例外が通知されているということですね。

    バッファサイズやポインタの処理を再確認してみます。

    2018年5月14日 0:10
  • > 例外コード0xc0000409 で異常終了する現象が発生します。
    > (必ず発生する訳ではなく、同じ操作でも発生する場合と発生しない場合があります。)

    現象発生時に表示される例外コードは、毎回 STATUS_STACK_BUFFER_OVERRUN なのでしょうか?
    あくまでも一般論ですが、STATUS_STACK_BUFFER_OVERRUN は STATUS_HEAP_CORRUPTION (0xC0000373) とは異なり特定スタック フレームを破壊するので、大抵の場合再現性があります。
    なので「必ず発生する訳ではなく」という部分が私にはちょっと引っかかるのですが、もし問題現象発生時の例外コードに 0xC0000409 以外が表示されるケースがあるのであれば、別の要因も考えられると思います。
    例えば、ブルースクリーンが発生すると "Bug Check Code" というエラー コードが記録されますが、この "Bug Check Code" はシステム クラッシュの引き金になった「直接的」な原因を示すだけであり、「根本的」な原因を示しているわけではありません。
    ブルースクリーン発生時の "Bug Check Code" が「根本的」な原因を示していない場合、システム クラッシュ発生時の状況に応じて表示される "Bug Check Code" は変化するので、それと同様に異なる例外コードが表示されるケースがあるのであれば、0xc0000409 は「根本的」が原因を示していない可能性も考えられると思います。

    いずれにしてもこの手のメモリ破壊の原因特定には、"Application Verifier" を利用するのがもっとも効率的だと思います。
    ------------------------------------
    Windows SDK ツール:Application Verifier のご紹介
    https://blogs.msdn.microsoft.com/japan_platform_sdkwindows_sdk_support_team_blog/2011/05/29/windows-sdk-application-verifier/
    ------------------------------------

    2018年5月14日 0:33
  • 例外コード、障害オフセットともに毎回同じ値が設定されています。

    デバッグ用に修正すると障害オフセットは変わりますが、同じexeなら同じオフセットが設定されています。

    説明不足でしたが、DBアクセス等を含むので、同じ操作でも流れが異なる場合があるのかもしれません。

    2018年5月14日 1:22
  • お馬鹿さん、A_Takadaさん

    > 特定スタック フレームを破壊するので、大抵の場合再現性があります。なので「必ず発生する訳ではなく」という部分が私にはちょっと引っかかる

    例えば文字列の長さに依って破壊したりしなかったり等あるため、原因次第では100%再現するとは限りません。ですので「引っかかる」必要はありません。もちろん、他の問題も含んでいる可能性は常にありますが。

    > デバッグ用に修正すると障害オフセットは変わりますが、同じexeなら同じオフセットが設定されています。

    先に説明したようにオーバーランが発生した瞬間を捉えての例外ではありません。関数開始時にガード領域を特定パターンで初期化し、関数return直前にガード領域が書き換えられていないかをチェックします。書き換わっていたことを以ってバッファオーバーランと見なしています。ですので、障害オフセットは常にreturn直前を示します。

    • 回答としてマーク A_Takada 2018年5月16日 8:36
    • 回答としてマークされていない A_Takada 2018年5月16日 8:37
    2018年5月14日 2:08
  • > 例えば文字列の長さに依って破壊したりしなかったり等あるため、
    > 原因次第では100%再現するとは限りません。
    > ですので「引っかかる」必要はありません。
    > もちろん、他の問題も含んでいる可能性は常にありますが。

    私もその可能性は考えましたが、質問内容には「同じ操作でも」とあったので、同じ「文字列」で現象に差異が生じている可能性もあるのかなぁ。。。。と思い、先の返信をしました。
    (「DBアクセス」で具体的に何を行っているのかは知りませんが、「同じ操作」であるならば、一般的にそこで使用される「文字列」も同じになるのではないかと。)
    とにかく「例外コード、障害オフセットともに毎回同じ値」であるならば、先の返信でも述べたように "Application Verifier" を使えば原因箇所を特定できると思います。

    2018年5月14日 2:32
  • 佐祐理様、お馬鹿様、ありがとうございます。
    返信が遅くなり申し訳ありません。

    はい、return 前のチェックによる例外throwであると認識しています。

    当該関数をを調べたところ、char[20]に 20バイトの文字列を設定している
    (より正確には15バイトstrcpyして、5バイトをstrcatで連結している) 箇所
    =終端'\0'が他所にはみ出している箇所がありました。
    この領域を拡大した結果、現象が解消しました。

    バッファーオーバーラン例外についても、私の誤解を解消していただき
    問題の解決と併せて非常に助かりました。
    ありがとうございます。

    2018年5月16日 8:36