BLOG | OFFICE OF THE CTO

C言語の先へ

Published February 06, 2023

何十年もの間、C言語とその直系のプログラミング言語は、システムのプログラミングに最適な言語として選ばれてきました。1970年代初めにC言語が開発されたとき、それはアセンブリ言語から踏み出した重要な一歩でした。それから50年が経ち、私たちはさらに良い方法を手にしています。

1970年当時、コンピュータ セキュリティはほとんどの人の眼中になく、その後、何年もその状況は続きました。インターネット上で最初に重大なセキュリティ脆弱性が発生したのは、それから何年も経った1988年のことでした。これはインターネット ワームと呼ばれ、メモリ内の配列がその境界を越えてインデックスされるバッファ オーバーフローを利用したものでした。

C++を含むC言語ファミリでは、配列の境界がチェックされません。配列に正しくアクセスするかどうかは、プログラマ次第です。そのため、バッファ オーバーフローが頻繁に発生します。さらに悪いことに、バッファ オーバーフローを簡単に意図的に発生させて、アクセスされてはならないメモリにアクセスできてしまいます。

バッファ オーバーフローは、安全性が欠如したメモリの一例に過ぎません。その他にも、ポインタ演算やダングリング ポインタ(別名、use-after-freeバグ)といった例があります。現在では多くのプログラミング言語があり、それらの言語で書かれたプログラムにはメモリの安全性に問題がないことを保証するためにさまざまな手法が利用されています。C言語ファミリにはこうした保証はありません。メモリの安全性は、これらの言語の設計目標ではなかったからです。

セキュリティ脆弱性の約70%が、メモリの安全性の侵害に起因しています。このことは、圧倒的な量のデータで裏付けられており、以下の脆弱性にメモリの安全性が関与しています。

  • 公開され、実際に検出された、脆弱性の悪用の67%(Googleプロジェクト ゼロ2021の推定より)。
  • Androidの脆弱性の90%(Googleより)。この数値は、Rustの採用により最近減少していますが、リモートで悪用可能な脆弱性の89%が依然としてメモリの安全性に関係しています。
  • Microsoftの脆弱性の70%(Microsoftより)。
  • 最近のAppleの脆弱性修正の大半(CatalinaBig SurMontereySafariiOStvOSwatchOS)。

2022年7月、Chrome 103.0.5060.134で修正された脆弱性のうち、6分の5がメモリの安全性の問題でした。メモリの安全性のバグをなくすことが、非常に有益であることは明白です。この業界が長い年月をかけて明らかにしたその方法が、メモリ セーフのプログラミング言語を使用することです。これまでこの問題は常に、性能面での代償が問われてきましたが、メモリの安全性を重視した人々は、従来のオーバーヘッドなしに安全性を実現する新たな戦略に取り組んできました。今日では、実行時の性能をほとんど低下させずにメモリの安全性エラーを排除する方法がわかっています。Rustなどの言語では、高価なランタイム サポートなしにメモリの安全性を保証する型システム設計のイノベーションを利用しています。

このことから、必然的に1つの結論にたどり着きます。C/C++(より一般的に言えば、メモリ セーフでない言語)で新しいシステム コードを書くのをやめるということです。

これは、見境なく大量の既存のコードの書き直しを求めているのではありません。既存のソフトウェアの置き換えはコストがかかり、リスクも伴います。しかし、この業界は、メモリ セーフでないコードをこれ以上、既存のコードベースに追加するのを止めて、問題の悪化を防ぐ必要があります。既存のコードについては、最も機密性の高いコンポーネントの書き直しを優先します。すなわち、信頼できないユーザー入力情報の検証や使用に関与するコンポーネントや、特権コンテキストで実行するコンポーネント、サンドボックスの外で実行するコンポーネントなどです。

この考え方は広く支持されていますが、一部では異なる意見もあります。以下に、現状を支持する一般的な主張とそれに対する私たちの見解を示します。

  • バグはプログラミング言語に限ったものではない。
    • ほとんどの脆弱性はメモリの安全性のバグに起因しています。メモリの安全性のバグは、Rustのようなメモリ セーフ言語にはありません。そのため、メモリ セーフ言語を使用することで、ほとんどの脆弱性を未然に防ぐことができます。
  • コード レビューでバグを発見できる。
    • 各調査によると、コード レビューで状況が改善する可能性はあるものの、多くの問題が見落とされており、すべてを発見することはできません。
  • モジュールが安全でなければ、本質的に無意味なものになる。
    • 安全でないコードは、非常に小さいコード セクションではっきりと示されるため、担当者は、その検証に専念できます。
  • メモリ セーフ言語の導入で、たくさんのバグが持ち込まれる可能性がある。
    • 確かにそうですが、その可能性はかなり低いです。コンパイラは比較的小さく、厳格なテストが行われています。また、1つのプログラムの1つのバグを修正するのとは異なり、1つのコンパイラを修正することで、そのコンパイラでコンパイルされたすべてのプログラムが自動的に修正されます。
  • こうしたミスは初心者か実力のないプログラマしかしない。
    • むしろ上記のデータは、Chromeブラウザや、WindowsとMacOSのオペレーティング システムなど、主要なシステム プロジェクトのデータです。これらのプロジェクトには、業界で最も優秀で経験豊富な開発者が関わっていますが、それでも、メモリの安全性に問題があることがわかっています。
  • こうした問題はベスト プラクティスに従うことで回避できる。
    • 上で挙げた各チームは、非常に厳格なプラクティスに従い、最も優れたツールを使用しています。
  • すべてのコードを書き直すのは不可能だ。
    • すべてのコードを完全に書き直そうと言っているのではありません。そうではなく、重要な要素(権限の高いもの、大きな攻撃対象、セキュリティ保証の中核)に重点を置いて、メモリ セーフ言語で新しいコードを書き直すアプローチを推奨しています。
  • 性能が不十分だ。
    • Rustの性能はC/C++とほぼ同じです。わずかな違いでは、セキュリティ リスクを正当化できません。また、Rustプログラムのほうが速い場合もあります。
  • Rustなどのメモリ セーフのシステム プログラミング言語はまだ新しすぎる。
  • 静的分析、ファジング、サンドボックスなど、他にも解決策がある。

上記のポイントの詳細については、『メモリの安全性の欠如の数値化とその対応』をご覧ください。

こうした反論をよそに、変革を求める動きは高まっています。例えば、Open Source Software Security Foundation(Linux Foundationが後援)では、大規模な投資が行われています。メモリの安全性は、米国上院の報告書やNSAでも論じられています。Consumer Reportsも、企業や行政機関に一連の提言を行うことで、この動きを加速するさまざまなインセンティブの特定に取り組んでいます。

まとめ:

社会におけるコンピューティングの重要性は、この50年間で大幅に増大しています。コンピューティング インフラストラクチャに対する脅威の状況も、ここ数十年で根底から変わっています。一方、コンピューティング システムの構築に使用されるプログラミング言語はそれに応じて変わっていません。メモリの安全性の欠如は、ソフトウェアにおけるセキュリティ脆弱性の最大の単一要因です。これは、特定の種類のソフトウェアに限った話ではなく、メモリ セーフでない言語が使用されているあらゆる場所で、メモリの安全性の問題が山積しています。そして数字の上では、他の脆弱性を大きく引き離しています。

現在、私たちには、この拡大しつづける問題に対処する方法があり、業界としてこれを実施することが極めて重要です。幸い、この状況に対する認識は広がりつつありますが、問題は切迫していて、一刻の猶予もありません。