Androidの開発へ「Rust」を導入、なぜなのか:メモリ安全ではないバグを予防
GoogleはAndroidオープンソースプロジェクト(AOSP)が、「Android」の開発へ「Rust」導入を進めている。従来のようなC/C++とJavaを組み合わせた開発にはどのような問題があるのだろうか。
2021年4月6日(米国時間)、Googleは公式ブログでAndroidオープンソースプロジェクト(AOSP)がモバイルデバイス向けオープンソースOS「Android」の開発において、オープンソースのシステムプログラミング言語「Rust」の導入を進めていることを明らかにした。Googleは2021年2月に設立された独立非営利団体「Rust Foundation」にも加盟している。
Androidはこれまで、「C」や「C++」といったシステムプログラミング言語を用いて開発されてきた。Android開発にRustを導入した目的は、メモリ安全性のバグを予防することにある。
メモリ安全性のバグを生み出す危険なコードとは 信頼できない入力を処理する(緑色)、安全性の低いC/C++で記述する(オレンジ色)、サンドボックスを使った分離を実行していない(青色)(出典:Google)
AOSPはこれまでもメモリ安全性のバグの検出や修正、軽減に注力してきたが、さらに予防を強化しようとしている。メモリ安全性を特徴とした言語の採用が、最も費用対効果の高い予防方法だとの認識から、Rustの導入に至った。
Android開発に新しい言語を取り入れるのは大掛かりな取り組みであり、ツールチェーンや依存関係のメンテナンス、インフラやツールのアップデート、開発者のトレーニングが必要になる。
AOSPは過去18カ月にわたってRust導入に取り組んできた。現在、幾つかの早期導入プロジェクトを進めており、数カ月後に成果を発表する計画だ。この取り組みを発展させ、Android開発にRustを本格的に活用するのは、数年がかりのプロジェクトになるとしている。
GoogleはRustを導入した背景としてメモリ安全性バグの脅威が最大であることや、これまでのシステム開発言語では開発者にメモリのライフサイクルが任されていること、サンドボックス化ではメモリ安全性バグを除去できないことを挙げている。
厄介なメモリ安全性のバグ問題
CやC++プログラムのメモリ安全性のバグは以前から、最も対処が困難な動作異常の原因だった。AOSPはメモリ安全性を原因とするバグの検出や修正、軽減に多大な労力やリソースを注ぎ、Androidへのバグの混入を数多く防いできた。それでも、メモリ安全性のバグは、システムの安定性を損なう最大の原因であり続けており、Androidにおける深刻度の高いセキュリティ脆弱(ぜいじゃく)性の最大70%を占めている。
システムプログラミング言語とRustの特徴
Androidアプリケーションの開発では、「Java」や「Kotlin」のようなメモリ安全性を備えたマネージド言語が最適な選択肢となっている。これらの言語は使いやすさやポータビリティー、安全性を考慮して設計されており、Androidランタイム(ART)が開発者に代わってメモリを管理するからだ。
OSとしてのAndroidはJavaを幅広く利用し、Androidプラットフォームの大部分をメモリバグから効果的に保護している。だが、JavaとKotlinは、Android自体の低レベルな開発には適さない。
OSの低レベルの開発には、C、C++といったシステムプログラミング言語が必要になる。これらの言語は、低レベルのシステムリソースとハードウェアへのアクセスを可能にする。加えてリソース消費が少なく、パフォーマンスも予測しやすい。ただし、CとC++には課題もある。開発者自身がメモリのライフサイクルを管理しなければならないということだ。しかも管理は容易ではなく、ミスも発生しやすい。複雑なマルチスレッドのコードでは特にそうだ。
Rustは、コンパイル時のチェックによるオブジェクトライフサイクル/所有権の強制や、ランタイムチェックによるメモリアクセスの有効性の確保を通じて、メモリ安全性を保証する。さらに、CやC++と同等のパフォーマンスも実現する。
サンドボックス化の限界
CやC++では、Rustのような安全性を保証できないため、「強い分離」を必要とする。Androidの開発においては次のような制限がある。
コードがC/C++で作成されていて、信頼できない入力を解析する場合は、厳しい制約が課され、特権のないサンドボックスに収容されなければならない。この仕組みは、セキュリティ脆弱性の深刻度や影響の軽減に効果を発揮してきたが、限界もある。
まず、サンドボックス化はコストが高くつく。新しいプロセスが必要になり、そのために追加のオーバーヘッドが生じる他、IPC(プロセス間通信)と追加のメモリ使用に伴うレイテンシ(遅延)が起きるからだ。
さらにサンドボックス化は、コードから脆弱性を取り除くわけではなく、バグの密度が高いと、サンドボックス化の効力は薄れ、攻撃者が複数の脆弱性を連鎖させて、攻撃を仕掛けることが可能になってしまう。
Rustのようなメモリ安全性を特徴とした言語は、これらの限界の克服に役立つ。次の2つの働きがあるからだ。
- コード内のバグ密度を低下させ、現行のサンドボックス化の効果を高める
- サンドボックス化の必要性自体を下げ、より安全で、リソース消費の少ない新機能の導入を可能にする
既存のC/C++コードをどうすればよいのか
Androidチームのソフトウェアエンジニア全員を動員しても、Androidに含まれる数千万行のコード全体をRustで書き換えることは、現実的ではない。
そこで重点的な対策を取るという。Androidのメモリ安全性のバグには分布に偏りがあるからだ。
メモリ安全性のバグが、どのコードに含まれていたのかを調べたところ、次の図のように、導入後1年に満たないコードのメモリ安全性のバグが、メモリ安全性のバグ全体の約5割を占めていた。この割合は、年数が長くなるほど小さくなることも分かった。
つまり、成熟したC/C++コードの書き換えではなく、コードの新規作成時にメモリ安全性の高い言語を導入することが最善の策となる。
メモリ安全性のバグの発生頻度が、古いコードほど低いのは、ソフトウェアのバグは時間の経過とともに見つかり、修正されるためだ。
バグ検出の制約と限界
厳しいテストやサニタイズ、ファジングといった手法を用いてバグを検出することは、Rustで作成されたソフトウェアを含め、あらゆるソフトウェアの品質と正確性を高める上で、極めて重要だ。
だが、これらの手法はメモリ安全性のバグには役立たないこともある。なぜならバグを検出するには、対象コードで実際にエラー状態をトリガーできなければならないからだ。コードベースをテストやファジングによって適切にカバーしても、この制約によって、多くのバグが検出されないままになってしまう。
この他、バグ検出に修正が追い付かない場合もある。バグ修正は、コストのかかる長いプロセスであるため、一部のプロジェクトでは、バグが検出されても、必ず修正されるとは限らない。
さらに、複雑なC/C++コードベースに対しては、修正コードの作成やレビューを実行できる開発者が、足りない場合もよくある。せっかくバグの修正に力を注いでも、修正自体に誤りがある場合も珍しくない。
バグ検出が最も効果的なのは、バグの数が比較的少なく、危険なバグの修正を、緊急かつ優先的に進めることができる場合だ。つまり、バグ検出と修正の流れを改善するには、新しいバグを予防することに重点を置くとよい。
バグ予防に効果的なモダン言語
Rustは新しいバグを予防する際に役立つという。さまざまな先進的な機能を備えており、コードの正確性を向上させるために役立つ。
- メモリ安全性 コンパイラとランタイムチェックの組み合わせにより、メモリ安全性を強制する
- データの並行性 データの競合を防止する
- 表現力の高い型システム 論理的なプログラミングバグの予防を助ける
- 参照と変数が既定で不変 最小権限の原則の徹底を支援する
- 標準ライブラリによる優れたエラー処理 Resultにおける呼び出しの潜在的な失敗をラップする
- 初期化の強制 全ての変数を使用前に初期化することを要求する
- 安全な整数処理 デバッグビルドでは、オーバーフローサニタイズが既定でオンになっている
Copyright © ITmedia, Inc. All Rights Reserved.
関連記事
- 「Rust」が開発者に最も愛される言語であり続ける理由――Rustを取り巻く状況はここ1年でどう変わったのか
人気記事を電子書籍化して無料ダウンロード提供する@IT eBookシリーズ。第69弾は、プログラミング言語「Rust」のニュース記事をeBookにまとめてお送りする。 - Rustの利用状況調査、ビジネス利用が進む一方で習得の難しさなどが依然課題
Rust Survey Teamはプログラミング言語「Rust」の利用状況に関する年次調査結果を発表した。Rustの安定性に対する評価が高い一方で、C++との相互運用性の向上や学習のしやすさを改善してほしいという回答が多かった。 - Rustを支援する独立非営利団体「Rust Foundation」が発足
プログラミング言語「Rust」とそのエコシステムを支援する独立非営利団体「Rust Foundation」の設立が発表された。巨大IT企業の金銭的な支援を受けており、「本番環境に対応できるエンタープライズ技術としてのRustの進化」を表しているという。