
# CVE-2021-0928を利用したAndroid 12 Betaのインストール済みアプリからシステム権限への権限昇格に関するWriteupとエクスプロイト `OutputConfiguration`における`writeToParcel`/`createFromParcel`のシリアライゼーション不一致を悪用した、Android 12 Betaのインストール済みアプリからシステム権限への権限昇格です。
CVE-2021-0928、android.hardware.camera2.params.OutputConfiguration における writeToParcel/createFromParcel のシリアライゼーション不一致
これは、その脆弱性を利用して、インストール済みのAndroidアプリからAndroid設定アプリ(または、インストール済みの他のアプリがAndroidManifest.xmlで宣言された<receiver>に送信できる任意のアプリ)への権限昇格を実現するエクスプロイトです。<activity>への送信による権限昇格も可能でしたが、ここでは紹介していません。
この問題は元々Android 12 Developer Preview 3で発見しました。
このリポジトリに含まれるエクスプロイト版は、Android 12 Beta 2および3で動作します。
この脆弱性は、最初の公式Android 12リリースで修正されました。
以下の解説は、このレポートを完全なエクスプロイトチェーンとして検討してもらうために、元々Google向けに書かれたものです。
執筆時点では、Android 12はAOSPで利用できませんでした(Android Developer Preview/Betaリリースはオープンソースではありません)。

