Scudo

Scudo は動的ユーザーモード メモリ アロケータつまりヒープ アロケータであり、パフォーマンスを低下させることなくヒープ関連の脆弱性(ヒープベースのバッファ オーバーフロー解放後の使用二重解放など)に対する抵抗力を強化するために設計されました。標準 C 割り当てと割り当て解除プリミティブ(malloc や free など)、C++ プリミティブ(new や delete など)を提供します。

Scudo は、AddressSanitizer(ASan)のような本格的なメモリエラー検出ツールではなく、問題の軽減を目的とするものです。

Android 11 以降では、scudo がすべてのネイティブ コードに使用されます(例外として、ローメモリ デバイスでは jemalloc が引き続き使用されます)。実行時には、Scudo によってすべての実行ファイルとそのライブラリ依存関係でネイティブなすべての割り当てと割り当て解除が処理され、ヒープ内で破損または不審な動作が検出されたときはプロセスが中止されます。

Android 10 では、.mk ファイルの LOCAL_SANITIZE := scudo オプションまたは .bp ファイルの sanitize: { scudo: true, } を設定して、バイナリ単位で scudo を有効にする必要があります。

Scudo はオープンソースで、LLVM の compiler-rt プロジェクトの一部です。ドキュメントは、https://llvm.org/docs/ScudoHardenedAllocator.html で入手できます。Scudo ランタイムは Android ツールチェーンの一部です。バイナリでアロケータを簡単に有効化できるようにするため、Soong と Make にサポートが追加されました。

アロケータ内で追加の軽減策を有効化または無効化するには、以下のオプションを使用します。

カスタマイズ

このアロケータの一部のパラメータは、プロセスごとに次の方法で定義できます。

  • 静的: 解析するオプション文字列を返す __scudo_default_options 関数をプログラム内で定義します。この関数はプロトタイプである extern "C" const char *__scudo_default_options() を持つ必要があります。
  • 動的: 解析するオプション文字列を含む環境変数 SCUDO_OPTIONS を使用します。この方法で定義したオプションは、__scudo_default_options による定義をすべてオーバーライドします。

使用できるオプションを以下に示します。

オプション 64 ビットのデフォルト 32 ビットのデフォルト 説明
QuarantineSizeKb 256 64 チャンクの実際の割り当て解除を遅延させるために使用される検疫のサイズ(KB 単位)。この値が小さいほどメモリ使用量は少なくなりますが、軽減の効果は低下します。負の値を指定すると、デフォルト値が使用されます。このオプションと ThreadLocalQuarantineSizeKb の両方をゼロに設定すると、検疫が完全に無効になります。
QuarantineChunksUpToSize 2048 512 検疫できるチャンクの最大サイズ(バイト単位)。
ThreadLocalQuarantineSizeKb 64 16 グローバル検疫をオフロードするためのスレッドごとのキャッシュのサイズ(KB 単位)。この値が小さいほどメモリ使用量は少なくなりますが、グローバル検疫の競合が増える可能性があります。このオプションと QuarantineSizeKb の両方をゼロに設定すると、検疫が完全に無効になります。
DeallocationTypeMismatch false false malloc/delete、new/free、new/delete[] のエラーレポートを有効にします。
DeleteSizeMismatch true true new と delete のサイズの不一致に関するエラーレポートを有効にします。
ZeroContents false false 割り当てと割り当て解除で、ゼロのチャンク コンテンツを有効にします。
allocator_may_return_null false false 回復可能なエラーが発生したとき、アロケータがプロセスを終了する代わりに null を返すよう指定します。
hard_rss_limit_mb 0 0 プロセスの RSS がこの制限に達すると、プロセスは終了します。
soft_rss_limit_mb 0 0 プロセスの RSS がこの制限に達すると、RSS が減少して新しい割り当てが可能になるまで、それ以降の割り当ては(allocator_may_return_null の値に応じて)失敗するかまたは null を返します。
allocator_release_to_os_interval_ms なし 5000 64 ビット アロケータにのみ影響します。設定すると、未使用のメモリを OS に解放しようとしますが、頻度がこの間隔(ミリ秒単位)を超えることはありません。値が負の場合、メモリは OS に解放されません。
abort_on_error true true 設定すると、ツールはエラー メッセージを出力した後、_exit() ではなく abort() を呼び出します。

検証

現在、Scudo 専用の CTS テストはありません。Scudo がデバイスに影響しないことを確認するには、特定のバイナリで Scudo を有効または無効にして CTS テストをパスするかどうかを検証してください。

トラブルシューティング

回復不能な問題が検出された場合、アロケータは標準エラー記述子にエラー メッセージを表示した後、プロセスを終了します。終了に至るまでのスタック トレースがシステムログに追加されます。通常、出力は Scudo ERROR: で始まり、その後に問題の簡単な概要がポインタとともに続きます。

現在のエラー メッセージとその考えられる原因のリストを次に示します。

  • corrupted chunk header: チャンク ヘッダーのチェックサム検証が失敗しました。考えられる原因は、ヘッダーが(部分的または全体的に)上書きされたことか、関数に渡されたポインタがチャンクでないかのいずれかです。
  • race on chunk header: 2 つの異なるスレッドが同じヘッダーを同時に操作しようとしています。これは、通常、競合状態の兆候であるか、そのチャンクでオペレーションを実行したときの全般的なロックの欠如を示します。
  • invalid chunk state: チャンクが特定のオペレーションで想定される状態にありません。たとえば、チャンクを解放しようとしているのに割り当てられていないか、リサイクルしようとしているのに検疫されていません。このエラーの典型的な原因は二重解放です。
  • misaligned pointer: 基本的な整列要件は厳格に適用されます。つまり、32 ビット プラットフォームでは 8 バイト、64 ビット プラットフォームでは 16 バイトです。関数に渡されたポインタがこの要件に適合しない場合、関数の 1 つに渡されたポインタは不整列になります。
  • allocation type mismatch: このオプションを有効にした場合、チャンクで呼び出される割り当て解除関数は、割り当てのために呼び出された関数の型と一致する必要があります。 この種類の不一致はセキュリティ問題を引き起こす可能性があります。
  • invalid sized delete: C++14 サイズの delete 演算子を使用し、オプションのチェックを有効にしている場合に、チャンクの割り当てを解除する際に渡されたサイズと、割り当ての際に要求されたサイズが一致しません。これは、通常は、コンパイラ問題または割り当て解除されるオブジェクトの型の混乱です。
  • RSS limit exhausted: オプションとして指定された RSS の最大値を超えました。

OS 自体でクラッシュをデバッグする場合は、HWASan OS ビルドを使用できます。アプリでクラッシュをデバッグする場合は、HWASan アプリのビルドも使用できます。