strsafe.h : C 言語での安全な文字列処理

Michael Howard
Secure Windows Initiative
Microsoft Corporation

June 2002
日本語版最終更新日 2002 年 10 月 8 日

要約 : C プログラミング言語でより安全に文字列を処理する関数セットである strsafe.h 関数を使って C コードの安全性を確保します。

Microsoft® Windows® Security を推し進める間に、テスター、プログラム マネージャ、プログラマのグループでは C プログラミング言語のための安全な文字列処理関数のセットを定義し、作成することを決めました。目的は、安全に使用できる関数セットを Windows やその他のマイクロソフト製品の開発者に提供することでした。

今日の油断ならない環境では単に既存の C ランタイム関数を使うだけでは不十分です。現在の関数には、戻り値やパラメータに一貫性がなく、切り詰めの誤差があり、そして高度な機能が不足しています。率直に言って、既存の関数を用いて、バッファ オーバーランを伴うコードを作成するのはとても簡単なことです。

MFC (CString)、ATL (CComBSTR)、STL (string)、その他の文字列操作に優れたクラス ライブラリには、C++ 開発者が使用できるクラスが多数あります。現在でも多数の C コードが存在するのに、多くの人は C++ を 「優れた C」 として使用し、クラスを使用しません。

strsafe を使用してみてください (「Using the Strsafe.h Functions」(英語) を参照)。ヘッダー ファイルとオプションのライブラリで構成されている strsafe は、新しいバージョンの Platform SDK に含まれています。ソース コードで必要な操作は、たった 1 行のコードを追加するだけです。

#include "strsafe.h"

このようにいたって簡単です。

ライブラリ実装の使用はあくまでもオプションであることを覚えておいてください。

strsafe をデザインする際に必要な主な条件は次のとおりです。

  • 常に文字列は NULL で終了すること
  • 常にターゲット バッファ サイズを使用すること
  • 常に矛盾しないリターン コードを返すこと (HRESULT)
  • 32 ビットおよび 64 ビット環境のサポート
  • 柔軟性

既存の C ランタイム文字列処理関数の多くがセキュリティ エラーの影響を受けやすいのは一貫性に欠けるためで、この問題は strsafe の高度な一貫性によって解決されるものと考えられます。ただし、strsafe は万能薬ではありません。新しい関数を単に使用するだけでは、安全かつを強力なコードを作成することはできないでしょう。strsafe をうまく活用するために、その使用を良く考慮する必要があります。

これは、現在の C ランタイム関数を使った C コードの例です。

void UnsafeFunc(LPTSTR szPath,DWORD cchPath) {
   TCHAR szCWD[MAX_PATH];

   GetCurrentDirectory(ARRAYSIZE(szCWD), szCWD);
   strncpy(szPath, szCWD, cchPath);
   strncat(szPath, TEXT("\\"), cchPath);
   strncat(szPath, TEXT("desktop.ini"),cchPath);
}

これはバグを含む不可解なコードです。戻り値はチェックされず、cchPath は strncat への呼び出しに不正に使用され (最大値は、バッファの合計サイズではなく、ターゲット バッファの空き領域である必要があります。)、バッファ オーバーランを起こすきっかけになってしまいます。しかし、この手のコードはとても一般的なのです。

同じコードで strsafe を使用すると次のようになります。

bool SaferFunc(LPTSTR szPath,DWORD cchPath) {
   TCHAR szCWD[MAX_PATH];

   if (GetCurrentDirectory(ARRAYSIZE(szCWD), szCWD)            &&
      SUCCEEDED(StringCchCopy(szPath, cchPath, szCWD))     &&
      SUCCEEDED(StringCchCat(szPath, cchPath, TEXT("\\"))) &&
      SUCCEEDED(StringCchCat(szPath, cchPath, TEXT("desktop.ini")))) {
         return true;
   }

   return false;
}

このコードはすべての戻り値をチェックし、常に同じターゲット バッファ サイズを渡します。次のような高度な機能が必要な場合は、strsafe の Ex バージョン (extended version) を使用します。

  • 現在のターゲット バッファ エンドを確認する場合
  • ターゲット バッファの中に残っている空き領域の量を確認する場合
  • 特定の文字でバッファの空き領域を埋める場合
  • 文字列処理関数が失敗したときに、特定する値で文字列を埋める場合
  • 失敗時にターゲット バッファを NULL に設定する場合

では、パフォーマンスはどうでしょう。良い点は、コードの中ではパフォーマンスの違いには気が付かないということでしょう。1.8 GHz のコンピュータ上で、複数の C ランタイム文字列連結関数、複数の strsafe 関数、複数の strsafe Ex 関数を 1000 万回 (本当に 10,000,000 回です) 実行したところ、次のような統計を得ました。

  • C ランタイム - 7.3 秒
  • strsafe - 8.3 秒
  • strsafe (Ex) - 11.1 秒

このテストでは、失敗時の strsafe 関数の Ex バージョンは NULL を、そして 0xFE をフィル バイトに設定します。

DWORD dwFlags = STRSAFE_NULL_ON_FAILURE | STRSAFE_FILL_BYTE(0xFE);

フィル バイトの設定には時間がかかります。失敗時に、フラグを NULL に設定するだけの場合でも、 Ex バージョンでない strsafe 関数と同じ時間を要します。

パフォーマンスのインパクトは大きくありません。なぜなら、何百万回も文字列操作関数を大きな混在物とした呼び出しをする多くのコードがあると疑っているからです。

既定では、strsafe はすべての C ランタイム文字列処理関数を #undef し、エラーは、代わりに使用する strsafe 関数によって通知されるでしょう。

詳細については、「Using the Strsafe.h Functions」(英語) を参照してください。

Michael Howard は、Microsoft の Secure Windows Initiative グループの Security Program Manager であり、「プログラマのためのセキュリティ対策テクニック (原題 Writing Secure Code)」の共著者でもあります。また、「Designing Secure Web-based for Applications for Windows 2000」の著者です。彼の人生における興味は、人々がセキュアなシステムの設計、構築、テストおよびドキュメント化を行えるように手助けをすることにあります。お気に入りの台詞は、「ある人にとっての機能は、別の人にとっての悪用の手段となる」というものです。