API02-C. 配列の読み書きを行う関数はコピー元やコピー先のサイズを指定する引数を取れ
配列を引数に取る関数は、配列に格納できる最大要素数を示す引数も取るべきである。関数が配列の範囲外のメモリにアクセスしてプログラムの実行に悪影響を及ぼさないことを保証するには、この引数が必須である。引数は、各配列引数に1つ必要である(つまり、配列引数が1つあれば、それに対応して、配列の最大要素数を表す引数も1つ指定するということである)。
このレコメンデーションでは、配列を、配列、文字列、あるいは1つ以上の特定の型の要素が格納される(可能性のある)連続したメモリ領域を指すポインタの意味で使う。これらはすべて実質的には同義であり、同じ間違いが引き起こされる可能性がある。
また、このレコメンデーションでは、配列引数に対応する引数は、配列の最大サイズ(バイト数)ではなく、配列に格納できる要素の最大数を意味することを推奨する。理由は以下の通りである。
- 配列サイズを常にバイト数で考えるのは有益ではない。例えば整数配列の場合など。
- 配列サイズのバイト数が必要なら、要素数から求められる。
- 関数の利用者に配列サイズを必ずバイト数で計算させるような余計な負担をかけるべきではない。
要素数とバイト数の違いはほとんどの場合問題にならない。これら2つの対応付けは明確であり、いずれにせよ要素数で考えた方が分かり易い。残念ながら、この問題はマルチバイト文字列を扱う際に混乱を招くことがある。というのも、マルチバイト文字列の場合、操作対象の論理エンティティは、その実装に使われる型の論理エンティティとは異なるためである。ここでは、配列の型は文字型でありマルチバイトの文字型ではないことを覚えておくことが重要である。このため、配列内の要素数は文字数として表される。
違反コード
C言語は、堅牢性を犠牲にして性能を優先することが多いので、他を探すまでもなくC言語の標準ライブラリの中に違反例を見つけることができる。次の2つの例は、C 言語規格、セクション 7.24 からの抜粋である。[ISO/IEC 9899:2011]
char *strncpy(char * restrict s1, const char * restrict s2, size_t n); char *strncat(char * restrict s1, const char * restrict s2, size_t n);
これらの関数には2つの問題がある。第1に、最初の配列 s1
のサイズが指定されていない。そのため、s1
の大きさと、s1
に書き込める要素の数を関数内で調べることができない。第2に、s2
にはサイズが指定されているように見えるが、実際には size_t
の引数 n
にコピーする要素の数を指定する。ゆえに、配列 s2
のサイズを特定する方法がない。
適合コード
Cの標準関数である strncpy()
と strncat()
を改良するには、次のように要素数を表す引数を追加する。
char *strncpy(char * restrict s1, size_t s1count, const char * restrict s2, size_t s2count, size_t n); char *strncat(char * restrict s1, size_t s1count, const char * restrict s2, size_t s2count, size_t n);
引数 n
にコピーする要素の数を指定することができる。値はコピー元文字列の要素の数より小さい数を指定する。
適合コード (C11 附属書 K)
C 言語規格、附属書 K (規定) Bounds-checking interfaces には、C言語標準の、範囲検査を行う文字列処理ライブラリ関数が定義されている。
errno_t strncpy_s(char * restrict s1, rsize_t s1max, const char * restrict s2, rsize_t n); errno_t strcat_s(char * restrict s1, rsize_t s1max, const char * restrict s2);
附属書 K のセキュアな関数と適合コードとの間には、2 つの顕著な違いがある。1つ目は、附属書 K のバージョンでは、size_t
の代わりに rsize_t
を使用している点である。これにより、サイズは適切な上限である RSIZE_MAX
と比較されるようになる。2つ目は、附属書 K の関数は2番目の配列に要素数を必要としない点である。そのため、これらの関数は s2
への入力の妥当性を検証する能力が限られている。しかし、s1
のサイズの値が必須であるため、s1
の範囲外のメモリが上書きされることはない。
例外
API02-EX1: 範囲外の読み書きが発生しないことを実行時制約ハンドラを通じて保証できる関数は、最大要素数引数を省略することができる。たとえば、strcat_s()
の引数 s2
には、引数 max
が不要である。
errno_t strcat_s(char * restrict s1, rsize_t s1max, const char * restrict s2);
もう一つ、strcpy_s()
について考えてみよう。
errno_t strcpy_s(char * restrict s1, rsize_t s1max, const char * restrict s2);
この関数は、s2
に最大数を表す引数を明示的には指定していない。しかし、s1max
が s2
よりも大きいことが要求されており、範囲外の読み取りは生じない。
リスク評価
このレコメンデーションに従わないと、不適切なメモリアクセスやバッファオーバーフローが発生し、プログラムの継続的で正しい実行に支障が生じる可能性がある。
ルール |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
API02-C |
高 |
高 |
高 |
P9 |
L2 |
関連するガイドライン
ISO/IEC TR 24731-1:2007 |
参考資料
[ISO/IEC 9899:2011] | Annex K (normative) Bounds-checking interfaces |
翻訳元
これは以下のページを翻訳したものです。
API02-C. Functions that read or write to or from an array should take an argument to specify the source or target size (revision 25)