2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

strncpy_s で嵌った話: 文字列終端からバッファ末尾までの領域の扱いが strncpy と違う!

Last updated at Posted at 2025-04-18

はじめに

本記事は、Microsoft Visual C/C++ が提供する CRT のセキュリティ機能 を使う場合の注意喚起を目的としています。

古典: strncpy の動作

C言語教科書 K&R 付録B3 文字列関数 より抜粋

char *strncpy(s,ct,n) 文字列 ct のうち最大 n 文字を s にコピーし, s を返す。
ct が n 文字より少ないときは,'\0' をつめる

古き文字列関数 strncpy は、文字列バッファ s へ 必ず n 文字書き込みます
ct の長さが n より短い場合は、残り n文字目まで '\0' を書き込みます。
ct の長さが n と等しいか長い場合は、n文字目までコピーするだけで、終端記号 '\0' を書きません

JPCERTの推奨

上記の終端記号 '\0' を書き込まないという動作がバッファオーバーランの温床となるので、JPCERT のコーディング規約にて ISO-C11附属書K の strncpy_s が適合コードとして示されています。

業務において JCERT のコーディング規約が採用されている場合は、これに従うことになるので strncpy_s の動作仕様を見ていきましょう。

適合コード: strncpy_s の動作

errno_t strncpy_s(
   char *strDest,
   size_t numberOfElements,
   const char *strSource,
   size_t count
);

これらの関数は、strSource の最初の D 文字を strDest にコピーしようとします。このとき、D は count か strSourceの長さか、いずれか小さい方の値です。これらの D 文字が strDest 内に収まり (そのサイズが numberOfElementsとして指定されている) 終端のnullも収まる場合は、それらの文字はコピーされ、終端の null が追加されます。それ以外の場合は、 strDest[0] に null 文字が設定され、無効なパラメーターハンドラーが呼び出されます

コピー先に収まらない場合に例外ハンドラが呼び出される!
終端記号 '\0' を書き込めない場合、バッファオーバーランの温床とするぐらいならば例外で止める。という安全側へ全振りした仕様です。一応は抜け道が用意されており、

切り捨て動作が必要な場合は、次のように、_TRUNCATE を使用するか、size を 1 減算します。

strncpy_s(dst, 5, "a long string", _TRUNCATE);
strncpy_s(dst, 5, "a long string", 4);

上記の手段で回避は可能ですが、さらに次が大問題です。

strncpyとは異なり、countがstrSourceの長さを超える場合、変換先の文字列は、長さcountまで null 文字で埋め込まれません。

strncpy はコピー先の文字列バッファの count 文字分に確実に書き込みますが、
strncpy_s は、strcpy と同様に文字列終端以後は不定値となります。
構造体の固定長文字配列へ strncpy でコピーしていた処理を strncpy_s へ置き換えた場合、確実に値が書き込まれずに不定値が生じ、再現困難なバグや、単体テストの障害になります(というか、なりました😡)

これらの関数のデバッグ ライブラリ バージョンでは、最初にバッファーを 0xFE で埋めます。 この動作を無効にするには、_CrtSetDebugFillThreshold を使用します。

なんと、デバッグ版ではさらに書き込み内容が変わります!
不定値ではなく固定値 0xFE で埋めるから嬉しい?いやいや埋めるなら 0x00 にしてくださいよ。

デバッグ版にて最初にバッファーを 0xFE で埋める動作は、 strncpy_s に限らず、バッファーへ書き込む *****_s 関数の全てに共通する仕様のようです。

The debug versions of some security-enhanced CRT functions fill the buffer passed to them with a special character (0xFE).
Here's a list of the affected functions:
asctime_s, _wasctime_s
_cgets_s, _cgetws_s
ctime_s, _ctime32_s, _ctime64_s, _wctime_s, _wctime32_s, _wctime64_s
_ecvt_s
_fcvt_s
_gcvt_s
_itoa_s, _ltoa_s, _ultoa_s, _i64toa_s, _ui64toa_s, _itow_s, _ltow_s, _ultow_s, _i64tow_s, _ui64tow_s
_makepath_s, _wmakepath_s
_mbsnbcat_s, _mbsnbcat_s_l
_mbsnbcpy_s, _mbsnbcpy_s_l
_mbsnbset_s, _mbsnbset_s_l
_mktemp_s, _wmktemp_s
_splitpath_s, _wsplitpath_s
strcat_s, wcscat_s, _mbscat_s
strcpy_s, wcscpy_s, _mbscpy_s
_strdate_s, _wstrdate_s
strerror_s, _strerror_s, _wcserror_s, __wcserror_s
_strlwr_s, _strlwr_s_l, _mbslwr_s, _mbslwr_s_l, _wcslwr_s, _wcslwr_s_l
strncat_s, _strncat_s_l, wcsncat_s, _wcsncat_s_l, _mbsncat_s, _mbsncat_s_l
strncpy_s, _strncpy_s_l, wcsncpy_s, _wcsncpy_s_l, _mbsncpy_s, _mbsncpy_s_l
_strnset_s, _strnset_s_l, _wcsnset_s, _wcsnset_s_l, _mbsnset_s, _mbsnset_s_l
_strset_s, _strset_s_l, _wcsset_s, _wcsset_s_l, _mbsset_s, _mbsset_s_l
_strtime_s, _wstrtime_s
_strupr_s, _strupr_s_l, _mbsupr_s, _mbsupr_s_l, _wcsupr_s, _wcsupr_s_l
vsnprintf_s, _vsnprintf_s, _vsnprintf_s_l, _vsnwprintf_s, _vsnwpr`intf_s_l

まとめ

strncpy_s は、古き文字列関数 strncpy とは異なり、コピー先の文字列バッファのcount文字分を上書きせず、コピーした文字列終端以後は不定値となります。コピー元の文字列がコピー先の文字列バッファに収まらない場合は、例外ハンドラが呼び出されます。

Microsoft Visual C/C++ の固有実装なのか ISO-C11附属書K に明記されているのかわかりませんが、暗黙に上書き動作が変わってしまうのは困ります。皆さんも注意して strncpy_s を使ってください。

END

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?