CRTのセキュリティ強化(strcpy_s,sprintf_s等)について

さて、仕事でVC++の業務に携わって、疑問に思ったので、調べた結果を纏めてみた。

C言語でのお馴染みのstrcpy関数だけど、以下のようなソースをVC++コンパイルすると警告になる。

#include "stdafx.h"
#include

int _tmain(int argc, _TCHAR* argv)
{
char str[20];
str[0] = '\0';
char src
= "Hello World!";

strcpy(str, src);
printf("%s\n", str);
return 0;
}

警告の内容は以下の通り。

warning C4996: 'strcpy': This function or variable may be unsafe. Consider using strcpy_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.

で、ヘルプを見ると、CRTのセキュリティ強化にたどり着く。
strcpy関数は非推奨なので、strcpy_s関数を使うか警告を無視する定義を付けろと。。。

errno_t strcpy_s(char *strDestination, size_t numberOfElements, const char *strSource);
numberOfElements コピー先の文字列バッファのサイズ。

第二引数にバッファサイズを指定するようで、以下のソースに修正すると、警告は出なくなる。

#include "stdafx.h"
#include

int _tmain(int argc, _TCHAR* argv)
{
char str[20];
str[0] = '\0';
int ret = -1;
char src
= "Hello World!";

ret = strcpy_s(str, sizeof(str), src);
printf("%d:%s\n", ret, str);
return 0;
}

さて、ここで、実際にコピー元の文字列長がバッファ長を超えたらどうなるの?
ってことで、試してみる。

#include "stdafx.h"
#include

int _tmain(int argc, _TCHAR* argv)
{
char str[20];
str[0] = '\0';
char src
= "Hello World!34567890";

strcpy(str, src);
printf("%s\n", str);
return 0;
}

strcpy関数は、当然のことながら、ランタイムエラーになる。
(呼出し後、どこかのタイミングで。。。今回は実行終了時だった)

#include "stdafx.h"
#include

int _tmain(int argc, _TCHAR* argv)
{
char str[20];
str[0] = '\0';
int ret = -1;
char src
= "Hello World!34567890";

ret = strcpy_s(str, sizeof(str), src);
printf("%d:%s\n", ret, str);
return 0;
}

strcpy_s関数は。。。Debug版ではアサーション???

Release版ではランタイムエラーだ。

いずれも、その関数呼び出し時にエラーを検知した。

あれ?失敗したら、-1で変えるとかじゃないのか???
と、ヘルプを読み直すと、

また、セキュリティが強化された関数は、セキュリティ エラーを防止したり訂正したりするのではなく、エラー発生時にそのエラーをキャッチします。これらの関数はエラー条件をチェックし、エラーが発生した場合に、エラー ハンドラを呼び出します (「パラメータの検証」を参照)。

はぁ、チェックまでしてるけど、そういうことみたいだ。
(何だ、この関数。。。何の為に用意したんだろ?)

*1結局、アサーションや実行時エラーが出るんじゃ、面白くないので、さらに調べる。
ここを真似して、ソースを書き換えた。

#include

class CrtUtil
{
public:
CrtUtil();
virtual ~CrtUtil();

static int StrCpyS(char* dest, int len, char*src);

private:
static void myInvalidParameterHandler(const wchar_t* expression,
const wchar_t* function,
const wchar_t* file,
unsigned int line,
uintptr_t pReserved);

static bool illegal_operation_flag;
static void SetIllegalOperationFlag(bool flag);
static bool GetIllegalOperationFlag();
};

#include "CrtUtil.h"
#include

bool CrtUtil::illegal_operation_flag = false;

CrtUtil::CrtUtil()
{
}
CrtUtil::~CrtUtil()
{
}

void CrtUtil::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);
SetIllegalOperationFlag(true);
}

void CrtUtil::SetIllegalOperationFlag(bool flag)
{
illegal_operation_flag = flag;
}
bool CrtUtil::GetIllegalOperationFlag()
{
return illegal_operation_flag;
}

int CrtUtil::StrCpyS(char* dest, int len, char*src)
{
_invalid_parameter_handler oldHandler, newHandler;

SetIllegalOperationFlag(false);
newHandler = myInvalidParameterHandler;
oldHandler = _set_invalid_parameter_handler(newHandler);
_CrtSetReportMode(_CRT_ASSERT, 0);

int ret = strcpy_s(dest, len, src);
if (GetIllegalOperationFlag()) {
ret = -1;
SetIllegalOperationFlag(false);
}
_CrtSetReportMode(_CRT_ASSERT, 1);
_set_invalid_parameter_handler(oldHandler);
return ret;
}

#include "stdafx.h"
#include
#include "CrtUtil.h"

int _tmain(int argc, _TCHAR* argv)
{
char str[20];
str[0] = '\0';
int ret = -1;
char src
= "Hello World!34567890";

ret = CrtUtil::StrCpyS(str, (int)sizeof(str), src);
printf("%d:%s\n", ret, str);
strcpy_s(str, (int)sizeof(str), src);
return 0;
}

<実行結果>

-1:

ほぉ。ちゃんと-1で返ってきて、直後のstrcpy_s関数でアサーション出た。
で、まだ、続きがあるんだけど、行が長いので、分割することにして、次回へ。

*1:*2012.02.08記事追記