概要
strncpy は安全な文字列コピー関数として紹介されることが多いが、使い方を誤ると通常のバッファオーバーフローと同じ問題を引き起こす。
実際に、コピー対象とは関係のない変数の値が途中で書き換わる現象に遭遇した。本記事では、その原因を調査した結果と、安全に実装するためのポイントを整理する。
背景
デバッグ中に、ある変数の値が処理途中で突然書き換わる現象が発生した。
その変数へ代入しているコードは見当たらず、別のロジックを疑って調査を進めたが原因は見つからない。
メモリの状態を追跡していくと、原因は数十行前に実行されていた strncpy にあることが分かった。
問題
次のようなコードがあった。
char code[10];
char id[16];
文字列コピーには strncpy が使用されていた。
strncpy(code, src, strlen(src));
一見すると strncpy を利用しているため安全に見える。
しかし、入力データが長い場合だけ id の内容が途中で書き換わる現象が発生した。
調査内容
調査1:代入箇所を確認
まずは id を変更しているコードを調査した。
しかし、該当する代入処理は存在せず、処理の途中で値だけが変化していた。
このことから、別の処理によって隣接メモリが破壊されている可能性を疑った。
調査2:メモリ配置を確認
対象となる変数は隣接して配置されていた。
+-----------+
| code[10] |
+-----------+
| id[16] |
+-----------+
この状態で code に長い文字列をコピーすると、id の内容まで上書きされることを確認した。
調査3:コピーサイズを確認
問題となっていたコードでは、コピーサイズにコピー元の長さを指定していた。
strncpy(code, src, strlen(src));
例えば、
-
codeのサイズ:10バイト -
srcの長さ:20文字
の場合、20バイトのコピーが実行される。
コピー先のサイズを超えた領域まで書き込まれるため、後続の変数が破壊される。
原因
原因は、strncpy 自体ではなくコピーサイズの指定方法だった。
strncpy(code, src, strlen(src));
strlen(src) はコピー元の長さであり、コピー先のサイズではない。
そのため、コピー元がコピー先より長い場合は、strncpy でもバッファオーバーフローが発生する。
「strncpy を使っているから安全」という考え方は誤りであり、安全かどうかはコピーサイズの指定方法に依存する。
解決方法
コピー先のサイズを基準にコピーする。
strncpy(code, src, sizeof(code) - 1);
code[sizeof(code) - 1] = '\0';
終端文字も明示的に設定することで、終端されないケースを防げる。
用途によっては snprintf を利用する方法も分かりやすい。
snprintf(code, sizeof(code), "%s", src);
結果
コピー先のサイズを基準に修正したところ、隣接する変数が書き換わる現象は発生しなくなった。
原因不明に見えていた値の変化も解消され、再現していた問題は修正できた。
学んだこと
-
strncpyは安全な関数ではなく、使い方によってはオーバーフローが発生する - コピーサイズはコピー元ではなく、コピー先のサイズを基準に決める
-
strncpyは終端文字を保証しないため、必要に応じて'\0'を明示的に設定する - 関係ない変数が壊れる場合は、その変数ではなく周辺のメモリ操作を疑う
- 原因となるコードは、異常が発生した場所から離れていることもある
まとめ
今回の原因は strncpy ではなく、コピーサイズの指定方法にあった。
strncpy はコピーサイズを制限するだけであり、コピー先のサイズを自動で考慮してくれるわけではない。
原因不明に見える変数破壊でも、周辺のメモリ操作を追跡することで原因を特定できる場合がある。同様の問題に遭遇した際は、コピー処理やバッファサイズの指定方法も確認すると解決につながる可能性が高い。