
Writeup y exploit para escalada de privilegios de una app instalada a nivel de sistema en Android 12 Beta mediante CVE-2021-0928, una discrepancia de serialización `writeToParcel`/`createFromParcel` en `OutputConfiguration`
CVE-2021-0928, discrepancia de serialización writeToParcel/createFromParcel en android.hardware.camera2.params.OutputConfiguration
Este es un exploit que utiliza esa vulnerabilidad para escalar privilegios desde una app de Android instalada hacia la app de Configuración de Android (o cualquier otra app instalada a la que se pudiera enviar un <receiver> declarado en AndroidManifest.xml; la escalada de privilegios enviando a un <activity> también era posible, aunque no se presenta aquí)
Encontré el problema originalmente en Android 12 Developer Preview 3
La versión del exploit presente en este repositorio funciona en Android 12 Beta 2 y 3
La vulnerabilidad se corrigió en la primera versión oficial de Android 12
El informe que aparece a continuación fue escrito originalmente para Google para que este informe se considerara como una cadena de exploit completa
En el momento de redactar este informe, Android 12 no estaba disponible en AOSP (las versiones Developer Preview/Beta de Android no son de código abierto)

La mayor parte de la IPC en Android se realiza a través de la clase llamada Parcel
El uso básico de Parcel es el siguiente:```java Parcel p = Parcel.obtain(); p.writeInt(1); p.writeString("Hello");
Entonces `Parcel` se envía a otro proceso [a través de `Binder`](https://developer.android.com/reference/android/os/IBinder#transact(int,%20android.os.Parcel,%20android.os.Parcel,%20int)). Alternativamente, para pruebas se puede llamar a [`p.setDataPosition(0)`](https://developer.android.com/reference/android/os/Parcel#setDataPosition(int)) para rebobinar el parcel a la posición inicial y comenzar a leer:```java
int a = p.readInt(); // a = 1
String b = p.readString(); // b = "Hello"
Debe tenerse en cuenta que Parcel internamente mantiene la posición desde la cual se realizan las lecturas. Es responsabilidad del usuario de la clase Parcel asegurarse de que los métodos read* coincidan con los métodos write* utilizados previamente; de lo contrario, las lecturas posteriores se realizarán desde posiciones incorrectas en el búfer.
Parcel también proporciona la capacidad de escribir objetos personalizados; la forma preferida de hacerlo es implementando la interfaz Parcelable.
Aquí hay un ejemplo de implementación de la interfaz Parcelable (se eliminó código irrelevante; la clase WindowContainerTransaction se usa en el exploit como parte de la cadena de gadgets, sin embargo, no hay nada incorrecto en ella).```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);
}
};
}
Como se puede ver arriba, el método `writeToParcel()` se utiliza durante la escritura. Luego, al leer, se llama al método de fábrica `CREATOR.createFromParcel()`. Es responsabilidad de la implementación de `Parcelable` asegurarse de que `createFromParcel` lea la misma cantidad de datos que fue escrita por `writeToParcel`; de lo contrario, todas las lecturas posteriores de ese `Parcel` leerán datos desde un offset incorrecto.
Dicha clase se puede escribir/leer desde/hacia `Parcel` mediante:
* Llamando directamente a `obj.writeToParcel(parcel, 0)` / `obj = WindowContainerTransaction.CREATOR.createFromParcel()`, esto se usa a menudo cuando se conoce el tipo de la clase, por ejemplo cuando `Parcelable` tiene un campo con otro `Parcelable` o en código generado por AIDL cuando el método RPC definido tiene un `Parcelable` como argumento
* A través de `Parcel.writeParcelable`/`readParcelable`. [`writeParcelable`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=1909;drc=58787794eb5c879f6e39ee58c0071b36e337f8e3) primero escribe el nombre de la clase y luego llama al método `writeToParcel` de la interfaz `Parcelable`. [`readParcelable`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=3282;drc=58787794eb5c879f6e39ee58c0071b36e337f8e3) lee el nombre de clase escrito, encuentra la clase con ese nombre en el `ClassLoader` proporcionado o en `BOOTCLASSPATH` si se proporcionó null. Una vez que se encuentra la clase, se usa su campo estático `CREATOR` para obtener una instancia de [`Parcelable.Creator`](https://developer.android.com/reference/android/os/Parcelable.Creator), que es la fábrica utilizada para leer esa clase. Es importante notar que cuando se usa el método `readParcelable`, puede leer cualquier `Parcelable` disponible en el classpath, ya que el nombre del objeto a crear se lee del mismo `Parcel`
* `readParcelable` es utilizado por muchos otros métodos de `Parcel`, por ejemplo `readList` visto en el ejemplo anterior lee elementos a través de `readValue`, que es el método más genérico para transferir objetos en `Parcel` y una de las formas que usa es a través de `readParcelable`. Además, en el ejemplo anterior, debido al Type Erasure de Java, el campo `ArrayList<HierarchyOp> mHierarchyOps` puede contener en realidad cualquier objeto soportado por Parcel, no solo aquellos compatibles con el tipo especificado en la declaración del tipo genérico
# Desajustes de `writeToParcel`/`createFromParcel`
Como se señaló anteriormente, es responsabilidad de la implementación de la interfaz `Parcelable` asegurarse de que `createFromParcel` lea la misma cantidad de datos del `Parcel` que el `writeToParcel` correspondiente escribió previamente. Siempre que haya en `BOOTCLASSPATH` un `Parcelable` que pueda violar ese contrato, se crea una vulnerabilidad, ya que permite el siguiente escenario:
1. Una aplicación maliciosa envía a `system_server` un `Bundle` O un `Parcelable` que contiene una instancia `Parcelable` defectuosa junto con datos específicamente construidos que se leerán realmente en el paso 3 pero se pasarán textualmente durante el paso 2
2. `system_server` verifica que el `Bundle` es seguro y luego lo reenvía O `system_server` pasa el `Parcelable` proporcionado a un método AIDL que también tiene datos críticos pasados en el siguiente parámetro (si los datos recibidos en ese parámetro pudieran modificarse, eso causaría un problema de seguridad)
3. Otra aplicación recibe datos de `system_server` y confía en ellos; sin embargo, debido a la serialización defectuosa, los datos que realmente ve difieren de los datos que `system_server` pretendía enviar
He usado "O" en los pasos anteriores porque estos pasos describen tanto una [variante de exploit antigua que lleva a iniciar una Activity arbitraria que publiqué en 2017](https://github.com/michalbednarski/ReparcelBug) (en el lado izquierdo de "O") como una nueva variante que describiré aquí en la siguiente sección
# Cómo se ejecuta `BroadcastReceiver` en la aplicación
Desde el punto de vista del desarrollador de aplicaciones que usa las APIs disponibles en el Android SDK, la forma en que funciona [`BroadcastReceiver`](https://developer.android.com/guide/components/broadcasts) es que una aplicación llama a [`sendBroadcast`](https://developer.android.com/reference/android/content/Context#sendBroadcast(android.content.Intent)) (aunque a menudo las aplicaciones quieren recibir Broadcasts del sistema, no de la aplicación) y luego el Intent transmitido se compara con el `<receiver>` definido en `AndroidManifest.xml`; cuando eso sucede, el sistema inicia el proceso de la aplicación receptora, instancia la subclase de `BroadcastReceiver` según lo definido en el atributo `<receiver android:name>` y luego llama al método [`onReceive`](https://developer.android.com/reference/android/content/BroadcastReceiver#onReceive(android.content.Context,%20android.content.Intent))
Echemos un vistazo a la comunicación con `system_server` que ocurre en el proceso que recibe la transmisión:
* Cuando el proceso de la aplicación se inicia inicialmente, [llama a `IActivityManager.attachApplication()`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/ActivityThread.java;l=7340;drc=f53e23b917aa0f6a6310e46a233a29b6d6226b2c), al hacerlo pasa el handle de [`IApplicationThread`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/IApplicationThread.aidl) que el sistema usa para indicarle al proceso de la aplicación qué hacer
* Cuando el sistema quiere ejecutar un `BroadcastReceiver` registrado en el manifiesto en el proceso de la aplicación, llama al método [`scheduleReceiver`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/ActivityThread.java;l=950;drc=f53e23b917aa0f6a6310e46a233a29b6d6226b2c) usando el `IApplicationThread` descrito en el punto anterior. Este método tiene múltiples argumentos, pero aquí los más importantes son los dos primeros:
1. `Intent intent`, que se pasó previamente al sistema cuando se llamó a `sendBroadcast()`
2. `ActivityInfo info`, que contiene información sobre el componente que debe ejecutarse. El valor de este parámetro lo toma el sistema del Package Manager Service. Lo más importante es que los datos pasados en este parámetro incluyen la ruta al archivo desde el cual se cargará la clase Java que maneja la transmisión recibida
En este punto probablemente puedas adivinar cuál es esta nueva vía de exploit: llamar a `sendBroadcast()` pasando un `Intent` que hará que, cuando el sistema intente llamar a `scheduleReceiver`, cause que la aplicación en la que se invoca `scheduleReceiver` vea un `ActivityInfo` manipulado
Cabe señalar que esta nueva vía de exploit se volvió viable en Android 12, ya que anteriormente no había forma de poner `Parcelable`s arbitrarios en un `Intent` (los [extras de Intent](https://developer.android.com/reference/android/content/Intent#putExtra(java.lang.String,%20android.os.Parcelable)) no cuentan, ya que se colocan en un `Bundle` cuya longitud completa se escribe en el Parcel y se [lee como un solo blob](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/BaseBundle.java;l=1671-1681;drc=5d123b67756dffcfdebdb936ab2de2b29c799321), por lo que los extras no pueden causar una mala interpretación del objeto `Intent` que los contiene)
# Desencadenando el desajuste de `writeToParcel`/`createFromParcel`
La mayoría de las veces, los desajustes de `writeToParcel`/`createFromParcel` son casos en los que en uno de estos métodos uno de los campos se olvida o se escribe dos veces; en tal caso, enviar dicho objeto siempre desencadenará el desajuste. (La mayoría de las veces eso sucede cuando el objeto, aunque es `Parcelable`, realmente no se usa entre procesos; de lo contrario, eso se notaría rápidamente durante el uso normal)
Esta vez, sin embargo, ese no fue el caso y desencadenar el desajuste no es obvio
Echemos un vistazo a la clase vulnerable ([el original estaba aquí](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/hardware/camera2/params/OutputConfiguration.java;drc=46c390a1c695e2dc458cb889e40559f259f60aed), las líneas marcadas con `// New in Android 12` se agregaron manualmente ya que no estaban presentes en AOSP en el momento de escribir esto) ([Aquí está el commit que introdujo originalmente la vulnerabilidad](https://android.googlesource.com/platform/frameworks/base/+/a69b1bc58b0838e06deefb190e226774a34671e6%5E%21/#F10), sin embargo, se publicó después del lanzamiento de 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
}
Entonces, ¿qué es lo que falla aquí y por qué este cambio introduce una vulnerabilidad? Como dije al hablar del ejemplo de WindowContainerTransaction como Parcelable, readList puede llenar la lista con cualquier objeto compatible con Parcel, no solo con los que coinciden con la declaración genérica (ArrayList<Integer>). Sin embargo, como solo usamos esta clase como parte de una cadena de gadgets de serialización y no la usamos realmente, ese campo no se usará para nada más que para leer y escribir en Parcel (y los intentos de usar elementos de ArrayList que contengan elementos que no coincidan con su declaración genérica solo llevarían a una ClassCastException de todos modos), eso no es un problema en sí mismo.
En esta clase también hay un try-catch dentro de createFromParcel, lo que significa que si durante la lectura se lanza una Exception, la lectura de OutputConfiguration se detendrá y la lectura del objeto que contiene OutputConfiguration continuará. Cuando eso sucede, todo el OutputConfiguration se escribirá en el Parcel, pero se leerá solo hasta el punto en que ocurrió la Exception. Esto crea un desajuste, ya que los datos no consumidos escritos dentro de OutputConfiguration.writeToParcel serán realmente leídos por el objeto que estaba llamando a OutputConfiguration.CREATOR.createFromParcel.
Ahora, la combinación de estos dos (permitir anidar objetos arbitrarios compatibles con Parcel y envolver eso dentro de un try-catch sin relanzar) da la capacidad de construir un Parcelable que puede ser escrito por system_server y luego leído de una manera controlada por la aplicación que inicialmente construyó el Parcelable que está siendo reenviado por system_server.
Bien, entonces para construir tal Parcelable ahora necesitamos encontrar algo para poner en mSensorPixelModesUsed que se lea con éxito en system_server (ya que este objeto se recibe de la aplicación atacante a través de Parcel), que system_server escriba con éxito y luego falle al deserializar y lance una Exception en la aplicación víctima.
Una de las formas de hacerlo es usar una clase que esté presente dentro de system_server pero no en las aplicaciones, de modo que intentar deserializarla conduzca a una ClassNotFoundException. Sin embargo, no puedo elegir un Parcelable de system_server como readParcelable sin un ClassLoader especificado explícitamente, ya que solo buscará en BOOTCLASSPATH, que no contendrá clases específicas de system_server. La solución a ese problema es usar una de las clases Serializable, ya que ObjectInputStream tomará el ClassLoader del primer método que no esté en BOOTCLASSPATH en el stack trace.
He elegido PackageManagerException, sin embargo, antes de usarlo hay una cosa más que debemos hacer. En el constructor de OutputConfiguration, cuando se llama a readList, el argumento loader se establece explícitamente a Integer.class.getClassLoader(). Ese valor de loader se propaga a readValue(), luego a readSerializable() y dentro de readSerializable() si el parámetro loader no es nulo, se usa en lugar de de (esa comprobación no hace nada porque cuando no encuentra la clase, lanzará una excepción en lugar de devolver null). La forma de evitarlo es bastante simple, solo necesitamos envolver en algún que haga sin especificar un . Aquí es donde entra la clase descrita anteriormente.
Entonces, en este punto tenemos el siguiente objeto:
OutputConfiguration
mSensorPixelModesUsed.get(0) = WindowContainerTransaction
mHierarchyOps.get(0) = PackageManagerExceptionAhora, tal objeto puede deserializarse con éxito dentro de system_server: cuando WindowContainerTransaction llama a readList, intentará encontrar la clase PackageManagerException usando el ClassLoader de system_server (no BootClassLoader), ya que puede encontrarlo en el stack trace. Ese class loader resulta estar presente en el stack trace porque, aunque todos los siguientes métodos no eran de la ruta de clases de system_server: Binder#execTransact(), IActivityManager$Stub#onTransact() generado por AIDL y métodos de todas las clases Parcelable utilizadas, había un método declarado dentro de system_server en el stack trace: un onTransact anulado dentro de ActivityManagerService. Por lo tanto, puede leer y luego escribir tal objeto en un , y cuando la aplicación objetivo intente leerlo, la clase no estará disponible y, por lo tanto, se lanzará una , y luego .
Así que hemos provocado este desajuste. Bueno, en realidad todavía no en este punto, porque la excepción se capturó cuando no quedaban datos sin leer por OutputConfiguration.writeToParcel, pero podemos agregar fácilmente otro elemento a la List de mSensorPixelModesUsed y ese elemento se escribirá a través de Parcel.writeValue y quedará sin leer después de leer OutputConfiguration.
IntentComo se señaló anteriormente, querré provocar el desajuste desde el objeto Intent, ya que será pasado por system_server a un método AIDL que tiene Intent en el primer parámetro e información de ejecución en el segundo parámetro, de modo que la serialización/deserialización del Intent pasado en el primer parámetro conduzca a la modificación del valor en el segundo parámetro.
En Intent.readFromParcel() todos los valores se leen a través de métodos tipados dedicados, por lo que no podemos especificar una clase Parcelable personalizada allí.
Dentro de Intent, sin embargo, hay un ClipData anidado y desde Android 12 en ClipData$Item hay un nuevo campo ActivityInfo mActivityInfo (no estaba presente en AOSP en el momento de la redacción inicial, aquí está el commit que introduce ese campo, este campo se lee a través de in.readTypedObject(ActivityInfo.CREATOR) dentro del constructor ClipData(Parcel in)).
Luego, dentro del constructor ActivityInfo(Parcel source) tampoco hay forma de poner un Parcelable personalizado, pero como ActivityInfo extiende de ComponentInfo, tiene el campo applicationInfo.
Finalmente, dentro de ApplicationInfo hay un campo SparseArray<int[]> splitDependencies, que se lee a través de readSparseArray, que a su vez usa readValue para leer los elementos de SparseArray.
En este punto podríamos colocar OutputConfiguration dentro de splitDependencies, sin embargo, la lectura de splitDependencies va seguida de algunas llamadas a readString8() y sería bueno tener control total sobre los datos no consumidos después de que ocurra el desajuste, para poder colocar cadenas vacías directamente allí y no preocuparnos por una interpretación diferente de los datos no consumidos.
Para hacerlo, primero necesitamos poner algún contenedor de datos sin procesar dentro de OutputConfiguration.mSensorPixelModesUsed que se escriba a través de writeValue. He elegido Bundle. De esa manera, en los datos no consumidos nos quedará:
VAL_BUNDLE de writeValueBUNDLE_MAGICParcel.appendFromAsí que tenemos tres elementos Parcel.writeInt que quedarían sin consumir; podemos deshacernos de ellos envolviendo OutputConfiguration dentro de algún Parcelable que, al leerlo, lea un valor Parcelable arbitrario seguido de tres enteros. He encontrado eso en ZenPolicy CREATOR.
Para resumir, tenemos la siguiente jerarquía de objetos (que está presente en system_server y que intenta pasar a 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) = BundleEso es escrito por system_server. Luego, la aplicación receptora lee todo hasta (e incluyendo los datos readSerializable de) PackageManagerException normalmente; sin embargo, después de que se leen los datos Serializable para PackageManagerException, se lanza una excepción y se cancela la lectura de todo lo que está debajo de OutputConfiguration, dejando Bundle sin leer. La lectura continúa con ZenPolicy, que consume los tres enteros que preceden a los datos sin procesar dentro de Bundle. Luego, la lectura de ApplicationInfo continúa con la lectura de los datos que anteriormente eran datos sin procesar pasados textualmente en Bundle. La lectura de esos datos sin procesar continuará con los objetos restantes en esta pila (ApplicationInfo, ActivityInfo, e ), y luego esos datos sin procesar se usarán para leer el siguiente parámetro del método .
handleReceiverComo acabo de decir, ahora los parámetros restantes de scheduleReceiver se leen desde el buffer controlado por el atacante.
Echemos un vistazo a lo que sucede una vez que se invoca ese método.
Primero, scheduleReceiver empaqueta los valores de todos los argumentos y usa sendMessage() para pasar la ejecución al hilo principal.
Luego, en el hilo principal se llama a handleReceiver.
handleReceiver llama a getPackageInfoNoCheck, pasándole el ApplicationInfo que recibió como parte de ActivityInfo que se pasó al argumento de scheduleReceiver.
getPackageInfo verifica si el paquete con el nombre dado ya está presente en la caché y si no, construye una nueva instancia de LoadedApk, pasándole el objeto ApplicationInfo recibido anteriormente (dado que el atacante quiere que se construya un nuevo LoadedApk, se usa un packageName de un paquete que no se había visto antes en este proceso).
Luego se usa el método ContextImpl.getClassLoader(), que en la primera ejecución delega en mPackageInfo.getClassLoader(), siendo mPackageInfo un LoadedApk construido en el párrafo anterior.
Luego está createOrUpdateClassLoaderLocked, que llama a makePaths para poblar zipPaths con las rutas que se usarán en el ClassLoader, luego se unen y se asignan a la variable zip y eso se pasa a createClassLoader.
makePaths llena zipPaths usando información de ApplicationInfo, y lo más importante, esto incluye sourceDir. La aplicación atacante hace que el ApplicationInfo inyectado tenga sourceDir establecido en la ruta a su propio apk, por lo tanto, la clase del receptor se cargará realmente desde el apk del atacante. Esto conduce directamente a la ejecución de código controlado por el atacante dentro de la aplicación que recibe la transmisión.
Había una cosa más que necesitaba ser omitida: las comprobaciones de API ocultas. Estas nunca estuvieron destinadas a ser un límite de seguridad (ya que la aplicación siempre puede usar NDK y llamar a la syscall subyacente directamente), pero en este caso se omitieron construyendo un ClipData manipulado escribiendo manualmente datos en un Parcel y luego usando readParcelable. Tal ClipData podría luego adjuntarse normalmente a un Intent y pasarse a sendBroadcast(), por lo que el envío de la transmisión en sí se hizo usando solo APIs públicas.
El informe anterior se envió originalmente a Google y parece que lo han utilizado, ya que hay múltiples correcciones que resultan de él (creo, no tengo pruebas sobre la causalidad directa).
Publicado con Android 12:
OutputConfiguration y clases relacionadasOutputConfiguration#mSensorPixelModesUsed ya no se escribe a través de writeValueClipData#mActivityInfo ya no se escribe en Parcel a menos que se solicite explícitamente durante la escritura (por lo que Intent ya no puede contener Parcelables arbitrarios de BOOTCLASSPATH, eliminando esta técnica de explotación)Presente solo en la rama master en el momento de escribir esto, no en versiones publicadas, probablemente aparecerá en Android 13 (no en 12L):
List en Parcel que verifican el tipo de los elementos y las versiones sin tipo se han marcado como obsoletasParcel#enforceNoDataAvail() que verifica que no queden datos sin leer en el Parcel, que aparentemente será utilizado por AIDL después de leer los argumentos de la llamada RPC. Por lo general, mis exploits se basaban en el hecho de que después de que se leyeran todos los datos del Parcel, todo lo demás se ignoraba; eso ya no será el caso, aunque creo que en muchos casos uno podría construir datos que al final hagan que se busque la posición final, por lo que esta no es una mitigación fuerte. De todos modos, a veces tales problemas ocurren naturalmente sin ser detectados, por lo que eso los detectaría. Más discusión sobre eso está en el issue #3Bundle tendrá su longitud guardada por separado. Esto mata prácticamente toda la clase de bugs de la que he estado informando a Google en privado desde 2014, descripción publicada en 2017 y el código en sí aproximadamente un año después. Si cuento correctamente, eso serán 8 años de vida de la clase de bugs (honestamente no tengo idea si eso es mucho, aunque todavía podría haber variantes de explotación que no sean de Bundle, como exactamente esta (aunque exactamente esta ya fue corregida)).resolveClassObjectInputStreamc != nullClass.forNamePackageManagerExceptionParcelablereadListClassLoaderWindowContainerTransactionsystem_serverParcelPackageManagerExceptionClassNotFoundExceptionClipDataIntenthandleReceiver