nprogram’s blog

気ままに、プログラミングのトピックについて書いていきます

無効パラメータハンドラーとセキュア関数でバッファオーバーラン対策 (C / C++)

はじめに

Microsoft Visual Studio Community 2017を使用して、C++コンソールプロジェクトでプログラムを作成しています。
C++警告レベルはデフォルト(レベル3 (/W3))を使用しています。

セキュア関数を使用しないで、strcpy関数で文字列をコピーした場合

以下のようなstrcpy関数を使用したサンプルコードをビルドしてみます。

#include "stdafx.h"
#include <iostream>

int main()
{
    char name[8];

    printf("First Test Start\r\n");
    strcpy(name, "1234567");
    printf("First Test OK\r\n\r\n");

    printf("Second Test Start\r\n");
    strcpy(name, "12345678");
    printf("Second Test OK\r\n");
    return 0;
}

ビルド結果は以下のとおり。警告レベルがデフォルトでも、エラーが発生してビルドが成功しません。 f:id:nprogram:20171215073504p:plain

セキュア関数(strcpy_s)を使用して、文字列コピーをした場合

セキュア関数strcpy_sを使用した場合は、ビルドが通りました。

コピー1回目は正しく実行されましたが、2回目が失敗しています。

これは、コピー元の文字列の要素数は、NULL文字を含めると9です。
しかし、コピー先の文字列の要素数は、8なので、バッファオーバーランが発生して、アサーションエラーによるメッセージダイアログが表示されます。

#include "stdafx.h"
#include <iostream>

int main()
{
    char name[8];

    printf("First Test Start\r\n");
    strcpy_s(name, _countof(name), "1234567");
    printf("First Test OK\r\n\r\n");

    printf("Second Test Start\r\n");
    strcpy_s(name, _countof(name), "12345678");
    printf("Second Test OK\r\n");
    return 0;
}

f:id:nprogram:20171215074207p:plain

バッファオーバーランが発生すると、アサーションエラーが発生して、以下のような感じに、エラー処理を書いても、通りません。 そこで、無効パラメータハンドラーを使用することによって、エラー処理を通るようにしたいと思います。

 if (strcpy_s(name, _countof(name), "12345678")) {
        // エラー処理
        printf("\r\n\r\nError!! [file : %s] [line : %d] [Second]\r\n\r\n", __FILE__, __LINE__);
    } else 
    {
        printf("Second Test OK\r\n");
    }

無効パラメータハンドラー・セキュア関数(strcpy_s)を用いた文字列コピー

無効パラメータハンドラーmyInvalidParameterHandlerを定義した以下のプログラムを実行してみます。
実行結果を見ると、正しくエラー処理が実行されています。

プログラムの説明をします。まず、自作した無効パラメータハンドラーを登録します。 2回目のstrcpy_s実行時、バッファオーバーランが発生します。
これまでは、エラー通知はデバッグ メッセージ ウィンドウに送られますが、
_CrtSetReportModeで、0(モードなし)を設定することで、デバッグメッセージウィンドウへの通知を無効にできます。
その場合は、エラー通知は、自作した無効パラメータハンドラーに送られます。
無効パラメータハンドラーによる処理が実行された後、プログラム処理は、元の2回目のstrcpy_sの処理に戻ります。

無効パラメータハンドラーを用いることで、プログラムを停止せずに、正しくエラー処理を実行できました。

また、登録した無効パラメータハンドラーの設定は必要な箇所のみにしておき、必要がなくなれば、
登録解除して、デフォルトのエラーハンドラーを使用させるようにしておくことをお勧めします。
その際、_CrtSetReportMode(_CRTDBG_MODE_WNDW, 0);で、エラー通知をもとのデバッグ メッセージ ウィンドウに送る設定に戻してください。

#include "stdafx.h"
#include <iostream>

void myInvalidParameterHandler(const wchar_t* expression,
    const wchar_t* function,
    const wchar_t* file,
    unsigned int line,
    uintptr_t pReserved)
{
    wprintf(L"Invalid parameter detected in function %s."
        L" File: %s Line: %d\n", function, file, line);
    wprintf(L"Expression: %s\n", expression);
}

int main()
{
    char name[8];

    _invalid_parameter_handler oldHandler, newHandler;
    newHandler = myInvalidParameterHandler;

    // 無効パラメータハンドラーを登録する
    oldHandler = _set_invalid_parameter_handler(newHandler);

    // Disable the message box for assertions.   
    _CrtSetReportMode(_CRT_ASSERT, 0);

    printf("First Test Start\r\n");

    if (strcpy_s(name, _countof(name), "1234567")) {
        printf("\r\\r\nnError!! [file : %s] [line : %d] [First]\r\n\r\n", __FILE__, __LINE__);
    }
    else {
        printf("First Test OK\r\n\r\n");
    }


    printf("Second Test Start\r\n");

    if (strcpy_s(name, _countof(name), "12345678")) {
        // エラー処理
        printf("\r\n\r\nError!! [file : %s] [line : %d] [Second]\r\n\r\n", __FILE__, __LINE__);
    } else 
    {
        printf("Second Test OK\r\n");
    }

    // Enable the message box for assertions.   
    _CrtSetReportMode(_CRTDBG_MODE_WNDW, 0);

    // 無効パラメータハンドラーの登録を解除する
    oldHandler = _set_invalid_parameter_handler(oldHandler);

    return 0;
}

https://msdn.microsoft.com/ja-jp/library/a9yf33zb.aspx

以下の情報を参考にしました