CSSエンジンは、ページにCSSルールを適用します。 これはDOMツリーを下降する下降プロセスです。親CSSを計算した後、子スタイルを独立して計算できます。並列計算に最適です。 2017年までに、MozillaはC ++を使用してスタイルシステムの並列化を2回試みました。 両方とも失敗しました。
量子CSSの開発により、生産性が向上し始めました。 セキュリティの改善は、良い副作用です。
メモリ保護と情報セキュリティのバグには一定の関係があります。 したがって、Rustを使用すると、Firefoxの攻撃対象領域が減少すると予想されました。 この記事では、2002年にFirefoxが最初にリリースされてからCSSエンジンで特定された潜在的な脆弱性について説明します。 次に、Rustで防止できるものとできないものを見てください。
FirefoxのCSSコンポーネントで常に69のセキュリティエラーが検出されています。 タイムマシンがあり、最初からRustを作成できれば、51(73.9%)のエラーは不可能になります。 Rustは優れたコードの記述を容易にしますが、絶対的な保護も提供しません。
さび
Rustは、タイプとメモリに対して安全な最新のシステムプログラミング言語です。 これらのセキュリティ保証の副作用として、Rustプログラムはコンパイル時にスレッドセーフでもあります。 したがって、Rustは特に次の用途に適しています。
- 信頼できない着信データの安全な処理。
- パフォーマンスを改善するための並行性;
- 既存のコードベースへの個々のコンポーネントの統合。
ただし、Rustは一部のエラークラス、特に正確性エラーを明示的に修正しません。 実際、エンジニアがQuantum CSSを書き直したときに、以前C ++コードで修正されていた重大なセキュリティエラーを誤って繰り返し、誤ってバグ修正641731を削除しました。 エラーはバグ1420001として再登録されました 。 履歴リークは、重大なセキュリティ脆弱性と評価されています。 最初の修正は、SVGドキュメントが画像であるかどうかを確認する追加のチェックでした。 残念ながら、コードを書き換える際にこのチェックは行われませんでした。
自動化されたテストでは、このように
:visited
たルールの違反を見つける必要がありますが、実際にはこのエラーは見つかりませんでした。 自動テストを高速化するために、この機能をテストするメカニズムを一時的に無効にしました。テストが実行されない場合、テストは特に役立ちません。 論理的なエラーを再実装するリスクは、テスト範囲を広くすることで低減できます。 しかし、新しい論理エラーの危険性はまだあります。
開発者がRustに慣れると、彼のコードはさらに安全になります。 Rustは考えられるすべての脆弱性を防ぐわけではありませんが、最も深刻なバグのクラス全体を修正します。
量子CSSセキュリティエラー
一般に、デフォルトでは、Rustはメモリ、境界、null /未初期化変数、整数オーバーフローに関連するエラーを防ぎます。 上記の非標準のバグは引き続き可能です。メモリ割り当ての失敗によりクラッシュが発生します。
カテゴリ別のセキュリティエラー
私たちの分析では、すべてのバグはセキュリティに関連していますが、公式の評価を受けたのは43のみです(「エクスプロイト可能性」に関する修飾された仮定に基づいてMozillaのセキュリティエンジニアによって割り当てられます)。 通常のバグは、機能の欠落や何らかの誤動作を示している可能性がありますが、必ずしもデータの漏洩や動作の変更につながるとは限りません。 公式のセキュリティエラーは、重要度が低い(攻撃対象領域に強い制限がある場合)から重大な脆弱性(攻撃者がユーザーのプラットフォームで任意のコードを実行できる可能性がある)にまで及びます。
- メモリー:32
- ボーダー:12
- 実装:12
- ヌル:7
- スタックオーバーフロー:3
- 整数オーバーフロー:2
- その他:1
多くの場合、メモリの脆弱性は深刻なセキュリティ問題として分類されます。 34の重大/深刻な問題のうち、32はメモリに関連していました。
セキュリティバグの重大度分布
- 合計:70
- セキュリティエラー:43
- クリティカル/深刻:34
- 修正さび:32
RustとC ++の比較
バグ955913 -
GetCustomPropertyNameAt
関数のヒープバッファオーバーフロー。 コードはインデックス付けに間違った変数を使用し、配列の終了後にメモリの解釈につながりました。 これにより、不良ポインタにアクセスしたり、メモリを別のコンポーネントに渡される文字列にコピーしたりするときにクラッシュが発生する場合があります。
すべてのCSSプロパティ (カスタムプロパティ 、カスタムプロパティを含む)の
mOrder
は、
mOrder
配列に格納されます。 各要素は、CSSプロパティ値、またはカスタムプロパティの場合は
eCSSProperty_COUNT
(非カスタムCSSプロパティの総数)で始まる値のいずれかで表されます。 カスタムプロパティの名前を取得するには、最初に
mOrder
から値を取得してから、
mVariableOrder
配列の対応するインデックスの名前にアクセスする必要があります。これは、カスタムプロパティの名前を順番に格納します。
脆弱なC ++コード:
void GetCustomPropertyNameAt(uint32_t aIndex, nsAString& aResult) const { MOZ_ASSERT(mOrder[aIndex] >= eCSSProperty_COUNT); aResult.Truncate(); aResult.AppendLiteral("var-"); aResult.Append(mVariableOrder[aIndex]);
この問題は、
aIndex
を使用して
mVariableOrder
配列の要素にアクセスするときに6行目で発生します。 事実は、
aIndex
は
mOrder
ではなく
mOrder
配列とともに使用する必要があるということです。
aIndex
で表されるカスタムプロパティに対応する要素は、実際には
mOrder[aIndex] - eCSSProperty_COUNT
です。
修正されたC ++コード:
void Get CustomPropertyNameAt(uint32_t aIndex, nsAString& aResult) const { MOZ_ASSERT(mOrder[aIndex] >= eCSSProperty_COUNT); uint32_t variableIndex = mOrder[aIndex] - eCSSProperty_COUNT; aResult.Truncate(); aResult.AppendLiteral("var-"); aResult.Append(mVariableOrder[variableIndex]); }
対応するRustコード
RustはC ++に多少似ていますが、他の抽象化とデータ構造を使用します。 RustコードはC ++とは大きく異なります(詳細については以下を参照)。 まず、脆弱なコードが文字通り可能な限り翻訳された場合に何が起こるか見てみましょう。
fn GetCustomPropertyNameAt(&self, aIndex: usize) -> String { assert!(self.mOrder[aIndex] >= self.eCSSProperty_COUNT); let mut result = "var-".to_string(); result += &self.mVariableOrder[aIndex]; result }
Rustコンパイラは、実行前にベクトルの長さを決定できないため、このコードを受け入れます。 長さがわかっている配列とは異なり、RustのVec型には動的なサイズがあります。 ただし、境界検証は標準ライブラリベクトルの実装に組み込まれています。 無効なインデックスが表示されると、プログラムは制御された方法で直ちに終了し、不正アクセスを防ぎます。
Quantum CSS の実際のコードは非常に異なるデータ構造を使用しているため、まったく同じものはありません。 たとえば、Rustの強力な組み込みデータ構造を使用して、配置とプロパティ名を統一します。 これにより、2つの独立したアレイを維持する必要がなくなります。 また、Rustデータ構造はデータのカプセル化を改善し、そのような論理エラーの可能性を減らします。 コードはブラウザの他の部分のC ++コードと対話する必要があるため、新しい
GetCustomPropertyNameAt
関数
GetCustomPropertyNameAt
Rustの慣用的なコードのようには見え
GetCustomPropertyNameAt
ん。 ただし、セキュリティのすべての保証は提供されますが、基礎となるデータのより理解しやすい抽象化が提供されます。
tl; dr
脆弱性は多くの場合メモリセキュリティ違反に関連しているため、Rustコードは重要なCVEの数を大幅に削減するはずです 。 しかし、Rustでさえ完璧ではありません。 開発者は、依然として正確性エラーとデータ漏洩攻撃を追跡する必要があります。 セキュリティで保護されたライブラリのサポートには、コードレビュー、テスト、ファジングが必要です。
コンパイラは、すべてのプログラマエラーをキャッチすることはできません。 それでも、Rustはメモリセキュリティの負担を肩から取り除き、コードの論理的な正確さに集中することができます。