この記事は、インテル® デベロッパー・ゾーンに公開されている「The Ultimate Question of Programming, Refactoring, and Everything」の日本語参考訳です。
32. 危険な printf
TortoiseGIT プロジェクトから抜粋した以下のコードについて考えてみます。このコードにはエラーが含まれています。PVS-Studio アナライザーは、次の診断を出力します。
V618 It’s dangerous to call the ‘printf’ function in such a manner, as the line being passed could contain format specification. The example of the safe code: printf(“%s”, str); (V618 渡される行に書式設定を含めることができるため、このような方法で ‘printf’ 関数を呼び出すことは危険です。安全なコードの例: printf(“%s”, str);)
BOOL CPOFile::ParseFile(....) { .... printf(File.getloc().name().c_str()); .... }
説明
文字列を出力したり、ファイルへ書き込む場合、多くのプログラマーは次のようなコードを記述します。
printf(str); fprintf(file, str);
これは非常に危険であることを忘れてはいけません。何らかの形で書式指定子が文字列内に入ってしまうと、予期しない結果になります。
オリジナルのコード例に戻りましょう。ファイル名が “file%s%i%s.txt” の場合、プログラムはクラッシュするか、無意味なものを出力する可能性があります。しかし、それは問題の半分に過ぎません。実際、このような関数呼び出しは脆弱性につながります。利用することで、プログラムを攻撃できます。特別な方法で文字列を準備して、メモリーに格納されているプライベート・データを出力することが可能です。
この脆弱性に関する詳細は、こちらの記事 (英語) を参照してください。興味深い内容なのでお読みになると良いでしょう。理論的な根拠だけでなく、実例も示されています。
正しいコード
printf("%s", File.getloc().name().c_str());
推奨事項
printf() のような関数は、多くのセキュリティー関連の問題を引き起こす可能性があります。それらは使用せずに、より現代的なものに切り替えたほうが良いでしょう。例えば、boost::format や std::stringstream は非常に便利です。
一般に、printf()、sprintf()、fprintf() などの関数の不注意な使用は、プログラムの不正な動作を引き起こすだけでなく、第三者が利用可能な潜在的な脆弱性につながる可能性があります。
コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください。