UndefinedBehaviorSanitizer

UndefinedBehaviorSanitizer(UBSan)は、コンパイル時のインストルメンテーションを実行して、さまざまな種類の未定義の動作をチェックします。UBSan は多くの未定義の動作バグを検出できますが、Android でサポートしているのは以下のとおりです。

  • alignment
  • bool
  • bounds
  • enum
  • float-cast-overflow
  • float-divide-by-zero
  • integer-divide-by-zero
  • nonnull-attribute
  • null
  • return
  • returns-nonnull-attribute
  • shift-base
  • shift-exponent
  • signed-integer-overflow
  • unreachable
  • unsigned-integer-overflow
  • vla-bound

unsigned-integer-overflow は、技術的には未定義の動作ではないもののサニタイザーの対象に含まれており、潜在的な integer-overflow の脆弱性を排除するために、mediaserver コンポーネントを含む多くの Android モジュールで使用されています。

実装

Android ビルドシステムでは、グローバルまたはローカルで UBSan を有効にできます。UBSan をグローバルで有効にするには、Android.mk で SANITIZE_TARGET を設定します。UBSan をモジュール単位のレベルで有効にするには、LOCAL_SANITIZE を設定し、Android.mk で検索する未定義の動作を指定します。次に例を示します。

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_CFLAGS := -std=c11 -Wall -Werror -O0

LOCAL_SRC_FILES:= sanitizer-status.c

LOCAL_MODULE:= sanitizer-status

LOCAL_SANITIZE := alignment bounds null unreachable integer
LOCAL_SANITIZE_DIAG := alignment bounds null unreachable integer

include $(BUILD_EXECUTABLE)

同等のブループリント(Android.bp)設定は以下のとおりです。

cc_binary {

    cflags: [
        "-std=c11",
        "-Wall",
        "-Werror",
        "-O0",
    ],

    srcs: ["sanitizer-status.c"],

    name: "sanitizer-status",

    sanitize: {
        misc_undefined: [
            "alignment",
            "bounds",
            "null",
            "unreachable",
            "integer",
        ],
        diag: {
            misc_undefined: [
                "alignment",
                "bounds",
                "null",
                "unreachable",
                "integer",
            ],
        },
    },

}

UBSan のショートカット

Android には、一連のサニタイザーを同時に有効にする integerdefault-ub という 2 つのショートカットもあります。integer は integer-divide-by-zerosigned-integer-overflowunsigned-integer-overflow を有効にします。default-ub はコンパイラのパフォーマンスの問題を最小限に抑えるチェック(bool, integer-divide-by-zero, return, returns-nonnull-attribute, shift-exponent, unreachable and vla-bound)を有効にします。integer サニタイザー クラスは SANITIZE_TARGET と LOCAL_SANITIZE で使用できますが、default-ub は SANITIZE_TARGET でのみ使用できます。

エラーレポートの機能向上

Android のデフォルトの UBSan 実装は、未定義の動作を検出すると、指定された関数を呼び出します。デフォルトでは、この関数は abort です。ただし、2016 年 10 月以降、Android の UBSan にオプションのランタイム ライブラリが追加され、検出された未定義の動作の種類や、ファイルとソースコードの行情報を含む詳細なエラーレポートが出力できるようになりました。このエラーレポートを integer チェックで有効にするには、Android.mk ファイルに次の行を追加します。

LOCAL_SANITIZE:=integer
LOCAL_SANITIZE_DIAG:=integer

LOCAL_SANITIZE 値は、ビルド中にサニタイザーを有効にします。 LOCAL_SANITIZE_DIAG は、指定されたサニタイザーの診断モードを有効にします。LOCAL_SANITIZE と LOCAL_SANITIZE_DIAG を異なる値に設定することは可能ですが、LOCAL_SANITIZE のチェックのみが有効になります。LOCAL_SANITIZE でチェックが指定されておらず LOCAL_SANITIZE_DIAG で指定されている場合、そのチェックは有効にならず、診断メッセージは出力されません。

UBSan ランタイム ライブラリによって提供される情報の例を次に示します。

pixel-xl:/ # sanitizer-status ubsan
sanitizer-status/sanitizer-status.c:53:6: runtime error: unsigned integer overflow: 18446744073709551615 + 1 cannot be represented in type 'size_t' (aka 'unsigned long')

整数オーバーフローの無害化