AndroidでのIPCのほとんどは、Parcelと呼ばれるクラスを通じて行われます。
Parcelの基本的な使い方は以下の通りです。```java Parcel p = Parcel.obtain(); p.writeInt(1); p.writeString("Hello");
Then `Parcel` は [Binder を介して](https://developer.android.com/reference/android/os/IBinder#transact(int,%20android.os.Parcel,%20android.os.Parcel,%20int)) 別のプロセスに送信されます。あるいは、テスト目的で [`p.setDataPosition(0)`](https://developer.android.com/reference/android/os/Parcel#setDataPosition(int)) を呼び出して parcel を先頭位置に巻き戻し、読み取りを開始することもできます:```java
int a = p.readInt(); // a = 1
String b = p.readString(); // b = "Hello"
Parcelは内部的に読み取りが実行される位置を保持していることに注意すべきです。read*メソッドが以前に使用されたwrite*メソッドと一致することを保証するのはParcelクラスのユーザーの責任であり、そうでない場合、後続の読み取りはバッファ内の誤った位置から実行されます。
Parcelはカスタムオブジェクトを書き込む機能も提供しており、その推奨される方法はParcelableインターフェースを実装することです。
以下はParcelableインターフェースの実装例です(無関係なコードは削除済み、WindowContainerTransactionクラスはエクスプロイトでガジェットチェーンの一部として使用されていますが、それ自体に問題はありません)```java
package android.window;
public final class WindowContainerTransaction implements Parcelable {
private final ArrayMap<IBinder, Change> mChanges = new ArrayMap<>();
private final ArrayList mHierarchyOps = new ArrayList<>();
private WindowContainerTransaction(Parcel in) {
in.readMap(mChanges, null /* loader */);
in.readList(mHierarchyOps, null /* loader */);
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeMap(mChanges);
dest.writeList(mHierarchyOps);
}
@NonNull
public static final Creator<WindowContainerTransaction> CREATOR =
new Creator<WindowContainerTransaction>() {
@Override
public WindowContainerTransaction createFromParcel(Parcel in) {
return new WindowContainerTransaction(in);
}
};
}
上記のとおり、書き込み時には `writeToParcel()` メソッドが使用されます。そして読み取り時には `CREATOR.createFromParcel()` ファクトリメソッドが呼び出されます。`Parcelable` の実装は、`createFromParcel` が `writeToParcel` によって書き込まれたのと同じ量のデータを読み取ることを保証する責任があります。そうでない場合、その `Parcel` からの後続の読み取りはすべて、誤ったオフセットからデータを読み取ることになります。
このようなクラスは、以下の方法で `Parcel` への書き込み/読み取りが可能です:
* `obj.writeToParcel(parcel, 0)` / `obj = WindowContainerTransaction.CREATOR.createFromParcel()` を直接呼び出す。これは、クラスの型が既知である場合によく使用されます。例えば、`Parcelable` が別の `Parcelable` をフィールドとして持つ場合や、AIDL で生成されたコードで定義された RPC メソッドが `Parcelable` を引数として持つ場合などです
* `Parcel.writeParcelable`/`readParcelable` を介する方法。[`writeParcelable`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=1909;drc=58787794eb5c879f6e39ee58c0071b36e337f8e3) は最初にクラス名を書き込み、次に `Parcelable` インターフェースの `writeToParcel` メソッドを呼び出します。[`readParcelable`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=3282;drc=58787794eb5c879f6e39ee58c0071b36e337f8e3) は書き込まれたクラス名を読み取り、提供された `ClassLoader` または null が提供された場合は `BOOTCLASSPATH` からその名前のクラスを検索します。クラスが見つかると、その静的フィールド `CREATOR` を使用して、そのクラスの読み取りに使用されるファクトリである [`Parcelable.Creator`](https://developer.android.com/reference/android/os/Parcelable.Creator) インスタンスを取得します。`readParcelable` メソッドを使用する場合、作成されるオブジェクトの名前が同じ `Parcel` から読み取られるため、クラスパス内で利用可能な任意の `Parcelable` を読み取ることができることに注意することが重要です
* `readParcelable` は、他の多くの `Parcel` メソッドによって使用されます。例えば、上記の例で見られる `readList` は `readValue` を介して要素を読み取ります。`readValue` は `Parcel` 内でオブジェクトを転送する最も汎用的なメソッドであり、その使用方法の 1 つが `readParcelable` を介するものです。また、上記の例では、Java の型消去 (Type Erasure) により、`ArrayList<HierarchyOp> mHierarchyOps` フィールドには、ジェネリック型宣言で指定された型と互換性のあるオブジェクトだけでなく、Parcel がサポートする任意のオブジェクトを実際に含めることができます
# `writeToParcel`/`createFromParcel` の不一致
上記のとおり、`Parcelable` インターフェースの実装は、`createFromParcel` が、対応する `writeToParcel` が以前に書き込んだのと同じ量のデータを `Parcel` から読み取ることを保証する責任があります。`BOOTCLASSPATH` 内にこの契約に違反する可能性のある `Parcelable` が存在する場合、それは脆弱性を生み出します。なぜなら、以下のシナリオが可能になるからです:
1. 悪意のあるアプリが、欠陥のある `Parcelable` インスタンスと、ステップ 3 で実際に読み取られるがステップ 2 ではそのまま渡される特別に構築されたデータを含む `Bundle` または `Parcelable` を `system_server` に送信します
2. `system_server` は `Bundle` が安全であることを検証してからそれを転送するか、または `system_server` は提供された `Parcelable` を、次のパラメータで重要なデータも渡される AIDL メソッドに渡します (そのパラメータで受信したデータが変更可能な場合、セキュリティ問題が発生する可能性があります)
3. 別のアプリが `system_server` からデータを受信し、それを信頼します。しかし、欠陥のあるシリアライゼーションにより、実際に表示されるデータは `system_server` が送信しようとしたデータとは異なります
上記のステップで「または」を使用しました。これらのステップは、[2017 年に公開した任意の Activity を起動する古いエクスプロイトの亜種](https://github.com/michalbednarski/ReparcelBug) (「または」の左側) と、次のセクションで説明する新しい亜種の両方を説明しているためです。
# アプリ内での `BroadcastReceiver` の実行方法
Android SDK で利用可能な API を使用するアプリケーション開発者の観点から見ると、[`BroadcastReceiver`](https://developer.android.com/guide/components/broadcasts) の動作方法は、あるアプリケーションが [`sendBroadcast`](https://developer.android.com/reference/android/content/Context#sendBroadcast(android.content.Intent)) を呼び出し (ただし、多くの場合アプリはアプリからのブロードキャストではなくシステムからのブロードキャストを受信したいと考えています)、その後ブロードキャストされた `Intent` が `AndroidManifest.xml` で定義された `<receiver>` と照合されるというものです。これが発生すると、システムは受信アプリケーションのプロセスを開始し、`<receiver android:name>` 属性で定義された `BroadcastReceiver` サブクラスをインスタンス化して、[`onReceive`](https://developer.android.com/reference/android/content/BroadcastReceiver#onReceive(android.content.Context,%20android.content.Intent)) メソッドを呼び出します。
ブロードキャストを受信するプロセスで発生する `system_server` との通信を見てみましょう:
* アプリケーションプロセスが最初に開始されると、[`IActivityManager.attachApplication()` を呼び出します](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/ActivityThread.java;l=7340;drc=f53e23b917aa0f6a6310e46a233a29b6d6226b2c)。これにより、システムがアプリケーションプロセスに何をすべきかを指示するために使用する [`IApplicationThread`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/IApplicationThread.aidl) ハンドルを渡します
* システムがアプリケーションプロセスでマニフェスト登録された `BroadcastReceiver` を実行したい場合、前のポイントで説明した `IApplicationThread` を使用して [`scheduleReceiver`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/ActivityThread.java;l=950;drc=f53e23b917aa0f6a6310e46a233a29b6d6226b2c) メソッドを呼び出します。このメソッドには複数の引数がありますが、ここで最も重要なのは最初の 2 つです:
1. `Intent intent`。これは、`sendBroadcast()` が呼び出されたときに以前にシステムに渡されたものです
2. `ActivityInfo info`。実行する必要があるコンポーネントに関する情報が含まれています。このパラメータの値は、システムが Package Manager Service から取得します。最も重要なのは、このパラメータで渡されるデータに、受信したブロードキャストを処理する Java クラスがロードされるファイルへのパスが含まれていることです
この時点で、この新しいエクスプロイト経路が何であるかを推測できるでしょう: `sendBroadcast()` を呼び出して、システムが `scheduleReceiver` を呼び出そうとしたときに、`scheduleReceiver` が呼び出されるアプリケーションが改ざんされた `ActivityInfo` を認識するような `Intent` を渡します。
この新しいエクスプロイト経路は Android 12 で利用可能になったことに注意してください。以前は `Intent` に任意の `Parcelable` を配置する方法がなかったためです ([Intent の extras](https://developer.android.com/reference/android/content/Intent#putExtra(java.lang.String,%20android.os.Parcelable)) は、`Bundle` に配置され、その全体の長さが Parcel に書き込まれ、[単一のブロブとして読み取られる](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/BaseBundle.java;l=1671-1681;drc=5d123b67756dffcfdebdb936ab2de2b29c799321)ため、extras はそれらを含む `Intent` オブジェクトの誤解釈を引き起こすことはできないため、カウントされません)
# `writeToParcel`/`createFromParcel` の不一致のトリガー
ほとんどの場合、`writeToParcel`/`createFromParcel` の不一致は、これらのメソッドのいずれかでフィールドの 1 つが忘れられるか、2 回書き込まれるケースです。そのような場合、そのようなオブジェクトを送信すると常に不一致がトリガーされます。(これは、オブジェクトが `Parcelable` でありながら実際にはプロセス間で使用されていない場合に最も頻繁に発生します。そうでなければ、通常の使用中にすぐに気付かれるでしょう)
しかし今回はそうではなく、不一致のトリガーは明白ではありません。
脆弱なクラスを見てみましょう ([オリジナルはここにありました](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/hardware/camera2/params/OutputConfiguration.java;drc=46c390a1c695e2dc458cb889e40559f259f60aed)、`// New in Android 12` とマークされた行は、執筆時点で AOSP に存在しなかったため手動で追加されました) ([脆弱性を最初に導入したコミットはここにあります](https://android.googlesource.com/platform/frameworks/base/+/a69b1bc58b0838e06deefb190e226774a34671e6%5E%21/#F10)、ただしこれは Android 12 のリリース後に公開されました)```java
package android.hardware.camera2.params;
public final class OutputConfiguration implements Parcelable {
private OutputConfiguration(@NonNull Parcel source) {
int rotation = source.readInt();
int surfaceSetId = source.readInt();
int surfaceType = source.readInt();
int width = source.readInt();
int height = source.readInt();
boolean isDeferred = source.readInt() == 1;
boolean isShared = source.readInt() == 1;
ArrayList<Surface> surfaces = new ArrayList<Surface>();
source.readTypedList(surfaces, Surface.CREATOR);
String physicalCameraId = source.readString();
boolean isMultiResolution = source.readInt() == 1; // New in Android 12
ArrayList<Integer> sensorPixelModesUsed = new ArrayList<Integer>(); // New in Android 12
source.readList(sensorPixelModesUsed, Integer.class.getClassLoader()); // New in Android 12
// SNIP: copy values from variables set above to fields of this class
}
public static final @android.annotation.NonNull Parcelable.Creator<OutputConfiguration> CREATOR =
new Parcelable.Creator<OutputConfiguration>() {
@Override
public OutputConfiguration createFromParcel(Parcel source) {
try {
OutputConfiguration outputConfiguration = new OutputConfiguration(source);
return outputConfiguration;
} catch (Exception e) {
Log.e(TAG, "Exception creating OutputConfiguration from parcel", e);
return null;
}
}
@Override
public OutputConfiguration[] newArray(int size) {
return new OutputConfiguration[size];
}
};
@Override
public void writeToParcel(Parcel dest, int flags) {
if (dest == null) {
throw new IllegalArgumentException("dest must not be null");
}
dest.writeInt(mRotation);
dest.writeInt(mSurfaceGroupId);
dest.writeInt(mSurfaceType);
dest.writeInt(mConfiguredSize.getWidth());
dest.writeInt(mConfiguredSize.getHeight());
dest.writeInt(mIsDeferredConfig ? 1 : 0);
dest.writeInt(mIsShared ? 1 : 0);
dest.writeTypedList(mSurfaces);
dest.writeString(mPhysicalCameraId);
dest.writeInt(mIsMultiResolution ? 1 : 0); // New in Android 12
dest.writeList(mSensorPixelModesUsed); // New in Android 12
}
private ArrayList<Surface> mSurfaces;
private final int mRotation;
private final int mSurfaceGroupId;
private final int mSurfaceType;
private final Size mConfiguredSize;
private final int mConfiguredFormat;
private final int mConfiguredDataspace;
private final int mConfiguredGenerationId;
private final boolean mIsDeferredConfig;
private boolean mIsShared;
private String mPhysicalCameraId;
private boolean mIsMultiResolution; // New in Android 12
private ArrayList<Integer> mSensorPixelModesUsed; // New in Android 12
}
では、ここで何が問題で、なぜこの変更が脆弱性を引き起こすのでしょうか?WindowContainerTransactionのParcelableの例について説明した際に述べたように、readListは実際にはParcelでサポートされている任意のオブジェクトでリストを埋めることができ、ジェネリック宣言(ArrayList<Integer>)に一致するものだけではありません。ただし、このクラスをシリアライゼーションガジェットチェーンの一部として使用しているだけで、実際にはそのフィールドをParcelへの読み書き以外に使用していないため(そして、ジェネリック宣言に一致しない要素を含むArrayListの要素を使用しようとすると、いずれにせよClassCastExceptionが発生するだけです)、それ自体は問題ではありません。
このクラスにはcreateFromParcel内にtry-catchもあります。つまり、読み取り中にExceptionがスローされた場合、OutputConfigurationの読み取りは停止され、OutputConfigurationを含むオブジェクトの読み取りが続行されます。その場合、OutputConfiguration全体がParcelに書き込まれますが、Exceptionが発生した時点までしか読み取られません。これにより、OutputConfiguration.writeToParcel内で書き込まれた未消費データが、実際にはOutputConfiguration.CREATOR.createFromParcelを呼び出していたオブジェクトによって読み取られるため、不一致が発生します。
さて、これら2つの組み合わせ(Parcelでサポートされている任意のオブジェクトのネストを許可し、それを再スローなしのtry-catchでラップすること)により、system_serverによって書き込まれ、その後、system_serverによって転送されるParcelableを最初に構築したアプリによって制御される方法で読み取られるParcelableを構築する能力が得られます。
さて、そのようなParcelableを構築するには、mSensorPixelModesUsedに配置するものを見つける必要があります。それは、system_server内で正常に読み取られ(このオブジェクトは攻撃者アプリからParcelを通じて受信されるため)、system_serverによって正常に書き込まれ、その後、被害者アプリでアンパーセルに失敗してExceptionをスローするものです。
その方法の1つは、system_server内には存在するがアプリには存在しないクラスを使用して、逆シリアライズを試みるとClassNotFoundExceptionが発生するようにすることです。ただし、明示的にClassLoaderを指定せずにreadParcelableを使用すると、BOOTCLASSPATHのみが検索され、system_server固有のクラスは含まれないため、system_serverからParcelableを選択することはできません。その問題の解決策は、ObjectInputStreamがスタックトレース内の最初の非BOOTCLASSPATHメソッドからClassLoaderを選択するため、Serializableクラスの1つを使用することです。
PackageManagerExceptionを選択しましたが、使用する前にあと1つ行う必要があります。OutputConfigurationコンストラクタでreadListが呼び出されるとき、loader引数は明示的にInteger.class.getClassLoader()に設定されます。そのloader値はreadValue()に伝播され、次にreadSerializable()に渡され、readSerializable()内でloaderパラメータがnullでない場合、ObjectInputStreamのの代わりに使用されます(そのチェックは、がクラスを見つけられない場合、nullを返す代わりに例外をスローするため、何もしません)。ただし、回避策は非常に簡単です。を、を指定せずにを実行するでラップするだけです。ここで前述のクラスが登場します。
つまり、この時点で次のオブジェクトがあります:
OutputConfiguration
mSensorPixelModesUsed.get(0) = WindowContainerTransaction
mHierarchyOps.get(0) = PackageManagerExceptionこのようなオブジェクトはsystem_server内で正常に逆シリアライズできます:WindowContainerTransactionがreadListを呼び出すとき、システムサーバーのClassLoader(BootClassLoaderではない)を使用してPackageManagerExceptionクラスを見つけようとします。スタックトレース内でそれを見つけることができるからです。そのクラスローダーがスタックトレース内に存在するのは、以下のすべてのメソッドがシステムサーバーのクラスパスからのものではない一方で(Binder#execTransact()、AIDLによって生成されたIActivityManager$Stub#onTransact()、および使用されたすべてのParcelableクラスのメソッド)、スタックトレース内にシステムサーバー内で宣言されたメソッドがあったためです:ActivityManagerService内のオーバーライドされたonTransact。したがって、system_serverはそのようなオブジェクトを読み取り、後でに書き込むことができ、ターゲットアプリがそれを読み取ろうとすると、クラスが利用できないためがスローされ、、その後。
これでこの不一致を引き起こしました。まあ、この時点ではまだ実際にはそうではありません。なぜなら、OutputConfiguration.writeToParcelによって未読データが残っていないときに例外がキャッチされたからです。ただし、mSensorPixelModesUsedのListに別のアイテムを簡単に追加でき、そのアイテムはParcel.writeValueを通じて書き込まれ、OutputConfigurationの読み取り後に未読のまま残ります。
Intentへの組み込み前述のように、Intentオブジェクトから不一致を引き起こしたいと思います。これは、system_serverによって、最初のパラメータにIntentがあり、2番目のパラメータに実行情報があるAIDLメソッドに渡されるため、最初のパラメータで渡されたIntentのシリアライズ/デシリアライズが2番目のパラメータの値の変更につながるからです。
Intent.readFromParcel()では、すべての値が専用の型付きメソッドを通じて読み取られるため、カスタムParcelableクラスを指定することはできません。
ただし、Intent内にはネストされたClipDataがあり、Android 12以降、ClipData$Itemには新しいフィールドActivityInfo mActivityInfoがあります(当初の執筆時点ではAOSPに存在しませんでした。そのフィールドを導入したコミットはこちら)。このフィールドはClipData(Parcel in)コンストラクタ内のin.readTypedObject(ActivityInfo.CREATOR)を通じて読み取られます。
次に、ActivityInfo(Parcel source)コンストラクタ内でも、カスタムParcelableを配置する方法はありませんが、ActivityInfoはComponentInfoを拡張しているため、applicationInfoフィールドがあります。
最後に、ApplicationInfoにはSparseArray<int[]> splitDependenciesフィールドがあり、これはreadSparseArrayを通じて読み取られ、さらにreadValueを使用してSparseArrayのアイテムを読み取ります。
この時点でsplitDependencies内にOutputConfigurationを配置できますが、splitDependenciesの読み取り後にはいくつかのreadString8()呼び出しが続き、不一致が発生した後の未消費データを完全に制御して、そこに空の文字列を直接配置し、未消費データの異なる解釈を心配する必要がないようにするのが理想的です。
そのためには、まずOutputConfiguration.mSensorPixelModesUsed内にwriteValueを通じて書き込まれる生データコンテナを配置する必要があります。Bundleを選択しました。これにより、未消費データには次のものが残ります:
writeValueのVAL_BUNDLEタグBUNDLE_MAGICParcel.appendFromを通じてそのまま渡される生データつまり、未消費のParcel.writeIntアイテムが3つあります。OutputConfigurationを、読み取り中に任意のParcelable値とそれに続く3つのintを読み取るParcelableでラップすることで、これらを取り除くことができます。これはZenPolicy CREATORで見つけました。
まとめると、次のオブジェクト階層があります(これはsystem_server内に存在し、scheduleReceiverに渡そうとしているものです)
Intent
mClipData = ClipData
mItems.get(0).mActivityInfo = ActivityInfo
applicationInfo = ApplicationInfo
splitDependencies.get(0) = ZenPolicy
mVisualEffects.get(0) = OutputConfiguration
mSensorPixelModesUsed.get(0) = WindowContainerTransaction
mHierarchyOps.get(0) = PackageManagerExceptionmSensorPixelModesUsed.get(1) = Bundleこれはsystem_serverによって書き込まれます。次に、受信アプリケーションはPackageManagerExceptionのreadSerializableデータまで(およびそれを含めて)すべてを正常に読み取りますが、PackageManagerExceptionのSerializableデータが読み取られた後に例外がスローされ、OutputConfigurationより下のすべての読み取りがキャンセルされ、Bundleが未読のまま残ります。読み取りはZenPolicyに進み、Bundle内の生データに先行する3つのintを消費します。次に、ApplicationInfoの読み取りは、以前にBundle内でそのまま渡された生データであったデータの読み取りに進みます。その生データの読み取りは、このスタック内の残りのオブジェクト(ApplicationInfo、ActivityInfo、ClipData、)で続行され、その後、その生データは次のメソッドパラメータの読み取りに使用されます。
handleReceiver内で何が起こるか先ほど述べたように、残りのscheduleReceiverパラメータは攻撃者によって制御されたバッファから読み取られます。
そのメソッドが呼び出された後に何が起こるかを見てみましょう。
まず、scheduleReceiverはすべての引数の値をパックし、sendMessage()を使用して実行をメインスレッドに渡します。
次に、メインスレッドでhandleReceiverが呼び出されます。
handleReceiverはgetPackageInfoNoCheckを呼び出し、scheduleReceiver引数に渡されたActivityInfoの一部として受け取ったApplicationInfoを渡します。
getPackageInfoは、指定された名前のパッケージがキャッシュに既に存在するかどうかを確認し、存在しない場合は新しいLoadedApkインスタンスを構築し、以前に受け取ったApplicationInfoオブジェクトを渡します(攻撃者が新しいLoadedApkを構築させたいため、このプロセスで以前に見られなかったパッケージのpackageNameが使用されます)。
次に、ContextImpl.getClassLoader()メソッドが使用され、最初の実行ではmPackageInfo.getClassLoader()に委譲します。ここでmPackageInfoは前の段落で構築されたLoadedApkです。
次に、createOrUpdateClassLoaderLockedがあり、makePathsを呼び出してzipPathsにClassLoaderで使用されるパスを入力し、次にそれらは結合されてzip変数に割り当てられ、createClassLoaderに渡されます。
makePathsはApplicationInfoからの情報を使用してzipPathsを入力します。最も重要なのは、これにはsourceDirが含まれることです。攻撃者アプリケーションは、sourceDirを自身のapkへのパスに設定した注入されたApplicationInfoを作成するため、レシーバークラスは実際に攻撃者apkからロードされます。これは、ブロードキャストを受信するアプリケーション内での攻撃者制御コードの実行に直接つながります。
回避する必要があったもう1つのことがありました:非SDKインターフェース制限です。これらはセキュリティ境界として意図されたものではありませんでした(アプリケーションは常にNDKを使用して基盤となるsyscallを直接呼び出すことができるため)が、この場合、Parcelにデータを手動で書き込み、その後readParcelableを使用して細工されたClipDataを構築することで回避されました。そのようなClipDataは通常どおりIntentに添付し、sendBroadcast()に渡すことができたため、ブロードキャストの送信自体は公開APIのみを使用して行われました。
上記のレポートは元々Googleに送信され、その結果として複数の修正が行われたようです(直接の因果関係の証拠はありませんが)。
Android 12でリリースされたもの:
OutputConfigurationおよび関連クラスからの例外の飲み込みが削除されましたOutputConfiguration#mSensorPixelModesUsedはwriteValueを通じて書き込まれなくなりましたClipData#mActivityInfoは、書き込み中に明示的に要求されない限りParcelに書き込まれなくなりました(そのため、IntentにBOOTCLASSPATHからの任意のParcelableを含めることができなくなり、この悪用テクニックが排除されました)執筆時点でmasterブランチにのみ存在し、リリース版には含まれていないもの(おそらくAndroid 13に登場するでしょう。12Lには含まれません):
List読み取りメソッドがあり、型なしバージョンは非推奨としてマークされましたParcel#enforceNoDataAvail()があります。これはAIDLがRPC呼び出し引数を読み取った後に使用されるようです。通常、私のエクスプロイトは、Parcelからすべてのデータが読み取られた後、他のすべてが無視されるという事実に依存していましたが、これはもはや当てはまらないでしょう。ただし、多くの場合、最後に最終位置にシークするデータを構築できるため、これは強力な緩和策ではないと思います。いずれにせよ、このような問題が自然に発生して検出されないことがあるため、これでそれらを捕捉できるでしょう。これに関するさらなる議論はissue #3にありますBundle内のすべてのアイテムについて、その長さが個別に保存されます。 これにより、2014年から非公開でGoogleに報告していたバグクラス全体がほぼ完全に排除されます。2017年に公開された説明と約1年後のコード自体があります。正しく数えているなら、これはバグクラスの寿命が8年になることを意味します(正直なところ、これが多いのかどうかはわかりませんが、Bundle以外のエクスプロイトの亜種がまだ存在する可能性があります。例えば、まさにこれです(ただし、まさにこれはすでに修正されています))。resolveClassc != nullClass.forNamePackageManagerExceptionClassLoaderreadListParcelableWindowContainerTransactionParcelPackageManagerExceptionClassNotFoundExceptionIntenthandleReceiver