C標準ライブラリが提供するstrncpy
関数の仕様について。
有限長バッファへ文字列を切り詰めてコピーする処理では、strncpy
関数が用いられるケースが多い。strncpy
関数の仕様として「コピー元src
の文字列長が指定長BUFSIZE
以上のとき、コピー先dest
末尾にはNUL終端文字が設定されない」ため、下記★のようにコピー先バッファ末尾へのNUL終端文字代入が必須となる。この'\0'
代入を忘れると正しいNUL終端文字列を構成しないケースが発生し、文字列としてdest
を扱うタイミングでバッファオーバーランを引き起こす。
#define BUFSIZE 128 char dest[BUFSIZE]; void safe_store(const char* src) { strncpy(dest, src, BUFSIZE); dest[BUFSIZE - 1] = '\0'; // ★ }
上記実装のためには不便な関数仕様に思えるが、これはstrncpy
関数の目的が “長さ制限付きstrcpy
関数” では無いことに起因する。strncpy
関数は「構造体中にある固定長フィールドへの値設定*1」を意図したものであり、他の文字列処理関数のようにコピー先でNUL終端文字列を扱うことを想定していない。C標準ライブラリの策定時には、この既存設計がそのまま引き継がれた。
一方で、長さ制限付きstrcpy
関数を意図したものとして下記候補が挙げられる。
- OpenBSD由来*2で非標準の strlcpy関数。ただし、GNU Cライブラリ(glibc)では意図的*3に
strlcpy
関数を提供しない。 - C11標準ライブラリ Annex.K Bounds-checking interfaces拡張*4で定義される
strncpy_s
関数。ただしオプション扱いのため、全てのC11処理系で提供されるとは限らない。
C89(ANSI C)策定時の理論的根拠を説明する "Rationale for American National Standard for Information Systems - Programming Language - C" より引用。この説明文は(PDF)C99 Rationaleの§7.21.2.4にもそのまま引き継がれている。
4.11.2.4 The
strncpy
function
strncpy
was initially introduced into the C library to deal with fixed-length name fields in structures such as directory entries. Such fields are not used in the same way as strings: the trailing null is unnecessary for a maximum-length field, and setting trailing bytes for shorter names to null assures efficient field-wise comparisons.strncpy
is not by origin a "boundedstrcpy
," and the Committee has preferred to recognize existing practice rather than alter the function to better suit it to such use.
C99 7.21.2.4/p2-3より引用。
2 The
strncpy
function copies not more thann
characters (characters that follow a null character are not copied) from the array pointed to bys2
to the array pointed to bys1
.269) If copying takes place between objects that overlap, the behavior is undefined.
3 If the array pointed to bys2
is a string that is shorter thann
characters, null characters are appended to the copy in the array pointed to bys1
, untiln
characters in all have been written.脚注269) Thus, if there is no null character in the first
n
characters of the array pointed to bys2
, the result will not be null-terminated.
関連URL
- c - Why does strncpy not null terminate? - Stack Overflow
- STR07-C. 境界チェックインタフェースを使用し、文字列操作を行う既存のコードの脅威を緩和する
- STR32-C. 文字列を引数にとるライブラリ関数に null 終端されていない文字配列を渡さない
- cppreference: strncpy, strncpy_s
*1:本文中コードのように strncpy 関数を利用する際には気付きにくいが、strncpy 関数仕様では「コピー元文字列が指定長よりも短い場合、コピー先バッファの末尾側を全てNUL終端文字で埋める」となっている。これはNUL終端文字列として扱う際には冗長な処理だが、固定長フィールドを単純バイト比較するためには必須な処理となる。
*2:https://www.sudo.ws/todd/papers/strlcpy.html
*3:strlcpy 関数によりバッファオーバーラン脆弱性は防げるが、文字列切り詰めによる意図しないデータ欠落もまたセキュリティ上の懸念となるという理由。詳細はThe ups and downs of strlcpy()参照。
*4:標準ヘッダ string.h の include 前にマクロ __STDC_WANT_LIB_EXT1__ を1で定義すると、strncpy_s 関数が利用可能となる。