JPCERT コーディネーションセンター

FIO47-C. 書式指定文字列を正しく使う

書式指定出力関数 (fprintf() および関連の関数) は引数として指定された書式指定文字列にしたがって、残りの引数の値を変換、フォーマット、出力する。C 標準の 7.21.6.1 パラグラフ3 では次のように規定されている[ISO/IEC 9899:2011]。

書式は, 初期シフト状態で始まり初期シフト状態で終わる多バイト文字の並びでなければならない。書式は, 0個以上の指令から成る。指令は, そのまま出力ストリームにコピーされる(%以外の)通常の多バイト文字, 及び変換指定とする。各変換指定は, 後に続く0個以上の実引数を取り出し, 適用可能であれば対応する変換指定子に従って変換し, その結果を出力ストリームに書き込む。

変換指定は % 文字で始まり以下の要素をこの順に並べたものである。

書式指定文字列を作成する際には、次のような間違いを起こしやすい。

次の表に、様々な変換指定の組み合わせを示す。最初のカラムは1個以上の変換指定文字である。その次の4つのカラムは、変換指定文字と組み合わせるフラグを示す(アポストロフィ「'」、「-」、「+」、空白文字、「#」、「0」)。その次の8個のカラムは、変換指定と組み合わせる長さ修飾子を示す(hhhllljztL)。適切な組み合わせには型名を記載している。変換指定に対応する実引数はこの型の値として解釈される。例えば、%hd に対応する実引数は short として解釈されるので、dh の交差するセルには short と記載している。最後の(右端の)カラムには、元の変換指定子で想定されている実引数の型を示す。有効な、そして意味のある組み合わせは記号 (tick) で示している(長さ修飾子のカラムについては既に書いたとおり)。有効な組み合わせではあるが、とくになんの効果もないものについては N/E と記載している。記号 (error) で示す組み合わせ、表に示されていない変換指定、想定されていない型の実引数を使用した場合は未定義の動作となる (未定義の動作 153、155、157、158、161、162 を参照)。

変換指定子

' XSI

-
+
空白文字


#


0


h


hh


l


ll


j


z


t


L

引数の型

d, i

(tick)

(tick)

(error)

(tick)

short

signed char

long

long long

intmax_t

size_t

ptrdiff_t

(error)

符号付き整数

o

(error)

(tick)

(tick)

(tick)

unsigned short

unsigned char

unsigned long

unsigned long long

uintmax_t

size_t

ptrdiff_t

(error)

符号無し整数

u

(tick)

(tick)

(error)

(tick)

unsigned short

unsigned  char

unsigned long

unsigned long long

uintmax_t

size_t

ptrdiff_t

(error)

符号無し整数

x, X

(error)

(tick)

(tick)

(tick)

unsigned short

unsigned char

unsigned long

unsigned long long

uintmax_t

size_t

ptrdiff_t

(error)

符号無し整数

f, F

(tick)

(tick)

(tick)

(tick)

(error)

(error)

N/E

N/E

(error)

(error)

(error)

long double

double あるいは long double

e, E

(error)

(tick)

(tick)

(tick)

(error)

(error)

N/E

N/E

(error)

(error)

(error)

long double

double あるいは long double

g, G

(tick)

(tick)

(tick)

(tick)

(error)

(error)

N/E

N/E

(error)

(error)

(error)

long double

double あるいは long double

a, A

(tick)

(tick)

(tick)

(tick)

(error)

(error)

N/E

N/E

(error)

(error)

(error)

long double

double あるいは long double

c

(error)

(tick)

(error)

(error)

(error)

(error)

wint_t

(error)

(error)

(error)

(error)

(error)

int あるいは wint_t

s

(error)

(tick)

(error)

(error)

(error)

(error)

NTWS

(error)

(error)

(error)

(error)

(error)

NTBS あるいは NTWS

p

(error)

(tick)

(error)

(error)

(error)

(error)

(error)

(error)

(error)

(error)

(error)

(error)

void*

n

(error)

(tick)

(error)

(error)

short*

char*

long*

long long*

intmax_t*

size_t*

ptrdiff_t*

(error)

整数へのポインタ

C XSI

(error)

(tick)

(error)

(error)

(error)

(error)

(error)

(error)

(error)

(error)

(error)

(error)

wint_t

S XSI

(error)

(tick)

(error)

(error)

(error)

(error)

(error)

(error)

(error)

(error)

(error)

(error)

NTWS

%

(error)

(tick)

(error)

(error)

(error)

(error)

(error)

(error)

(error)

(error)

(error)

(error)

None

     SPACE: 空白文字 (" ")
     N/E: No effect
     NTBS: null 終端文字列を指す char* 引数
     NTWS: null 終端ワイド文字列を指す wchar_t* 引数
     XSI: ISO/IEC 9945-2003 XSI 拡張

書式指定入力関数 (fscanf() および関連の関数) についても、同様の書式指定文字列が使われる。書式指定文字列と実引数との間の制約についても同様である。

書式指定入出力関数の引数として、未知のあるいは無効な書式指定や、フラグ文字、精度、長さ修飾子、変換指定子の無効な組み合わせを与えないこと。また、書式指定文字列中の変換指定子に対応しない型の実引数や変換指定子と異なる数の実引数を与えないこと。

通常、書式指定文字列は呼び出し側で指定される文字列リテラルであるが、必ずそうでなければならないというわけではない。注意すべきは、書式指定文字列のなかに信頼できない値を混ぜるべきではないということである(「FIO30-C. ユーザからの入力を使って書式指定文字列を組み立てない」を参照)。

違反コード

実引数と変換指定の間の不一致が原因で未定義の動作が引き起こされることがある。多くのコンパイラは、書式付き出力関数の呼び出しにおける型の不一致を診断する機能を持っている。次のコード例では、実引数 error_typed 変換指定子ではなく s 変換指定子に対応し、error_msgs 変換指定子ではなく 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

自動検出

ツール

バージョン

チェッカー

説明

GCC

4.3.5

 

-Wformat フラグを使用することでこのルールの違反を検出できる

Klocwork

9.1

SV.FMT_STR

 

LDRA tool suite

8.5.4

486 S
589 S

実装済み

PRQA QA-C 8.1

0179 (U)
0180 (C99)
0184 (U)
0185 (U)
0190 (U)
0191 (U)
0192 (U)
0193 (U)
0194 (U)
0195 (U)
0196 (U)
0197 (U)
0198 (U)
0199 (U)
0200 (U)
0201 (U)
0202 (I)
0206 (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)

Top へ

Topへ
最新情報(RSSメーリングリストTwitter