意図的でない整数オーバーフローは、メモリアクセスやメモリ割り当てに関連する変数において、メモリの破損または情報漏洩の脆弱性の原因となる可能性があります。これに対処するため、Clang の UndefinedBehaviorSanitizer(UBSan)の符号付き / 符号なし整数オーバーフロー サニタイザーを Android 7.0 で追加し、メディア フレームワークを強化しました。Android 9 では、より多くのコンポーネントをカバーするため UBSan の拡張を行い、それに対するビルドシステムのサポートを向上させました。

これは、オーバーフローが発生した場合にプロセスを安全に中止するため、オーバーフローする可能性がある算術演算 / 算術命令のチェックを追加するものです。 上記のサニタイザーにより、整数オーバーフローが根本原因であるメモリの破損と情報漏洩の脆弱性全般(最初の Stagefright 脆弱性など)を軽減できます。

例とソース

コンパイラによって提供される Integer Overflow Sanitization(IntSan)は、算術オーバーフローを検出するため、コンパイル時にインストルメンテーションをバイナリに追加します。IntSan は、プラットフォーム全体のさまざまなコンポーネント(/platform/external/libnl/Android.bp など)で、デフォルトで有効になっています。

実装

IntSan では、UBSan の符号付き整数オーバーフロー サニタイザーと符号なし整数オーバーフロー サニタイザーを使用します。この脆弱性軽減機能は、モジュールごとに有効化されます。この機能は Android の重要なコンポーネントを安全に保護するものであり、無効にすることはできません。

追加のコンポーネントでも整数オーバーフロー サニタイズを有効にすることを強くおすすめします。権限を付与されたネイティブ コードや信頼できないユーザー入力を解析するネイティブ コードは、この機能を有効にするのに適しています。コードの使用量と算術演算の頻度に依存するサニタイザーには、わずかなパフォーマンス オーバーヘッドが伴います。オーバーヘッドの割合は少ないと予想されますが、パフォーマンスが懸念される場合はテストを実施してください。

makefile で IntSan をサポートする

makefile で IntSan を有効にするには、以下を追加します。

LOCAL_SANITIZE := integer_overflow
    # Optional features
    LOCAL_SANITIZE_DIAG := integer_overflow
    LOCAL_SANITIZE_BLOCKLIST := modulename_BLOCKLIST.txt
  • LOCAL_SANITIZE はサニタイザーのカンマ区切りのリストを受け取ります。integer_overflow は、個々の符号付き / 符号なし整数オーバーフロー サニタイザー用に事前にパッケージ化されたオプションのセットで、デフォルトの BLOCKLIST を備えています。
  • LOCAL_SANITIZE_DIAG はサニタイザーの診断モードを有効にします。診断モードはテスト時にのみ使用してください。診断モードではオーバーフローが発生してもプロセスが中止されず、セキュリティの脆弱性軽減のメリットが完全になくなるためです。詳細については、トラブルシューティングをご覧ください。
  • LOCAL_SANITIZE_BLOCKLIST に BLOCKLIST ファイルを指定し、関数とソースファイルがサニタイズされないようすることができます。詳細については、トラブルシューティングをご覧ください。

よりきめ細かい制御が必要な場合は、一方または両方のフラグを使用してサニタイザーを個別に有効にします。

LOCAL_SANITIZE := signed-integer-overflow, unsigned-integer-overflow
    LOCAL_SANITIZE_DIAG := signed-integer-overflow, unsigned-integer-overflow

ブループリント ファイルで IntSan をサポートする

ブループリント ファイル(/platform/external/libnl/Android.bp など)で整数オーバーフロー サニタイズを有効にするには、以下を追加します。

   sanitize: {
          integer_overflow: true,
          diag: {
              integer_overflow: true,
          },
          BLOCKLIST: "modulename_BLOCKLIST.txt",
       },

make ファイルの場合と同様、integer_overflow プロパティは、個別の符号付き / 符号なし整数オーバーフロー サニタイザー用に事前にパッケージ化されたオプションのセットで、デフォルトの BLOCKLIST を備えています。

プロパティの diag セットは、サニタイザーの診断モードを有効にします。診断モードはテスト時にのみ使用してください。診断モードではオーバーフローが発生してもプロセスが中止されず、ユーザービルドにおけるセキュリティの脆弱性軽減のメリットが完全になくなるためです。詳細については、トラブルシューティングをご覧ください。

BLOCKLIST プロパティで BLOCKLIST ファイルを指定することで、関数とソースファイルがサニタイズされないようにすることができます。詳細については、トラブルシューティングをご覧ください。

