FlawOpen

flawopen.com/メモリ安全性/strcpy と strncpy

strncpy() は strcpy() より安全ですか?

CWE-787:範囲外書き込み、CWE-170:不適切なヌル終端リファレンスページ
要点

固定幅フィールドは区切りのあるマス目のようなものです。後続コードがバイト列を C 文字列として扱うなら、最後に終端マーカーが必要です。strncpy() は最大 n バイトを書き込みますが、実際のバッファ容量以下の n を指定した場合にだけ範囲外書き込みを防げます。ソースが上限いっぱいならヌル終端を追加しません。後続の文字列関数が終端を探して宛先の範囲外を読み込む可能性があります。

どちらも誤り
char dst[16];

/* 1. Unbounded — classic overflow */
strcpy(dst, src);

/* 2. Bounded, but may not terminate.
   If strlen(src) >= 16, dst has no
   '\0' and every later strlen/printf
   reads out of bounds.            */
strncpy(dst, src, sizeof(dst));

/* 3. Off-by-one: room for 16 chars
   plus a terminator = 17 bytes,
   but dst only holds 16.          */
strncpy(dst, src, sizeof(dst));
dst[sizeof(dst)] = '\0';   /* OOB! */
適切な方法
char dst[16];

/* Preferred: snprintf always
   null-terminates (when size > 0),
   and tells you about truncation. */
int n = snprintf(dst, sizeof(dst),
                 "%s", src);
if (n < 0 || (size_t)n >= sizeof(dst)) {
    /* truncated — handle it */
    return ERR_TOO_LONG;
}

/* Or strlcpy where available —
   always terminates, returns the
   length it tried to create.      */
if (strlcpy(dst, src, sizeof(dst))
        >= sizeof(dst)) {
    return ERR_TOO_LONG;
}

strncpy が本来想定していた用途

この動作は欠陥ではありません。strncpy() は固定幅でヌル埋めされる文字列フィールド向けで、汎用の安全な C 文字列コピー関数ではありません。ソース文字列が n より短い場合、宛先を n - 1 バイト目までヌルバイトで埋めます。先頭 n バイトにヌルバイトがなければ、ちょうど n バイトをコピーして終端しません。固定幅フィールドには適しますが、ヌル終端 C 文字列を期待する API に渡す用途には危険です。

ソースが n より短いと、必要なパディングでフィールド幅全体を書き込みます。大きなバッファに短いソースをコピーすると、終端で止まる関数より余分な処理になることがあります。

切り詰めを必ず確認する

オーバーフローを防ぐだけでは不十分です。サイレントな切り詰めも危険です。ホスト名、パス、認可値が短くなると意味が変わる場合があります。snprintf は書き込もうとした長さを返すため、バッファサイズと比較して切り詰めを検出します。この戻り値を実際の書き込みバイト数と誤解した問題は Citrix Bleed にも関係しました。

よくある質問

strcpy_s や Annex K の関数はどうですか?

ヌル終端と境界チェックを行いますが、移植性にはばらつきがあります。Windows ではよくサポートされますが、他環境では実装が不均一です。複数環境向けなら snprintf のほうが利用しやすいでしょう。

strlcpy は C 標準ですか?

いいえ。OpenBSD で始まり、BSD と macOS で広く使えます。比較的新しい glibc にも追加されています。対象環境で利用可能か確認してください。

C++ ではどれを使うべきですか?

長さを管理する std::string と std::string_view を優先してください。生バッファを要求する API 境界でのみ C 関数を使います。

参考資料

関連項目 CWE と CVE:何が違うのか Mass Assignment:リスクと安全なフィールド許可リスト Serverless の状態漏えい:ユーザーデータをリクエストごとに分離 PHP == と Magic Hash の比較 Rust の unsafe ブロックはメモリ安全なのか?