11 ポイント 投稿者 GN⁺ 2026-01-01 | 5件のコメント | WhatsAppで共有
  • cURLプロジェクトは、従来のstrncpy()の削除に続き、今度は**strcpy()もコードベースで完全に禁止**した
  • strcpy()はAPIが単純だが、バッファサイズの検証が分離される危険があり、長期的なメンテナンスでは安全ではない
  • その代わりに**curlx_strcopy()**という新しい関数が導入され、宛先バッファサイズと文字列長の両方を引数として受け取り、コピー可能かどうかを検査してから実行する
  • この関数は内部的にmemcpy()を使用し、ヌル終端文字の処理まで保証する
  • こうした変更により、セキュリティとコードの一貫性を高め、AIが誤った脆弱性レポートを生成する問題も減らせる

strcpy削除の背景

  • cURLは過去にstrncpy()呼び出しをすべて削除しており、この関数の直感的でないAPI、ヌル終端を保証しない点、不要な0埋めの問題を指摘していた
    • 部分文字列のコピーが必要な場合はmemcpy()を使用し、ヌル終端を手動で処理するよう変更
  • strcpy()はAPIが単純だが、バッファサイズを明示しないため、メンテナンス中に検証コードとコピー呼び出しが分離される危険がある
    • コードが数十年にわたって複数の開発者によって修正される場合、バッファサイズ検証が無効化される可能性がある

新しい文字列コピー関数の導入

  • こうした危険を防ぐため、**curlx_strcopy()**という代替関数を導入
    • 引数として宛先バッファ、バッファサイズ、元バッファ、元文字列長を受け取る
    • コピーとヌル終端の両方が可能な場合にのみmemcpy()で実行
    • 失敗時は宛先バッファを空文字列に初期化
  • この関数はstrcpy()より引数もコード量も多いが、バッファ検証をコピー処理と密接に結び付けることで安全性を確保する
  • cURLコードベースではstrcpy()の使用を完全に禁止し、strncpy()と同様に削除した

実装の詳細

  • 関数定義の例は次のとおり
    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;
    }
    
  • DEBUGASSERTにより開発中のエラーを早期検知し、実際の配布環境では常に成功するよう設計されている
  • strcpyのような戻り値はなく、テストおよびファジング段階でエラーを捕捉する方式を採用している

コミュニティの反応

  • 一部の開発者は**strcpy_s()(C11 Annex K)に似ていると指摘したが、cURLは依然としてC89標準**を使用している
  • 別の意見としては、戻り値追加の必要性バッファ失敗時の処理改善の提案があった
  • これに対しcURL側は、「常に成功する関数として設計されているため戻り値は不要だ」と説明した

AI関連の副次効果

  • 今回の変更により、AIチャットボットがcURLコード内のstrcpy使用を誤って検出し、『脆弱だ』と主張する問題を防げる
  • ただし筆者は、「AIが別の虚偽レポートを作り出す可能性は依然としてある」として、AIベースのコード分析の限界にも言及している

5件のコメント

 
ahwjdekf 2026-01-02

strcpy の代わりに snprintf を使うのが正しい。コードに strcpy があるなら、それを作った開発者の所在地を突き止めるべきだ。

 
winmain 2026-01-02

これは25年前にゲーム会社に勤めていた頃、デバッグコードとしてやっていたやり方でしたが、strcpyだけの話ではないでしょう。リリース版では速度向上のために再び制限を外した状態でサービスされていました。実際、ゲーム業界はメモリ衝突に最も敏感なので、作業も非常に慎重に気を引き締めて進めていましたし、メモリデバッガも独自に作って使っていました。ところが今日になって振り返ると、それはガベージコレクションを作っていたようなものだったのです。ほのかな思い出ですね。

 
secwind 2026-01-02