サニタイザーを個別に有効にするには、以下を使用します。

   sanitize: {
          misc_undefined: ["signed-integer-overflow", "unsigned-integer-overflow"],
          diag: {
              misc_undefined: ["signed-integer-overflow",
                               "unsigned-integer-overflow",],
          },
          BLOCKLIST: "modulename_BLOCKLIST.txt",
       },

トラブルシューティング

新しいコンポーネントで整数オーバーフロー サニタイズを有効にしている場合や、整数オーバーフロー サニタイズが行われたプラットフォーム ライブラリに依存している場合、無害な整数オーバーフローによりプロセスが中止される問題がまれに発生することがあります。無害なオーバーフローが確実に表面化するように、サニタイズを有効化したコンポーネントをテストしてください。

ユーザービルドのサニタイズで発生した中止を見つけるには、UBSan によってキャッチされたオーバーフローを示す Abort メッセージで SIGABRT クラッシュを検索します。例:

pid: ###, tid: ###, name: Binder:###  >>> /system/bin/surfaceflinger <<<
    signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
    Abort message: 'ubsan: sub-overflow'

スタック トレースには中止を引き起こした関数が含まれているはずですが、インライン関数で発生したオーバーフローはスタック トレースでは明らかにならない場合があります。

根本原因を簡単に特定するには、中止を引き起こしたライブラリで診断を有効にして、エラーの再現を試みます。診断を有効にするとプロセスは中止されず、引き続き実行されます。中止されないことにより、個々のバグを修正した後で再コンパイルする手間をかけずに、特定の実行パスで無害なオーバーフローが発生する回数を最大化できます。診断モードでは、中止の原因となった行番号とソースファイルを示すエラー メッセージが生成されます。

frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp:2188:32: runtime error: unsigned integer overflow: 0 - 1 cannot be represented in type 'size_t' (aka 'unsigned long')

