◇ Secure ZeroMemoryとは?

ブロックチェーンでは「鍵」を扱うため Secure ZeroMemory・メモリロック・メモリプロテクト・Secure Allocatorの実装が必須です。そこでSecure ZeroMemoryを扱います。

◇ メモリアロケータとは?

メモリの動的確保および解放を行う仕組みをメモリアロケータと呼びます。その確保されたメモリを解放したときその内容をゼロで塗り潰す作業がSecure ZeroMemoryです。まずメモリの解放はテーブル側を操作して空きサイズを結合するだけの作業となっております。わざわざ塗りつぶす作業はしません。上書きで使用可能ですから何もせず他に使い回します。よって実際に使用されていたメモリは解放しても中身はそのままになっています。その内容が鍵だったら……解放後であってもプロセス経由のメモリ読み出しで取り出せてしまうのです。

◇ ではメモリ解放前に::memsetするだけでは?

::memsetでメモリの内容にゼロを埋めてあげれば問題ないはずでした。しかし、::memsetをするだけではコンパイラに取り除かれてしまいます。なぜなら解放前に::memsetする作用は一般的に必要がないためです。コンパイラは速度面を重要視します。必要がないコードは確実に取り除かれます。よってこのコンパイラの最適化により::memsetが取り除かれて未作用になります。

◇ そのためコンパイラ最適化で取り除かれない手法

1: OpenSSL
::OPENSSL_cleanse()という関数が用意されております。
埋め込む数値をグローバル変数に置き乱雑化させる仕組みで実装されております。
グローバル変数から変化させる数値の埋め込みになるためコンパイラの最適化では除去されません。

2: ::SecureZeroMemory()
Windowsには専用のAPI(SecureZeroMemory)があります。WindowsならAPIを優先して使います。それが確実です。

3: volatileで::memsetを別関数で再定義
ハッシュ関数Blake2の実装で興味深い実装方法がありました。
※ 以下のように別に定義されたmemset_vを通常通りに呼び出すだけです。
関数自体をvolatileの関数ポインタとして再定義する手法みたいです。シンプルですね。
static void *(*const volatile memset_v)(void *, int, size_t) = &memset;
memset_v(v, 0, n);

◇ サンプルコード(参照: Blake2ライブラリより)

inline void secure_memzero(void *v, size_t n)
{
    static void *(*const volatile memset_v)(void *, int, size_t) = &memset;
    memset_v(v, 0, n);
}