2019年1月17日

Cのリスクの軽減

C言語は非常に強力でLinuxカーネルを中心に幅広く使用されていますが、きわめて危険です。この記事では、開発者がプログラミング言語のセキュリティの脆弱性に対処する方法について、Linuxのエンジニアが概説します。

Cを使用すればあらゆることが行えますが、それが推奨されるわけではありません。Cコードは簡単に実行できますが、安全性が保証されているものではなく、大部分のLinuxカーネルの開発者のように、Cのエキスパートであっても、重大なミスを犯してしまう可能性があります。
いわゆるポインターエイリアスのリスクに加え、C言語自体には、不注意なユーザーを待ち受ける根本的な未修正のバグがあり、Google社のLinuxカーネルのセキュリティエンジニアであるKees "Case" Cook氏は、カナダのバンクーバーで開催されたLinux Security Summitのセミナーでこうした脆弱性を話題に取り上げました。
Cによってアプリケーションのスピードが向上することをわかっており、それを高く評価している数百人の聴衆に対して、Cook氏は「Cは魅力的なアセンブラーであり、マシンコードのようなものである」と述べる一方、残念ながら「Cには、セキュリティの問題とインフラストラクチャの脆弱性の原因となる、いくつかの厄介な問題や未定義の動作などの弱点がある」と指摘しました。
たとえ他の開発プロジェクトでCを使用していたとしても、そのセキュリティの課題に注意を払うだけの価値はあります。


Linuxカーネルの保護

Cook氏と同氏が連携してきたエンジニアは、長い期間をかけてCに固有の問題を数多く発見し、Kernel Self Protection Projectは、これらの弱点に対処するために、少しずつ着実にLinuxカーネルを攻撃から守るための取り組みを進めてきました。同プロジェクトはその過程でLinuxから問題のあるコードを取り除くことに力を尽くしました。
Cook氏は、「カーネルはメモリを管理するためにアーキテクチャーに固有の処理を行って、ハンドリングやスケジューリングなどを中断しようとする」ため、注意が必要であると述べています。多くのコードでは、慎重に確認しなければならない処理が行われますが、これについて同氏は、たとえば「ページテーブルを設定したり、64ビットモードに切り替えたりするためのC APIは存在しない」と語っています。
運用の問題があり、標準ライブラリが脆弱なCの動作は、多くが未定義です。これについてCook氏は、「With Undefined Behavior, Anything Is Possible (未定義の動作があらゆることを可能にする)」というRaph Levien氏のブログを引用し、それに同意しています。
そしてCook氏は、これに関する具体例を挙げています。「『未初期化』変数の内容はどのようなものでしょうか。以前からメモリに何が含まれているのかは関係ありません。voidポインターに型はありませんが、それらで型付関数は呼び出せるのでしょうか。もちろんです。アセンブリでは、すべてのアドレスを呼び出しに使用できます。memcpy()には、なぜ『max destination length』引数がないのでしょうか。言う通りにしてください。メモリ領域はどれも同じです」。


警告を無視すべきか否か

こうした特異性のいくつかは、比較的簡単に対処できるものであり、これについてCook氏は、「Linus (Torvalds)氏は、常にローカル変数を初期化することに賛成しているため、『そうすべき』である」とコメントしています。
ただし、このアドバイスには注意点があり、switchでローカル変数を初期化すると、コンパイラによるコードの処理方法が原因で「statement will never be executed [-Wswitch-unreachable]」という警告が表示されますが、この警告は無視してかまいません。
ただし、すべての警告を無視してもいいというわけではありません。これについてCook氏は、「可変長配列(VLA)には問題がつきものである」と述べており、たとえば、スタックが使い果たされたり、リニアオーバーフローが発生したり、ガードページが飛ばされたりといった問題が生じます。また、VLAが低速であることに気付いた同氏がそれを削除したところ、パフォーマンスが13%向上しました。そして今では、スピードと安全性の両方が向上し、大きな効果が得られています。
Cook氏によると、VLAはカーネルからほぼ取り除かれたものの、今もなお一部のコードに残っています。とはいえ、VLAは-Wvlaコンパイラフラグを使用することで簡単に見つけられます。
要するに、VLAに関しては使用をやめなければなりません。

Cのセマンティックには別の問題が隠されており、プログラマー以外の誰かがswitchステートメントの「break」を省略してしまうことがあります。このようにswitchのbreakが省略されると、広く知られている問題として、複数の条件でコードが実行されてしまう可能性があります。
既存のコードのbreak/switchステートメントを探している場合は、-Wimplicit-fallthroughを使用して新しいswitchステートメントを追加できます。これは実際にはコメントですが、最新のコンパイラで解析されます。また、「fallthrough」コメントでbreakがない部分をマークすることも可能です。
Cook氏は、Slabによるメモリ割り当ての境界チェックに時間がかかることにも気付きましたが、たとえば、strcpy()-familyのチェックでは、パフォーマンスに2%の影響が及びます。また、strncpy()などの代替関数にも固有の問題があり、strncpyは必ずしもNULLで終端するとは限りません。これについて同氏は、悲しげな様子で「効果的なAPIはないのか」と述べています。
Q&Aセッションにおいて、あるLinuxの開発者が「問題のある古いAPIを削除してもいいか」と尋ねたところ、Cook氏は、しばらくしてから、Linux社がAPIを廃止するという考えを支持していると答えましたが、Torvalds氏は、すべてのAPIを廃止する必要があるなら、完全になくすべきであるとしたうえで、こうした考えを捨てました。ただし、APIの完全な削除は「厄介な問題」であるとCook氏は付け加えており、私たちは今のところ、APIを使用し続けています。


長期的な解決策としては、セキュリティに精通したオープンソース開発者が必要

Cook氏は、これから長く険しい道のりが待っていると考えています。LinuxのC言語の仕様を考案することが魅力的であると考えられていたこともありましたが、今後そのような仕様が開発されることはないでしょう。危険なコードの問題の背景には、「問題のあるコードだけでなく、C自体を含むコードを一掃したいと考えている人がいない」という現実的な問題があり、すべてのオープンソースプロジェクトに関して、「より専門的な開発者、レビューアー、テスター、およびバックポート担当者が必要です」。


危険なC: リーダーのためのアドバイス

  • Cは成熟した強力な言語ですが、技術的な課題とセキュリティの課題をもたらします。
  • 非常に多くのオペレーティングシステムがCで作成されているため、Linuxの開発者は(パフォーマンスを低下させずに) Cの安全性を向上させることに特別な注意を払っています。
  • Google社のLinuxカーネルのセキュリティエンジニアが言語に固有の弱点を発見し、開発者がそれらに対処する方法を解説しました。

この記事/コンテンツは、記載されている個人の著者が執筆したものであり、必ずしもヒューレット・パッカード エンタープライズの見解を反映しているわけではありません。

enterprise.nxt

ITプロフェッショナルの皆様へ価値あるインサイトをご提供する Enterprise.nxt へようこそ。

ハイブリッド IT、エッジコンピューティング、データセンター変革、新しいコンピューティングパラダイムに関する分析、リサーチ、実践的アドバイスを業界の第一人者からご提供します。