FIO47-C. 書式指定文字列を正しく使う
書式指定出力関数 (fprintf() および関連の関数) は引数として指定された書式指定文字列にしたがって、残りの引数の値を変換、フォーマット、出力する。C 標準の 7.21.6.1 パラグラフ3 では次のように規定されている[ISO/IEC 9899:2011]。
書式は, 初期シフト状態で始まり初期シフト状態で終わる多バイト文字の並びでなければならない。書式は, 0個以上の指令から成る。指令は, そのまま出力ストリームにコピーされる(
%以外の)通常の多バイト文字, 及び変換指定とする。各変換指定は, 後に続く0個以上の実引数を取り出し, 適用可能であれば対応する変換指定子に従って変換し, その結果を出力ストリームに書き込む。
変換指定は % 文字で始まり以下の要素をこの順に並べたものである。
- 変換指定の意味を修飾する0個以上のフラグ (順序は任意)
- 最小フィールド幅 (省略可能)
- 特定の変換指定子に対して出力結果の最小桁数を指定する精度 (省略可能)
- 実引数の大きさを指定する長さ修飾子 (省略可能)
- 適用する変換の種類を指定する 変換指定子 の文字
書式指定文字列を作成する際には、次のような間違いを起こしやすい。
- 書式指定文字列に対する引数の数が少ない、あるいは多すぎる
- 無効な変換指定子の使用
- 変換指定子に対する不適切なフラグ文字の使用
- 変換指定子に対する不適切な長さ修飾子の使用
- 実引数と変換指定子の型の不一致
- 幅や精度の指定に対する
int以外の型の実引数の使用
次の表に、様々な変換指定の組み合わせを示す。最初のカラムは1個以上の変換指定文字である。その次の4つのカラムは、変換指定文字と組み合わせるフラグを示す(アポストロフィ「'」、「-」、「+」、空白文字、「#」、「0」)。その次の8個のカラムは、変換指定と組み合わせる長さ修飾子を示す(h、hh、l、ll、j、z、t、L)。適切な組み合わせには型名を記載している。変換指定に対応する実引数はこの型の値として解釈される。例えば、%hd に対応する実引数は short として解釈されるので、d と h の交差するセルには short と記載している。最後の(右端の)カラムには、元の変換指定子で想定されている実引数の型を示す。有効な、そして意味のある組み合わせは記号
で示している(長さ修飾子のカラムについては既に書いたとおり)。有効な組み合わせではあるが、とくになんの効果もないものについては N/E と記載している。記号
で示す組み合わせ、表に示されていない変換指定、想定されていない型の実引数を使用した場合は未定義の動作となる (未定義の動作 153、155、157、158、161、162 を参照)。
|
変換指定子 |
|
|
|
|
|
|
|
|
|
|
|
|
引数の型 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
符号付き整数 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
符号無し整数 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
符号無し整数 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
符号無し整数 |
|
|
|
|
|
|
|
|
N/E |
N/E |
|
|
|
|
|
|
|
|
|
|
|
|
|
N/E |
N/E |
|
|
|
|
|
|
|
|
|
|
|
|
|
N/E |
N/E |
|
|
|
|
|
|
|
|
|
|
|
|
|
N/E |
N/E |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NTWS |
|
|
|
|
|
NTBS あるいは NTWS |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
整数へのポインタ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NTWS |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
None |
SPACE: 空白文字 (" ")
N/E: No effect
NTBS: null 終端文字列を指す char* 引数
NTWS: null 終端ワイド文字列を指す wchar_t* 引数
XSI: ISO/IEC 9945-2003 XSI 拡張
書式指定入力関数 (fscanf() および関連の関数) についても、同様の書式指定文字列が使われる。書式指定文字列と実引数との間の制約についても同様である。
書式指定入出力関数の引数として、未知のあるいは無効な書式指定や、フラグ文字、精度、長さ修飾子、変換指定子の無効な組み合わせを与えないこと。また、書式指定文字列中の変換指定子に対応しない型の実引数や変換指定子と異なる数の実引数を与えないこと。
通常、書式指定文字列は呼び出し側で指定される文字列リテラルであるが、必ずそうでなければならないというわけではない。注意すべきは、書式指定文字列のなかに信頼できない値を混ぜるべきではないということである(「FIO30-C. ユーザからの入力を使って書式指定文字列を組み立てない」を参照)。
違反コード
実引数と変換指定の間の不一致が原因で未定義の動作が引き起こされることがある。多くのコンパイラは、書式付き出力関数の呼び出しにおける型の不一致を診断する機能を持っている。次のコード例では、実引数 error_type が d 変換指定子ではなく s 変換指定子に対応し、error_msg が s 変換指定子ではなく d 変換指定子に対応する形で printf() が呼び出されている。このようなコードは未定義の動作を引き起こす。考えられる挙動としては、printf() 関数が実引数 error_type をポインタとして解釈し、error_type の値を先頭のメモリアドレスとして文字列を読み込もうとすることだ。この場合、アクセス違反になるだろう。
#include <stdio.h>
void func(void) {
const char *error_msg = "Resource not available to user.";
int error_type = 3;
/* ... */
printf("Error (type %s): %d\n", error_type, error_msg);
/* ... */
}
適合コード
次のコードでは、printf() 関数に渡す実引数を変換指定と正しく対応させている。
#include <stdio.h>
void func(void) {
const char *error_msg = "Resource not available to user.";
int error_type = 3;
/* ... */
printf("Error (type %d): %s\n", error_type, error_msg);
/* ... */
}
リスク評価
書式指定文字列の不適切な使用は、メモリ内容の破壊やプログラムの異常終了につながる。
|
ルール |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
|---|---|---|---|---|---|
|
FIO47-C |
高 |
低 |
中 |
P6 |
L2 |
自動検出(最新の情報はこちら)
|
ツール |
バージョン |
チェッカー |
説明 |
|---|---|---|---|
| 4.3.5 |
|
-Wformat フラグを使用することでこのルールの違反を検出できる |
|
| 9.1 |
SV.FMT_STR |
|
|
|
LDRA tool suite |
8.5.4 |
486 S |
実装済み |
| PRQA QA-C | 8.1 |
0179 (U) |
一部実装済み |
関連するガイドライン
| CERT C++ Secure Coding Standard | FIO00-CPP. Take care when creating format strings |
| ISO/IEC TS 17961:2013 | Using invalid format strings [invfmtstr] |
| MITRE CWE | CWE-686, Function Call with Incorrect Argument Type |
参考資料
| [ISO/IEC 9899:2011] | 7.21.6.1, "The fprintf Function" |
翻訳元
これは以下のページを翻訳したものです。
FIO47-C. Use valid format strings (revision 71)



