yohhoyの日記

技術的メモをしていきたい日記

パスワードとmemset関数

C言語プログラム上で高機密性情報(パスワード文字列など)を消去するケースで、memset関数の単純利用では機密情報がメモリ上に残存してしまい、セキュリティ上の脆弱性につながる可能性がある。

void secure_operation()
{
  // パスワード文字列を取得
  char passwd[128];
  get_password(passwd, sizeof(passwd));
  //...

  // メモリ上の高機密データを消去...
  memset(passwd, 0, sizeof(passwd));  // ??
}

上記コードでは、パスワード文字列が格納された変数passwdを使用後にゼロクリアしているが、コンパイル時の最適化によりmemset呼び出しが削除される可能性がある。この(プログラマの意図に反する)最適化は、C言語の言語仕様上も許容されるコンパイラの振る舞いとなっている。*1

C11

C11標準ライブラリではAnnex.K Bounds-checking interfacesの一部として、セキュリティ用途に適したmemset_s関数が提供される。ただしAnnex.K対応はオプションとなっているため、必ずしも全てのC11準拠処理系で利用できるとは限らない。

#define __STDC_WANT_LIB_EXT1__ 1
#include <string.h>

void secure_operation()
{
  char passwd[128];
  get_password(passwd, sizeof(passwd));
  //...
  // メモリ上の高機密データを消去
  memset_s(passwd, sizeof(passwd), 0, sizeof(passwd));  // OK
}

C11(N1570) K.3.7.4.1/p1, 4より引用(下線部は強調)。*2

Synopsis

#define __STDC_WANT_LIB_EXT1__ 1
#include <string.h>
errno_t memset_s(void *s, rsize_t smax, int c, rsize_t n)

Description
The memset_s function copies the value of c (converted to an unsigned char) into each of the first n characters of the object pointed to by s. Unlike memset, any call to the memset_s function shall be evaluated strictly according to the rules of the abstract machine as described in (5.1.2.3). That is, any call to the memset_s function shall assume that the memory indicated by s and n may be accessible in the future and thus must contain the values indicated by c.

Windows

Windows APIではゼロクリアを行うZeroMemory関数(memset相当)の他に、セキュリティ用途に適したSecureZeroMemory関数(memset_s相当)が提供される。

#include <windows.h>

void secure_operation()
{
  char passwd[128];
  get_password(passwd, sizeof(passwd));
  //...
  // メモリ上の高機密データを消去
  SecureZeroMemory(passwd, sizeof(passwd));  // OK
}

Remarks
Use this function instead of ZeroMemory when you want to ensure that your data will be overwritten promptly, as some C++ compilers can optimize a call to ZeroMemory by removing it entirely.

SecureZeroMemory function

関連URL

*1:5.1.2.3/p3: "In the abstract machine, all expressions are evaluated as specified by the semantics. Anactual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced (including any caused by calling a function or accessing a volatile object)."

*2:errno_tはint型、rsize_tはsize_t型に等しい。rsize_t型は「マクロ定数RSIZE_MAXを超えないサイズ」を表す符号無し整数型として利用され、C標準ライブラリAnnex.K拡張関数では実行時制約違反のチェックが行われる。

*3:日本語版(JPCERT)では、本家CERTの更新内容が反映されていないことがあるので注意。2014年3月現在、JPCERT版にはC11への言及なし。