問題のある算術演算が見つかったら、オーバーフローが無害で意図的なものである(セキュリティ上の影響がない)ことを確認します。サニタイザーの中止には次の方法で対処できます。

  • コードをリファクタリングしてオーバーフローを回避する(
  • Clang の __builtin_*_overflow 関数を使用して明示的にオーバーフローを発生させる(
  • no_sanitize 属性を指定して関数内のサニタイズを無効にする(
  • BLOCKLIST ファイルを使用して関数またはソースファイルのサニタイズを無効にする(

できる限りきめ細かい解決策を使用する必要があります。たとえば、多くの算術演算と一つのオーバーフロー演算を含む大規模な関数の場合は、関数全体を BLOCKLIST に登録するのではなく、一つの演算をリファクタリングします。

無害なオーバーフローが発生するよくあるパターンを次に示します。

  • 暗黙的なキャストで、符号付きの型にキャストされる前に符号なしオーバーフローが発生する(
  • リンクされたリストの削除で、削除時にループ インデックスが減少する(
  • 実際の最大値を指定する代わりに、符号なしの型を -1 に割り当てる(
  • ループの条件内で符号なし整数が減少する(

サニタイズを無効にする前に、意図しない副作用やセキュリティ上の影響がない、実際には無害なオーバーフローをサニタイザーが検出するケースを確認しておくことをおすすめします。

IntSan を無効にする

IntSan は BLOCKLIST または関数属性で無効にできます。無効化は、コードのリファクタリングが合理的でない場合や、パフォーマンス オーバーヘッドが問題になる場合に限り、慎重に行ってください。

関数属性 BLOCKLIST ファイル形式で IntSan を無効にする方法については、Clang のアップストリーム ドキュメントをご覧ください。BLOCKLIST に登録する際は、対象のサニタイザーを指定するセクション名を使用して特定のサニタイザーに限定し、その他のサニタイザーに影響しないようにする必要があります。

検証

現在のところ、整数オーバーフロー サニタイズ専用の CTS テストはありません。代替策として、IntSan が有効かどうかにかかわらず CTS テストに合格することを確認し、デバイスに影響しないことを検証します。

境界サニタイズ

BoundsSanitizer(BoundSan)は、バイナリにインストルメンテーションを追加して、配列アクセスのまわりに境界チェックを挿入します。これらのチェックは、アクセスが安全であることをコンパイラがコンパイル時に証明できない場合と、配列のサイズが実行時にわかり、チェック可能である場合に追加されます。Android 10 は、Bluetooth とコーデックで BoundSan をデプロイします。BoundSan はコンパイラによって提供され、プラットフォーム全体のさまざまなコンポーネントで、デフォルトで有効になっています。

実装

BoundSan は UBSan の境界サニタイザーを使用します。この緩和は、モジュール単位で有効になります。Android の重要なコンポーネントを安全に保護するものであり、無効にすることはできません。

追加のコンポーネントで BoundSan を有効にすることを強くおすすめします。権限を付与されたネイティブ コードまたは信頼できないユーザー入力を解析する複雑なネイティブ コードは、この機能を有効にするのに適しています。BoundSan の有効化に伴うパフォーマンス オーバーヘッドは、安全性を証明できない配列アクセスの数に依存します。平均的にオーバーヘッドの割合は小さいと予想し、パフォーマンスが問題になるかどうかをテストします。

ブループリント ファイルで BoundSan を有効にする

バイナリ モジュールとライブラリ モジュールの misc_undefined sanitize プロパティに "bounds" を追加することで、ブループリント ファイルで BoundSan を有効にできます。

    sanitize: {
       misc_undefined: ["bounds"],
       diag: {
          misc_undefined: ["bounds"],
       },
       BLOCKLIST: "modulename_BLOCKLIST.txt",
diag

diag プロパティはサニタイザーの診断モードを有効にします。診断モードはテスト時にのみ使用します。診断モードではオーバーフローが発生してもプロセスは中止されないため、セキュリティ リスク軽減のメリットがなく、パフォーマンスのオーバーヘッドも高くなるため、本番環境ビルドでは推奨されません。

BLOCKLIST

BLOCKLIST プロパティを使用すると、BLOCKLIST ファイルを指定し、関数とソースファイルがサニタイズされないようにすることができます。パフォーマンスが問題となっていて、対象のファイル / 関数が大きく影響している場合にのみ、このプロパティを使用します。これらのファイル / 関数を手動で監査して、配列アクセスが安全であることを確認します。詳細については、トラブルシューティングをご覧ください。

makefile で BoundSan を有効にする

バイナリ モジュールとライブラリ モジュールの LOCAL_SANITIZE 変数に "bounds" を追加することで、makefile で BoundSan を有効にできます。

    LOCAL_SANITIZE := bounds
    # Optional features
    LOCAL_SANITIZE_DIAG := bounds
    LOCAL_SANITIZE_BLOCKLIST := modulename_BLOCKLIST.txt

LOCAL_SANITIZE は、サニタイザーのカンマ区切りのリストを受け付けます。

LOCAL_SANITIZE_DIAG は診断モードをオンにします。診断モードはテスト時にのみ使用します。診断モードではオーバーフローが発生してもプロセスは中止されないため、セキュリティ リスク軽減のメリットがなく、パフォーマンスのオーバーヘッドも高くなるため、本番環境ビルドでは推奨されません。

LOCAL_SANITIZE_BLOCKLIST に BLOCKLIST ファイルを指定し、関数とソースファイルがサニタイズされないようにすることができます。パフォーマンスが問題となっていて、対象のファイル / 関数が大きく影響している場合にのみ、このプロパティを使用します。これらのファイル / 関数を手動で監査して、配列アクセスが安全であることを確認します。詳細については、トラブルシューティングをご覧ください。

BoundSan を無効にする

関数とソースファイルの BoundSan は BLOCKLIST または関数属性で無効にできます。ただし、BoundSan は常に有効にしておくことをおすすめします。無効にするのは、関数またはファイルが大きなパフォーマンス オーバーヘッドを引き起こしており、ソースを手動で確認した場合のみにします。

関数属性 BLOCKLIST ファイル形式で BoundSan を無効にする方法については、Clang LLVM のドキュメントをご覧ください。対象のサニタイザーを指定するセクション名を使用して、BLOCKLIST に登録する対象を特定のサニタイザーに限定し、その他のサニタイザーに影響しないようにする必要があります。

検証

BoundsSan 専用の CTS テストはありません。デバイスに影響しないことを確認するには、BoundSan の有効 / 無効にかかわらず CTS テストに合格するかどうかを検証します。

トラブルシューティング

BoundSan を有効にした後、コンポーネントを徹底的にテストして、以前に検出されなかった境界外のアクセスに対処していることを確認します。

BoundSan エラーには次の Tombstone 中止メッセージが含まれるため、簡単に特定できます。

    pid: ###, tid: ###, name: Binder:###  >>> /system/bin/foobar <<<
    signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
    Abort message: 'ubsan: out-of-bounds'

診断モードで実行すると、ソースファイル、行番号、インデックス値が logcat に出力されます。デフォルトでは、このモードは中止メッセージをスローしません。logcat を調べて、エラーがないか確認します。

    external/foo/bar.c:293:13: runtime error: index -1 out of bounds for type 'int [24]'