制御フロー整合性(CFI)は、コンパイル済みバイナリの元の制御フローグラフへの変更を許容しないセキュリティ メカニズムであり、そうした変更を行う攻撃を著しく困難にします。
Android 9 では、LLVM の CFI 実装がより多くのコンポーネントとカーネルで可能になりました。システム CFI はデフォルトで有効になっていますが、カーネル CFI は自分で有効にする必要があります。
LLVM の CFI は、リンク時最適化(LTO)を有効にしてコンパイルする必要があります。LTO は、リンク時までオブジェクト ファイルの LLVM ビットコード表現を保持します。これにより、コンパイラはどの最適化を実行できるかをより適切に判断できます。LTO を有効にすると、最終バイナリのサイズが小さくなり、パフォーマンスが向上しますが、コンパイル時間が長くなります。Android でのテストでは、LTO と CFI を組み合わせた場合、コードのサイズとパフォーマンスのオーバーヘッドは無視できるほどわずかであり、少数のケースでは両方とも改善が見られました。
CFI の技術的な詳細と、他の forward-control チェックの処理方法については、LLVM の設計ドキュメントをご覧ください。
実装
kCFI パッチは、サポートされているすべての Android カーネル バージョンに含まれています。CONFIG_CFI_CLANG
オプションは kCFI を有効にし、GKI でデフォルトで設定されます。
トラブルシューティング
有効にしたら、ドライバに存在する可能性がある型の不一致エラーを調べます。互換性のない関数ポインタによる間接的な関数呼び出しは、CFI エラーを発生させます。CFI エラーが検出されると、カーネルは、呼び出された関数と、失敗に至るスタックトレースの両方を含む警告を出力します。このエラーを解決するには、関数ポインタと呼び出される関数が常に同じ型を持つようにします。
CFI エラーのデバッグを行う際は、CONFIG_CFI_PERMISSIVE
を有効にすると便利です。これにより、カーネル パニックが発生する代わりに警告が出力されます。Permissive モードは、製品版では使用しないでください。
検証
現在、CFI 専用の CTS テストはありません。CFI がデバイスに影響しないことを確認するには、CFI の有効 / 無効にかかわらず CTS テストに合格するかどうかを検証します。