yohhoyの日記

技術的メモをしていきたい日記

strncpy関数仕様のナゾ

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関数を意図したものとして下記候補が挙げられる。

  • FreeBSD由来*2で非標準の strlcpy関数。ただし、GNU Cライブラリ(glibc)では意図的*3にstrlcpy関数を提供しない。
  • C11標準ライブラリ Annex.K Bounds-checking interfaces拡張((標準ヘッダstring.hのinclude前にマクロ__STDC_WANT_LIB_EXT1__を1で定義すると、strncpy_s関数が利用可能となる。))で定義される 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 "bounded strcpy," 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 than n characters (characters that follow a null character are not copied) from the array pointed to by s2 to the array pointed to by s1.269) If copying takes place between objects that overlap, the behavior is undefined.
3 If the array pointed to by s2 is a string that is shorter than n characters, null characters are appended to the copy in the array pointed to by s1, until n characters in all have been written.

脚注269) Thus, if there is no null character in the first n characters of the array pointed to by s2, the result will not be null-terminated.

関連URL

*1:本文中コードのようにstrncpy関数を利用する際には気付きにくいが、strncpy関数仕様では「コピー元文字列が指定長よりも短い場合、コピー先バッファの末尾側を全てNUL終端文字で埋める」となっている。これはNUL終端文字列として扱う際には冗長な処理だが、固定長フィールドを単純バイト比較するためには必須である。

*2:https://www.sudo.ws/todd/papers/strlcpy.html

*3:strlcpy関数によりバッファオーバーラン脆弱性は防げるが、文字列切り詰めによる意図しないデータ欠落もまたセキュリティ上の懸念となるという理由。詳細はThe ups and downs of strlcpy()参照。