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

その他インテル® DPC++/C++ コンパイラー特集

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


2. 0 よりも大きいは 1 を意味しない

CoreCLR プロジェクトから抜粋した以下のコードについて考えてみます。このコードにはエラーが含まれています。PVS-Studio アナライザーは、次の診断を出力します。

V698 Expression ‘memcmp(….) == -1’ is incorrect. This function can return not only the value ‘-1’, but any negative value. Consider using ‘memcmp(….) < 0' instead. (V698 式 'memcmp(....) == -1' は不正です。この関数は、'-1' だけでなく、任意の負の値を返すことができます。代わりに、'memcmp(....) < 0' の使用を検討してください。)

bool operator( )(const GUID& _Key1, const GUID& _Key2) const
  { return memcmp(&_Key1, &_Key2, sizeof(GUID)) == -1; }

説明

memcmp() 関数の説明を見てみましょう。

int memcmp ( const void * ptr1, const void * ptr2, size_t num );

ptr1 で示されるメモリーブロックの最初の num バイトと ptr2 が指す最初の num バイトを比較し、すべて一致する場合は 0 を、そうでない場合はどちらのほうが大きいかを表す非ゼロ値を返します。

戻り値:

  • < 0 - 一致しない最初のバイトの ptr1 の値のほうが ptr2 の値よりも小さい (符号なし char 値として評価した場合)。
  • == 0 – 両方のメモリーブロックの内容が等しい。
  • > 0 – 一致しない最初のバイトの ptr1 の値のほうが ptr2 の値よりも大きい (符号なし char 値として評価した場合)。

ブロックが同じでない場合、関数は 0 よりも大きい値または小さい値を返します。大きいまたは小さいということが重要です。そのような関数の結果を memcmp()strcmp()strncmp() などを使用して定数 1 と -1 と比較することはできません。

興味深いことに、結果を 1/-1 と比較する不正なコードは、プログラマーが期待するように動作するかもしれません。しかし、それは運次第です。関数の動作は、予期せず変わる可能性があります。例えば、コンパイラーを変更したり、プログラマーが memcmp() を新しい方法で最適化したりすると、コードが動作しなくなります。

正しいコード

bool operator( )(const GUID& _Key1, const GUID& _Key2) const
  { return memcmp(&_Key1, &_Key2, sizeof(GUID)) < 0; }

推奨事項

現在、関数が動作する方法に依存しないことです。関数が 0 より小さい値または大きい値を返すとドキュメントに記載されている場合、関数は -10、2、あるいは 1024 を返すことがあります。戻り値が常に -1、0、または 1 だったとしても、それは何の証明にもなりません。

ところで、関数が 1024 のような値を返す可能性があるということは、memcmp() の実行結果を char 型で保存できないことを示しています。これもよくあるエラーで、その影響は重大です。このようなミスは、MySQL*/MariaDB* 5.1.61、5.2.11、5.3.5、5.5.22 よりも古いバージョンで重大な脆弱性の原因となりました。ユーザーが MySQL*/MariaDB* に接続すると、コードはトークン (パスワードとハッシュからの SHA) を評価して、memcmp() 関数の想定される値と比較します。しかし、一部のプラットフォームでは、戻り値が [-128..127] の範囲外になります。その結果、256 分の 1 の確率で、ハッシュ値に関係なく、ハッシュと想定値の比較が常に true を返します。そのため、ハッカーが単純な bash コマンドを利用して、パスワードを知らなくても root 権限で MySQL* サーバーにアクセスできてしまいます。この問題は、’sql/password.c’ ファイルの次のコードが原因でした。

typedef char my_bool;
...
my_bool check(...) {
  return memcmp(...);
}

この問題の詳細は、「MySQL*/MariaDB* のセキュリティーの脆弱性」 (英語) を参照してください。

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

タイトルとURLをコピーしました