エラー C4996 'strcpy': この関数または変数は安全でない可能性があります。代わりに strcpy_s の使用を検討してください。非推奨警告を無効にするには、_CRT_SECURE_NO_WARNINGS を使用します。詳細はオンラインヘルプを参照してください。

 
GN⁺ 2026-01-01
Hacker Newsの意見
  • strcpy() はセキュリティ面だけでなく、性能面でも良くない
    以前は文字列長が分からないときは strcpy() が効率的だと思っていたが、実際には 1 バイトずつコピーする構造なので CPU が分岐予測を行う必要があり、これは非効率的である

    • もはや可能な限り null-terminated 文字列そのものを捨てるべきだと思う
    • 最近は strcpy が スカラーループを使っているのを見たことがない。もしかすると ARM アーキテクチャだけなのか気になる
  • C の文字列ルーチンはどれもこれも 大きな制約があって役に立たないと感じていた
    そのため、文字列ポインタと一緒に 割り当てられたメモリサイズを記録するライブラリがぜひ必要だと思う
    例として bstring ライブラリ は参考になる

    • strncpy が作られた理由は固定長のファイル名をコピーするためだった。詳しくは この StackOverflow の回答 を参照
    • 文字列に長さ情報を含めなかったのは、過去に メモリ節約のためだった。当時は 1 バイトすら惜しかったからだ
    • C の文字列関数が問題を引き起こしたのは、設計者たちがその 結果を十分に予測しないまま追加したためだ。配列が関数引数でポインタに強制変換されるのも根本的な設計ミスである
    • このような追加の book-keeping は昔は負担だったが、今では十分に耐えられる水準である
    • strncpy は本来 固定幅文字列フィールドを扱うための関数だった。たとえば char username[20] のようなフィールドに NUL を詰めて埋める用途だった。関連文書は string_copying.7 マニュアル を参照
  • curlx_strcopy が成功可否を返さないのは不可解だ
    dest[0] を検査することもできるが、これは エラーを誘発しやすく直感的でもない

    • 以前のバージョンはエラーを返していたが、今は黙って失敗して空文字列を設定する。これはおかしい
    • おそらく DEBUGASSERT(slen < dsize); が通れば成功と見なしているのだろうが、release ビルドでは assert が除去される可能性がある。明示的なエラーコードのほうがよいと思う
    • この設計だと今後 CVE が出る可能性が高いと思う
  • strncpy() はもともと null-terminated 文字列のためのものではなく、固定長フィールドのための関数だった
    問題は静的解析器が strcpy の代わりに strncpy を使うよう推奨したことから始まった。実際の代替は snprintf や strlcpy だった

    • strlcpy は BSD 系の関数なので POSIX にはない。公式の推奨は stpecpy だが、実装はほとんど存在しない。関連文書 を参照
    • strncpy が null 以降をパディングする理由は、ディレクトリエントリのような 固定長の名前フィールドで効率的に比較するためだった。ANSI C 標準の根拠文書にもそのように明記されている
  • この API はまるで Annex-K のように感じる。宛先バッファサイズには NUL 用の領域が含まれるが、ソースサイズには含まれない
    むしろ memcpy を直接使うほうがましだと思う

  • 記事中の「strcpy は AI が誤った脆弱性レポートを作り出すための餌」という言葉が印象的だった

    • 実際には AI が単に strcpy を問題だと指摘するだけでなく、論理エラーのある複雑な証明を作り出し、メンテナたちはその検証に苦労している
    • このような誤ったレポートを提出する人たちは、AI が間違う可能性を知らないか、単に気にしていない。どうせ 誤報にもコストがかからないから
    • 結局のところ、AI を 不適切な用途で使う人たちが問題である
  • 「コードの近くで検査せよ」という原則は良いが、データの ライフサイクルの初期で検査すべき場合は曖昧になる
    Rust の Result 型のように、「検証済みデータ」であることを型で区別できればよいと思う

    • Result は単に成功/失敗を保持するだけで、検証済み状態を保証しない。代わりに、検証プロセスを経なければ生成できない別の型を用意するのがよい。これが「** parse, don’t validate**」という哲学である
    • 検証は消費側コードの近くではなく、システム境界でできるだけ早く行うのが理想的だ。ただしそのためには 表現力の高い型システムが必要になる
    • このような場合は Java の String と CharSequence のように型を分けて使うのも一つの方法だ
  • バッファサイズと文字列長の off-by-one の差はひどいユーザビリティ問題だ。今後もエラーを誘発する可能性が高い

  • 新たに提案された文字列コピーファンクションは、コピーできない場合に 対象バッファを空にして void を返す
    しかしこのような場合は エラーとして扱い、バッファには手を触れないほうがよいと思う。DEBUGASSERT だけで防ぐのは不安だ

  • プロジェクト完成おめでとう。C/C++ でも努力すれば メモリ安全性を確保できる
    ただしモバイル環境では グラフのフォントサイズが小さすぎて読みづらい

    • strcpy を削除したからといってコードが メモリ安全になるわけではない
    • グラフのフォントは印刷向けに設計されたように見える。ブログ向けとしては小さすぎる
 
hiongun 2026-01-04

C3言語へ完全に移行するのも良いですね。C言語の構文を最小限の変更にとどめつつ、モダンな機能を追加したプロジェクトなので、移行もしやすいです。