メモリ内容を確実に消すときはSecureZeroMemoryを使うべき

これはよく知られた問題だ。パスワードなど流出しては困る重要なデータを扱うときは、使い終わった後のメモリ内容を消さないと、そこから流出する可能性がある。そのため以下のようなソースコードにして使い終わったパスワード内容をメモリから消す。

■うまく動かない例
void	Test(void)
{
	char	pszPassword[256];

	//パスワード入力
	::strcpy_s(pszPassword,256 * sizeof(char),"password-string");

	//パスワード表示
	::MessageBoxA(NULL,pszPassword,"",MB_OK);

	//メモリ上からパスワード消去
	::ZeroMemory(&pszPassword,256);
}
プロジェクトファイルをダウンロード


test50_01.gif
上のソースコード自体には間違えた部分はどこにもない。デバッグビルドで動かすときちんとメモリは消されてパスワード流出の危険性は少ない(この例ではMessageBoxを利用してパスワードを表示しているなどなどの理由でバレバレだが、本質的な部分、つまりメモリ内容からの流出の危険性は少ない)。

実際生成される実行ファイルの中を逆アセンブラしてみると、ZeroMemoryが実行されているのがわかる(図はVisual Studio 2005が生成したソースコード付きのアセンブラコード)。

test50_02.gif
しかし、まったく同じソースコードをリリースビルドに切り替えてみると...ZeroMemoryに相当するアセンブラコードが出力されていない。

これは、リリースビルドではデフォルトで最適化オプションが有効になっているためだ。最適化オプションではそれ以下のコード部分でメモリを参照していない変数への代入などの処理を省くように作られている。この場合であればZeroMemory実行後に一度もpszPassword変数を利用する命令がないため、ZeroMemoryは不要な命令と判断し、ビルド時に省略された。


ではどうすればZeroMemoryがきちんと動作するのかというと2つの方法がある。1つはpszPasswordにゼロを代入した後に、その値を読みだすように作ること。しかしこの場合も読み出し方を間違えると、その読み出しも必要ないものだと最適化ルーチンに判断されてZeroMemoryもろとも省略されてしまう恐れがある。もう1つの方法はSecureZeroMemoryを利用する方法だ。

void	Test(void)
{
	char	pszPassword[256];

	//パスワード入力
	::strcpy_s(pszPassword,256 * sizeof(char),"password-string");

	//パスワード表示
	::MessageBoxA(NULL,pszPassword,"",MB_OK);

	//メモリ上からパスワード消去
	::SecureZeroMemory(&pszPassword,256 * sizeof(char));
}
プロジェクトファイルをダウンロード

test50_04.gif
使い方はZeroMemoryと同じなので、単純に置き換えるだけでいい。

このSecureZeroMemoryは名前が示すとおり安全にメモリをクリアするために用意されている。そのためリリースビルドした場合でも左図のアセンブラ出力のように最適化によって省略されることなく命令が実行されて、メモリ内容が消去される。


「重要なデータを格納した変数は必要なくなったらメモリ内容まで消去する」。このような細かいところまできちんと考えてプログラミングしている開発者は少ないと思う。そのためメモリ内容を確実にクリアするためのSecureZeroMemory関数の存在など知らなくてもさほど問題ではない(と、少なくとも私は思う)。
しかしながらリリースビルドに切り替えたとたんにプログラムが動かなくなったときなどは、今回のZeroMemoryのように「最適化によって省略されてしまう命令がある」ということが頭の片隅にあるかどうかで原因究明までの時間に及ぼす影響は大きい。そのため時間があったら最適化について調べておくといいだろう。


カテゴリー「コラム」 のエントリー