Linuxカーネルからstrncpyが消えた。6年362コミットの結末
C言語の標準ライブラリに含まれる関数が、Linuxカーネルから完全に姿を消した。strncpy()。文字列を指定バイト数だけコピーするこの関数は、1970年代のUNIXから受け継がれ、あらゆるCプログラマが一度は使ったことがあるだろう。だが、その挙動はバグの温床だった。
何が起きたのか
現地時間2026年6月19日、リーナス・トーバルズ(Linus Torvalds)氏がLinux 7.2のマージウィンドウでプルリクエストをマージした。strncpy()のAPI定義、5アーキテクチャ(alpha、m68k、powerpc、x86、xtensa)のアセンブリ実装、FORTIFY_SOURCEの防御コード、テストケースのすべてが削除され、19ファイルから346行が消えた。
この作業を率いたのは、Googleでカーネルセキュリティの強化に取り組むケース・クック(Kees Cook)氏だ。クック氏は2015年にKernel Self-Protection Project(KSPP)を立ち上げ、「バグを個別に潰すのではなく、バグが生まれる原因そのものを消す」という方針でカーネルの堅牢化を進めてきた。strncpy()の排除はその象徴的な成果だ。
コミットメッセージには「6年間で362コミット、70人のコントリビューター」と記されている。最多はGoogleのジャスティン・スティット(Justin Stitt)氏で、211コミット。全体の6割近くを一人で担った計算だ。
| 開発者 | コミット数 | 割合 |
|---|---|---|
| ジャスティン・スティット | 211 | 58.3% |
| シュー・パンダ | 22 | 6.1% |
| ケース・クック | 21 | 5.8% |
| トルステン・ブルム | 17 | 4.7% |
| アルント・ベルクマン | 12 | 3.3% |
| その他(55名) | 79 | 21.8% |
なぜstrncpyは危険だったのか
strncpy()の問題は、名前が暗示する動作と実際の動作がずれていることにある。
コピー元の文字列がコピー先のバッファより短ければ、残りをゼロで埋める。ここまでは直感に合う。だが、コピー元がバッファと同じかそれより長い場合、コピー先の末尾にNUL終端文字(\0)を付けない。つまり、文字列としての終わりが保証されない。
文字列の終わりがわからなければ、プログラムはメモリの先を読み続ける。読んではいけない領域まで突き進み、情報が漏れる。あるいはクラッシュする。カーネルドキュメント自身がstrncpy()を「永続的なバグの発生源」(persistent source of bugs)と呼んでいた。
もうひとつ、パフォーマンスの問題がある。コピー元が短い場合に発生するゼロ埋めは、NUL終端さえあれば不要な処理だ。数バイトの文字列をコピーするのに、バッファ全体をゼロで上書きする。カーネルのような性能が生命線のソフトウェアでは、この無駄も見過ごせない。
362コミットの内側
関数ひとつを消すのに6年かかった理由は、カーネルの規模と多様性にある。
2022年の時点で、Linux 6.0-rc2のカーネルにはまだ約900箇所のstrncpy()呼び出しが残っていた。ネットワークドライバ、ファイルシステム、HIDデバイス、SCSI、Wi-Fiと、あらゆるサブシステムに散在していた。しかも、呼び出しごとに意図が違う。NUL終端を前提にしているもの、逆にNUL終端なしの固定長フィールドを意図しているもの、ゼロ埋めが必要なもの、不要なもの。機械的に一括置換できるような作業ではなかった。
スティット氏のパッチを見ると、1件ごとにコードの文脈を読み解いている。コピー先がNUL終端を前提としているならstrscpy()に、ゼロ埋めも必要ならstrscpy_pad()に、NUL終端が不要な固定長フィールドならstrtomem()やstrtomem_pad()に。正解はひとつではなく、呼び出しの数だけ判断がある。
最終段階で消えたのは、strncpy()のAPI定義とアーキテクチャ固有の最適化実装だ。alphaのアセンブリ実装は83行。x86のインラインアセンブリは19行。1990年代に性能を絞り出すために手書きしたコードを、もう誰も呼ばない。
何に置き換わったのか
カーネルドキュメントはstrncpy()の代替として7つの関数を挙げている。
strscpy()がもっとも一般的な置き換え先で、NUL終端を保証する。strscpy_pad()はNUL終端に加えてゼロ埋めも行い、特権境界をまたぐ構造体で使う。memtostr()とmemtostr_pad()はNUL終端なしの固定長ソースからNUL終端付きの宛先へコピーする。strtomem()とstrtomem_pad()はその逆で、宛先がNUL終端を必要としない固定長フィールドの場合に使う。memcpy_and_pad()はソースの終端が保証されない場合の境界付きコピー用だ。
| 関数 | NUL終端 | ゼロ埋め |
|---|---|---|
| strscpy() | ○ | × |
| strscpy_pad() | ○ | ○ |
| memtostr() | ○ | × |
| memtostr_pad() | ○ | ○ |
| strtomem() | × | × |
| strtomem_pad() | × | ○ |
| memcpy_and_pad() | × | ○ |
代替が7つも必要になった事実が、strncpy()の曖昧さを物語る。1つの関数が担っていた役割は、本来それぞれ別の意図を持つ操作だった。意図が曖昧なコードは、書いた本人以外には正しく読めない。正しく読めないコードは、いつか壊れる。
20年越しの決着
strncpy()をカーネルから消すべきだという認識は、2000年代初頭から存在していた。LWN.netが2022年に振り返った記事では、「strncpyは20年近く前に排除が決まったのに、いまだに健在だ」と書かれていた。
排除が進まなかったのは、代替APIの不在が大きい。strlcpy()は2003年にカーネルに導入されたが、ソースの全長を返す戻り値の設計がバッファオーバーランの新たなリスクを生んだ。strscpy()が2015年に導入されて初めて、安全でパフォーマンスも妥当な代替が揃った。
クック氏がKSPPを立ち上げたのと同じ年だ。偶然ではない。KSPPの活動方針は「バグのクラスごと潰す」。strncpy()という関数を消すことは、その関数が原因で生まれうるバグの種類を丸ごと消すことを意味する。個別のCVEを追いかけるのではなく、CVEが発生する構造そのものを断つ。
362コミット、70人、6年。1行のコード変更も、900回繰り返せばカーネルの安全性を底上げする。派手な脆弱性修正に比べれば地味だ。だが、この種の仕事がなければ、Linuxが動いている約39億台のAndroidデバイスも世界中のサーバーも、strncpy()由来のバグに晒されるリスクを抱え続けていた。
参照元:Linux kernel commit - strncpy-removal-v7.2-rc1