世界を動かす技術を、日本語で。

strcpyも使用しない

2025年12月30日原文(daniel.haxx.se)

概要

curlプロジェクトでは、 strncpy()strcpy() の使用を廃止し、安全な文字列コピー関数へ移行。 strncpy() はAPIが不適切で、誤用リスクが高い関数。 strcpy() も安全性の観点から独自関数に置換。 新関数はバッファサイズや文字列長を明示的に扱い、バグを未然に防止。 AIによる誤検出も抑制可能だが、根本的な解決には至らない現状。

curlにおけるstrncpy()廃止の経緯

  • strncpy() はAPI仕様が分かりにくく、誤用によるバグ発生源
  • バッファ終端のnull文字未保証、余分なゼロ埋め発生
  • 多くのコードベースで strncpy() の利用自体を避けるべきとの判断
  • curlソースコードから strncpy() の呼び出しを完全廃止
  • 文字列全体のコピーができない場合はエラーを返す方針
  • 部分コピーが必要なケースは memcpy() +明示的なnull終端で対応
  • strlcpy 等の代替関数も不要と判断

strcpy()の課題と置換理由

  • strcpy() には一定の有用性があるが、APIが不十分
  • バッファサイズや文字列長の明示的指定ができない
  • 利用時は事前にバッファサイズチェックが必要
  • 長期運用・複数人開発でチェックと呼び出しが分離しやすく、リスク増大
  • バッファサイズチェックとコピー処理を密接に結合する必要性

独自string copy関数の導入

  • curlx_strcopy 関数の新設

  • 引数:ターゲットバッファ、バッファサイズ、ソースバッファ、ソース文字列長

  • コピー可能かつnull終端が収まる場合のみコピー実行

  • 実装例: memcpy() +明示的なnull終端処理

  • strcpy() の完全禁止が可能に

  • 利用は若干手間だが、安全性・保守性を優先

    • void curlx_strcopy(char *dest, size_t dsize, const char *src, size_t slen){
        DEBUGASSERT(slen < dsize);
        if(slen < dsize) {
          memcpy(dest, src, slen);
          dest[slen] = 0;
        }
        else if(dsize)
          dest[0] = 0;
      }
      

AIによる脆弱性誤検出の抑制

  • strcpy() のコード残存はAIによる脆弱性指摘の温床
  • 独自関数への統一でAIの誤検出を抑制
  • ただし、AIは他の箇所でも誤検出を継続する可能性
  • AIによる誤検出問題の根本解決には至らない現状

Hackerたちの意見

記事からの引用: 「ソースコードのstrcpyは、幻の脆弱性主張を生み出すためのハニーポットのようなものだということが、何度も証明されている。」この記事のこの締めくくりがすごく印象に残った。AIがCコードをチェックする意味って何なんだろう?strcpy()を問題として指摘するだけなら、何の注意もなしに。

人間ってバカだから、AIが得意じゃないことに使うんだよね。

記事が示唆しているほど単純じゃないよ。幻の脆弱性レポートは「注意なし」で指摘するわけじゃなくて、どこかに論理的な誤りがある複雑な脆弱性の証明を作り上げて、それが脆弱性レポートとして提出されるんだ。だから、メンテナンスする側にとってはイライラするんだよね。「証明」を読んで矛盾を見つけなきゃいけないから。

OSSコードにAIチェックをかけて、偽のバグレポートを提出する人たちは、AIが間違えないと思っているか、報告が正当かどうか気にしないんだよね。だって、たとえそれが正当じゃなくても、彼らにはほとんど個人的なコストがないから。

curlx_strcopyが成功を返さないのには驚いた。確かに、dest[0]が'/0'じゃないかをチェックすることもできるけど、それは書くのが面倒だし、ミスも起きやすいから、成功をチェックするのは推奨されてないんだよね。

もしこの行でコードがクラッシュしなければ、DEBUGASSERT(slen)が成功を意味するっていう考えなんだろうね。ただ、いくつかのコンパイラはリリースビルドでアサーションを削除しちゃうけど。明示的なエラーコードの方が良かったな。

これが特に奇妙なのは、彼が上で「部分的な文字列をコピーするのが正しい選択であることは稀だ」と説明しているのに、前の解決策がエラーを返したことだよね… それで今は、何も部分的にコピーせずにdestを空の文字列に設定して、静かに失敗するってこと!?

そうだね、私も同じこと思った。今後、いくつかのCVEが出るかもね。

変なAnnex-KみたいなAPIだね。宛先バッファのサイズには末尾のヌル文字のスペースも含まれてるけど、ソースのサイズはヌル以外の文字バイトだけを含んでる。これって、strcpyの代わりにmemcpyを使わせることに何の意味があるんだろう。

Cの様々な文字列ルーチンの動機についてずっと疑問に思ってたんだけど、どれも大きな注意点があって使えないんだよね。何年も経って、文字列にどれだけメモリが割り当てられているかをポインタと一緒に記録するライブラリが必要だと思うようになった。こんなのがあればいいな: https://github.com/msteinert/bstring

strncpyは結構簡単で、C文字列を固定幅の文字列にコピーするための特別な関数なんだ。昔のCアプリケーションのディスクフォーマットでよく使われてた。例えば、最大20文字を含むchar username[20]フィールドがあって、使われていない文字はヌルで埋められる。これがstrncpyの目的。宛先引数は常に固定サイズのchar配列であるべきだね。数年前にAlejandro Colomarのおかげで、これに関する新しいマニュアルページができたよ: https://man.archlinux.org/man/string_copying.7.en

これはコンピュータウイルスが登場する前の時代の話だよね?でも、こういう帳簿管理は余分な時間とスペースを取るから、今の時代では簡単にトレードオフできるよね。

Hacker Newsで議論の続きを見る