プログラミング、リファクタリング、そしてすべてにおける究極の疑問: No. 17

同カテゴリーの次の記事

プログラミング、リファクタリング、そしてすべてにおける究極の疑問: No. 18

この記事は、インテル® デベロッパー・ゾーンに公開されている「The Ultimate Question of Programming, Refactoring, and Everything」の日本語参考訳です。


17. プライベート・データのクリアには専用の関数を使用する

Apache HTTP Server* プロジェクトから抜粋した以下のコードについて考えてみます。このエラーは、次の PVS-Studio 診断によって検出されます。

V597 The compiler could delete the ‘memset’ function call, which is used to flush ‘x’ buffer. The RtlSecureZeroMemory() function should be used to erase the private data. (V597 コンパイラーは、’x’ バッファーのフラッシュに使用される ‘memset’ 呼び出しを削除できます。プライベート・データの消去には、RtlSecureZeroMemory() 関数を使用すべきです。)

static void MD4Transform(
  apr_uint32_t state[4], const unsigned char block[64])
{
  apr_uint32_t a = state[0], b = state[1],
               c = state[2], d = state[3],
               x[APR_MD4_DIGESTSIZE];  
  ....
  /* Zeroize sensitive information. */
  memset(x, 0, sizeof(x));
}

説明

このコードでは、プログラマーはプライベート・データの消去に memset() 関数呼び出しを使用しています。しかし、この方法では実際にデータが消去されないため、これは最良の方法ではありません。正確には、消去されるかどうかはコンパイラーやその設定などに依存します。

このコードをコンパイラーの視点から見てみましょう。コンパイラーは、コードをできるだけ高速に実行できるように、さまざまな最適化を行います。その 1 つは、C/C++ 言語の観点から余分なプログラムの動作に影響しない関数呼び出しを削除します。上記のコード例では、memset() 関数がこれに当たります。この関数は、’x’ バッファーを変更しますが、このバッファーは以降の処理で使用されません。つまり、memset() 関数呼び出しは削除可能であり、そうすべきです。

重要! これから述べることは、実際のコンパイラーの動作であって、理論モデルではありません。上記の例では、コンパイラーは memset() 関数呼び出しを削除します。これは、いくつかの実験をすることで確認できます。この問題の詳細と例は、次の記事を参照してください。

  1. セキュリティー、セキュリティー! しかし、それをテストしていますか? (英語)
  2. プライベート・データの安全な消去 (英語)
  3. V597 (英語) The compiler could delete the ‘memset’ function call, which is used to flush ‘Foo’ buffer. The RtlSecureZeroMemory() function should be used to erase the private data. (V597 コンパイラーは、’Foo’ バッファーのフラッシュに使用される ‘memset’ 呼び出しを削除できます。プライベート・データの消去には、RtlSecureZeroMemory() 関数を使用すべきです。)
  4. ゼロにして忘れる — C でメモリーをゼロにする場合の警告 (英語) (この記事に関する議論 (英語) も参照)
  5. MSC06-C. Beware of compiler optimizations. (MSC06-C. コンパイラーの最適化に気を付ける)

memset() 呼び出しの削除に関するこのエラーは、追跡が非常に困難なため特に厄介です。デバッガーを使用する場合、プログラマーはおそらく最適化前のまだ関数呼び出しが含まれているコードで作業します。しかし、このエラーは、最適化されたバージョンのビルド時に生成されるアセンブラー・リストを確認して初めて見つけることができます。

一部のプログラマーは、これはコンパイラーのバグであり、memset() のような重要な関数の呼び出しを削除すべきではないと考えています。しかし、それは違います。この関数がほかの関数よりも重要であるということはなく、コンパイラーはその呼び出しを最適化する権利があります。事実、そのようなコードが実際に余分である場合があります。

正しいコード

memset_s(x, sizeof(x), 0, sizeof(x));

または

RtlSecureZeroMemory(x, sizeof(x));

推奨事項

コンパイラーが最適化で削除できない特別なメモリー消去関数を使用すべきです。

例えば、Visual Studio* の RtlSecureZeroMemory (英語) 関数や C11 の memset_s (英語) があります。必要に応じて、独自の安全な関数を作成することもできます。インターネットには、さまざまな例があります。そのいくつかを以下に示します。

バージョン No.1 (英語):

errno_t memset_s(void *v, rsize_t smax, int c, rsize_t n) {
  if (v == NULL) return EINVAL;
  if (smax > RSIZE_MAX) return EINVAL;
  if (n > smax) return EINVAL;
  volatile unsigned char *p = v;
  while (smax-- && n--) {
    *p++ = c;
  }
  return 0;
}

バージョン No.2 (英語):

void secure_zero(void *s, size_t n)
{
    volatile char *p = s;
    while (n--) *p++ = 0;
}

一部のプログラマーは、さらに配列に疑似乱数値を設定する関数を実装して、これらの関数を異なる時点で実行することで時間計測攻撃からの保護を確実にしています。これらの関数の実装についても、インターネットで見つけることができます。

コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください。

関連記事

  • Parallel Universe マガジンParallel Universe マガジン Parallel Universe へようこそ。 米国インテル社が四半期に一度オンラインで公開しているオンラインマガジンです。インテルの技術者によるテクノロジーの解説や、最新ツールの紹介など、並列化に関する記事を毎号掲載しています。第1号からのバックナンバーを PDF 形式で用意しました、ぜひご覧ください。 12 […]
  • 比較関数の罠比較関数の罠 この記事は、インテル® デベロッパー・ゾーンに公開されている「The Evil within the Comparison Functions」の日本語参考訳です。 この記事の PDF […]
  • マルチスレッド開発ガイド: 4.6 インテル® Parallel Composer を利用して並列コードを開発するマルチスレッド開発ガイド: 4.6 インテル® Parallel Composer を利用して並列コードを開発する コードの並列化にはさまざまな手法があります。この記事では、インテル® Parallel Composer で利用可能な手法の概要を説明し、各手法の主な長所を比較します。インテル® Parallel Composer は Windows* 上の C/C++ を使用した開発のみを対象としていますが、これらの手法の多くは Fortran や […]
  • 並列プログラミングにおけるロックの効率的な使用並列プログラミングにおけるロックの効率的な使用 この記事は、インテル® ソフトウェア・ネットワークに掲載されている「Using Locks Effectively in Parallel Programming […]
  • インテル® AVX 命令を使用した complex float データ型の IIR フィルターの実装インテル® AVX 命令を使用した complex float データ型の IIR フィルターの実装 この記事は、インテル® ソフトウェア・ネットワークに掲載されている「Intel® AVX Realization Of IIR Filter For Complex Float Data」の日本語参考訳です。 はじめに この記事では、インテル® AVX の SIMD (Single Instruction […]