
Writeup и эксплойт для повышения привилегий установленного приложения до системных на Android 12 Beta через CVE-2021-0928, несоответствие сериализации `writeToParcel`/`createFromParcel` в `OutputConfiguration`
CVE-2021-0928, несоответствие сериализации writeToParcel/createFromParcel в android.hardware.camera2.params.OutputConfiguration
Это эксплойт, использующий данную уязвимость для повышения привилегий из установленного Android-приложения в приложение «Настройки» Android (или любое другое установленное приложение, которому можно отправить сообщение в <receiver>, объявленный в AndroidManifest.xml; повышение привилегий путём отправки в <activity> также было возможно, хотя здесь не представлено)
Изначально я обнаружил проблему в Android 12 Developer Preview 3
Версия эксплойта, представленная в этом репозитории, работает на Android 12 Beta 2 и 3
Уязвимость была исправлена в первом официальном выпуске Android 12
Описание ниже изначально было написано для Google для рассмотрения этого отчёта как полной цепочки эксплойта
На момент написания Android 12 не был доступен в AOSP (релизы Android Developer Preview/Beta не являются открытым исходным кодом)

Большая часть IPC в Android осуществляется через класс Parcel
Базовое использование Parcel выглядит следующим образом:```java Parcel p = Parcel.obtain(); p.writeInt(1); p.writeString("Hello");
Затем `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 внутренне хранит позицию, с которой выполняются чтения. Ответственность пользователя класса Parcel заключается в том, чтобы методы read* соответствовали ранее использованным методам write*, иначе последующие чтения будут выполняться с неправильных позиций в буфере.
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);
}
};
}
As can be seen above, `writeToParcel()` method is used during writing. Then while reading `CREATOR.createFromParcel()` factory method is called. It is responsibility of `Parcelable` implementation to ensure that `createFromParcel` reads same amount of data as was written by `writeToParcel`, otherwise all subsequent reads from that `Parcel` will read data from wrong offset
Such class can be written to/read from `Parcel` through:
* Directly calling `obj.writeToParcel(parcel, 0)` / `obj = WindowContainerTransaction.CREATOR.createFromParcel()`, this is often used when type of class is known, for example when `Parcelable` has field with different `Parcelable` or in code generated by AIDL when defined RPC method has `Parcelable` as argument
* Through `Parcel.writeParcelable`/`readParcelable`. [`writeParcelable`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=1909;drc=58787794eb5c879f6e39ee58c0071b36e337f8e3) first writes name of class and then calls `writeToParcel` method from `Parcelable` interface. [`readParcelable`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=3282;drc=58787794eb5c879f6e39ee58c0071b36e337f8e3) reads written class name, finds class with that name in provided `ClassLoader` or `BOOTCLASSPATH` if null was provided. Once class is found it's static field `CREATOR` is used to obtain [`Parcelable.Creator`](https://developer.android.com/reference/android/os/Parcelable.Creator) instance which is factory used to read that class. It is important to note that when `readParcelable` method is used it can read any `Parcelable` available in class path as name of object to be created is read from same `Parcel`
* `readParcelable` is used by many other `Parcel` methods, for example `readList` seen in example above reads elements through `readValue`, which is most generic method of transferring objects in `Parcel` and one of ways it uses is through `readParcelable`. Also in above example due to Java's Type Erasure `ArrayList<HierarchyOp> mHierarchyOps` field can actually contain any objects supported by Parcel, not only those compatible with type specified in generic type declaration
# Несоответствия `writeToParcel`/`createFromParcel`
Как отмечено выше, именно реализация интерфейса `Parcelable` обязана гарантировать, что `createFromParcel` считывает из `Parcel` тот же объём данных, который ранее был записан соответствующим `writeToParcel`. Всякий раз, когда в `BOOTCLASSPATH` существует `Parcelable`, способный нарушить этот контракт, это создаёт уязвимость, поскольку позволяет реализовать следующий сценарий:
1. Вредоносное приложение отправляет в `system_server` `Bundle` ИЛИ `Parcelable`, содержащий повреждённый экземпляр `Parcelable`, вместе со специально сконструированными данными, которые будут фактически прочитаны на шаге 3, но переданы без изменений на шаге 2
2. `system_server` проверяет, что `Bundle` безопасен, и пересылает его ИЛИ `system_server` передаёт предоставленный `Parcelable` в метод AIDL, который также получает критически важные данные в следующем параметре (если бы данные, полученные в этом параметре, можно было изменить, это вызвало бы проблему безопасности)
3. Другое приложение получает данные от `system_server` и доверяет им, однако из-за некорректной сериализации данные, которые оно фактически видит, отличаются от данных, которые `system_server` намеревался отправить
Я использовал "ИЛИ" в приведённых шагах, поскольку эти шаги описывают как [старый вариант эксплуатации, который приводит к запуску произвольного Activity и который я опубликовал в 2017 году](https://github.com/michalbednarski/ReparcelBug) (слева от "ИЛИ"), так и новый вариант, который я опишу здесь в следующем разделе
# Как `BroadcastReceiver` выполняется в приложении
С точки зрения разработчика приложения, использующего API, доступные в Android SDK, [`BroadcastReceiver`](https://developer.android.com/guide/components/broadcasts) работает следующим образом: одно приложение вызывает [`sendBroadcast`](https://developer.android.com/reference/android/content/Context#sendBroadcast(android.content.Intent)) (хотя часто приложения хотят получать Broadcasts от системы, а не от другого приложения), а затем отправленный `Intent` сопоставляется с `<receiver>`, определённым в `AndroidManifest.xml`; когда это происходит, система запускает процесс принимающего приложения, создаёт экземпляр подкласса `BroadcastReceiver`, как определено в атрибуте `<receiver android:name>`, и затем вызывает метод [`onReceive`](https://developer.android.com/reference/android/content/BroadcastReceiver#onReceive(android.content.Context,%20android.content.Intent))
Давайте рассмотрим взаимодействие с `system_server`, происходящее в процессе, принимающем broadcast:
* Когда процесс приложения первоначально запускается, он [вызывает `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` в процессе приложения, она вызывает метод [`scheduleReceiver`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/app/ActivityThread.java;l=950;drc=f53e23b917aa0f6a6310e46a233a29b6d6226b2c), используя `IApplicationThread`, описанный в предыдущем пункте. Этот метод имеет несколько аргументов, но здесь наиболее важными являются первые два:
1. `Intent intent`, который ранее был передан системе при вызове `sendBroadcast()`
2. `ActivityInfo info`, который содержит информацию о компоненте, который должен быть выполнен. Значение этого параметра система берёт из Package Manager Service. Наиболее важно то, что данные, передаваемые в этом параметре, включают путь к файлу, из которого будет загружен Java-класс, обрабатывающий полученный broadcast
На этом этапе вы, вероятно, можете догадаться, в чём заключается этот новый путь эксплуатации: вызвать `sendBroadcast()`, передав `Intent`, который приведёт к тому, что когда система попытается вызвать `scheduleReceiver`, приложение, в котором вызывается `scheduleReceiver`, увидит подменённый `ActivityInfo`
Следует отметить, что этот новый путь эксплуатации стал возможен в Android 12, поскольку ранее не было способа поместить произвольные `Parcelable` в `Intent` ([extras Intent](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` возникают, когда в одном из этих методов одно из полей забыто или записано дважды; в таком случае отправка такого объекта всегда вызовет несоответствие. (Чаще всего это происходит, когда объект, будучи `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
}
Итак, что здесь не так и почему это изменение вносит уязвимость? Как я уже говорил при обсуждении примера Parcelable WindowContainerTransaction, readList на самом деле может заполнить список любым объектом, поддерживаемым Parcel, а не только теми, что соответствуют объявлению дженерика (ArrayList<Integer>). Однако, поскольку мы используем этот класс лишь как часть цепочки сериализации и на самом деле не используем это поле ни для чего, кроме чтения и записи в Parcel (а попытки использовать элементы из ArrayList, содержащего элементы, не соответствующие его объявлению дженерика, в любом случае привели бы лишь к ClassCastException), это само по себе не является проблемой.
В этом классе также есть try-catch внутри createFromParcel, что означает, что если во время чтения возникнет Exception, чтение OutputConfiguration будет остановлено, и чтение объекта, содержащего OutputConfiguration, продолжится. Когда это происходит, весь OutputConfiguration записывается в Parcel, но читается только до точки, в которой произошло Exception. Это создает несоответствие, так как непрочитанные данные, записанные внутри OutputConfiguration.writeToParcel, на самом деле будут прочитаны объектом, который вызывал OutputConfiguration.CREATOR.createFromParcel.
Теперь комбинация этих двух факторов (возможность вкладывать произвольные объекты, поддерживаемые Parcel, и оборачивание этого в try-catch без повторного выброса) дает возможность сконструировать Parcelable, который может быть записан system_server и позже прочитан способом, контролируемым приложением, которое изначально создало Parcelable, пересылаемый system_server.
Итак, чтобы сконструировать такой Parcelable, нам нужно найти что-то, что можно поместить в mSensorPixelModesUsed, что будет успешно прочитано в system_server (так как этот объект принимается от приложения атакующего через Parcel), успешно записано system_server, а затем не сможет быть распаковано и вызовет Exception в приложении-жертве.
Один из способов сделать это — использовать класс, который присутствует в system_server, но отсутствует в приложениях, так что попытка его десериализации приведет к ClassNotFoundException. Однако я не могу выбрать Parcelable из system_server, так как readParcelable без явно указанного ClassLoader будет искать только в BOOTCLASSPATH, который не содержит специфичных для system_server классов. Решение этой проблемы — использовать один из классов Serializable, так как ObjectInputStream выберет ClassLoader из первого метода в стеке вызовов, не относящегося к BOOTCLASSPATH.
Я выбрал PackageManagerException, однако прежде чем использовать его, нужно сделать еще одну вещь. В конструкторе OutputConfiguration, когда вызывается readList, аргумент loader явно устанавливается в Integer.class.getClassLoader(). Это [значение loaderпередается вreadValue()](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=3641;drc=58787794eb5c879f6e39ee58c0071b36e337f8e3), затем [в readSerializable()](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=3236;drc=58787794eb5c879f6e39ee58c0071b36e337f8e3), и внутри [readSerializable(), если параметр loaderresolveClassObjectInputStreamc != nullClass.forNamePackageManagerExceptionParcelablereadListClassLoaderWindowContainerTransaction`.
Итак, на данный момент у нас есть следующий объект:
OutputConfiguration
mSensorPixelModesUsed.get(0) = WindowContainerTransaction
mHierarchyOps.get(0) = PackageManagerExceptionТеперь такой объект может быть успешно десериализован внутри system_server: когда WindowContainerTransaction вызывает readList, он попытается найти класс PackageManagerException, используя ClassLoader системного сервера (не BootClassLoader), так как может найти его в стеке вызовов. Этот загрузчик классов оказывается в стеке вызовов, потому что, хотя все следующие методы не были из classpath системного сервера: Binder#execTransact(), IActivityManager$Stub#onTransact(), сгенерированный AIDL, и методы из всех используемых классов Parcelable, в стеке вызовов был метод, объявленный внутри системного сервера: переопределенный onTransact в ActivityManagerService. Поэтому может прочитать и позже записать такой объект в , и когда целевое приложение попытается его прочитать, класс будет недоступен, и поэтому будет выброшен , , а затем .
Итак, мы вызвали это несоответствие. Ну, на самом деле еще не совсем, потому что исключение было перехвачено, когда после OutputConfiguration.writeToParcel не осталось непрочитанных данных, но мы можем легко добавить еще один элемент в List mSensorPixelModesUsed, и этот элемент будет записан через Parcel.writeValue и останется непрочитанным после чтения OutputConfiguration.
IntentКак отмечено выше, я хочу вызвать несоответствие из объекта Intent, так как он будет передан system_server в метод AIDL, у которого Intent находится в первом параметре, а информация о выполнении — во втором параметре, так что сериализация/десериализация Intent, переданного в первом параметре, приведет к изменению значения во втором параметре.
В Intent.readFromParcel() все значения читаются через специализированные типизированные методы, поэтому там мы не можем указать пользовательский класс Parcelable.
Однако внутри Intent есть вложенный ClipData, и начиная с Android 12 в ClipData$Item есть новое поле ActivityInfo mActivityInfo (отсутствовало в AOSP на момент первоначального написания, вот коммит, вводящий это поле, это поле читается через in.readTypedObject(ActivityInfo.CREATOR) внутри конструктора ClipData(Parcel in)).
Затем внутри конструктора ActivityInfo(Parcel source) снова нет способа поместить пользовательский Parcelable, но так как ActivityInfo наследуется от ComponentInfo, у него есть поле applicationInfo.
Наконец, в ApplicationInfo есть поле SparseArray<int[]> splitDependencies, которое читается через readSparseArray, которое, в свою очередь, использует readValue для чтения элементов SparseArray.
На этом этапе мы могли бы поместить OutputConfiguration в splitDependencies, однако чтение splitDependencies сопровождается несколькими вызовами readString8(), и было бы неплохо иметь полный контроль над непрочитанными данными после возникновения несоответствия, чтобы мы могли напрямую поместить туда пустые строки и не беспокоиться о различной интерпретации непрочитанных данных.
Чтобы сделать это, сначала нам нужно поместить некоторый контейнер необработанных данных в OutputConfiguration.mSensorPixelModesUsed, который будет записан через writeValue. Я выбрал Bundle. Таким образом, в непрочитанных данных у нас останется:
VAL_BUNDLE из writeValueBUNDLE_MAGICParcel.appendFromИтак, у нас есть три элемента Parcel.writeInt, которые останутся непрочитанными; мы можем избавиться от них, обернув OutputConfiguration в какой-нибудь Parcelable, который при чтении считывает произвольное значение 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. Затем принимающее приложение читает все вплоть до (и включая данные readSerializable для) PackageManagerException нормально, однако после чтения данных Serializable для PackageManagerException выбрасывается исключение, и чтение всего, что находится ниже OutputConfiguration, отменяется, оставляя Bundle непрочитанным. Чтение переходит к ZenPolicy, который потребляет три целых числа, предшествующие необработанным данным внутри Bundle. Затем чтение ApplicationInfo продолжается с чтения данных, которые ранее были необработанными данными, переданными дословно в Bundle. Чтение этих необработанных данных продолжится с оставшимися объектами в этом стеке (ApplicationInfo, ActivityInfo, и ), а затем эти необработанные данные будут использованы для чтения следующего параметра метода .
handleReceiverКак я только что сказал, теперь оставшиеся параметры scheduleReceiver читаются из буфера, контролируемого атакующим.
Давайте посмотрим, что происходит после вызова этого метода.
Сначала scheduleReceiver упаковывает значения из всех аргументов и использует sendMessage() для передачи выполнения в главный поток.
Затем в главном потоке вызывается handleReceiver.
handleReceiver вызывает getPackageInfoNoCheck, передавая ему ApplicationInfo, который он получил как часть ActivityInfo, переданного в аргумент scheduleReceiver.
getPackageInfo проверяет, присутствует ли пакет с данным именем в кэше, и если нет, создает новый экземпляр LoadedApk, передавая ему объект ApplicationInfo, полученный ранее (поскольку атакующий хочет вызвать создание нового LoadedApk, используется packageName пакета, который ранее не встречался в этом процессе).
Затем используется метод ContextImpl.getClassLoader(), который при первом запуске делегирует mPackageInfo.getClassLoader(), где mPackageInfo — это LoadedApk, созданный в предыдущем абзаце.
Затем есть createOrUpdateClassLoaderLocked, который вызывает makePaths для заполнения zipPaths путями, которые будут использоваться в ClassLoader, затем они объединяются и присваиваются переменной zip, и это передается в createClassLoader.
makePaths заполняет zipPaths, используя информацию из ApplicationInfo, что наиболее важно, включает sourceDir. Приложение атакующего делает так, чтобы внедренный ApplicationInfo имел sourceDir, установленный на путь к собственному apk, поэтому класс приемника на самом деле будет загружен из apk атакующего. Это напрямую приводит к выполнению кода, контролируемого атакующим, в приложении, принимающем широковещательное сообщение.
Была еще одна вещь, которую нужно было обойти: проверки скрытых API. Они никогда не предназначались для использования в качестве границы безопасности (поскольку приложение всегда может использовать NDK и вызывать базовый syscall напрямую), но в этом случае они были обойдены путем создания специально сконструированного ClipData с помощью ручной записи данных в Parcel, а затем использования readParcelable. Такой ClipData затем можно было нормально прикрепить к Intent и передать в sendBroadcast(), так что сама отправка широковещательного сообщения выполнялась только с использованием публичных API.
Вышеописанное исследование было изначально отправлено в Google, и похоже, что они им воспользовались, так как есть несколько исправлений, являющихся его результатом (я думаю, у меня нет доказательств прямой причинно-следственной связи).
Выпущено с Android 12:
OutputConfiguration и связанных классов было удаленоOutputConfiguration#mSensorPixelModesUsed больше не записывается через writeValueClipData#mActivityInfo больше не записывается в Parcel, если это явно не запрошено при записи (так что Intent больше не может содержать произвольные Parcelable из BOOTCLASSPATH, что устраняет эту технику эксплуатации).Присутствует только в ветке master на момент написания, не в выпущенных версиях, вероятно, появится в Android 13 (не в 12L):
List в Parcel, которые проверяют тип элементов, и нетипизированные версии помечены как устаревшиеParcel#enforceNoDataAvail(), который проверяет, что в Parcel не осталось непрочитанных данных, который, по-видимому, будет использоваться AIDL после чтения аргументов RPC-вызова. Обычно мои эксплойты полагались на тот факт, что после чтения всех данных из Parcel все остальное игнорировалось; это больше не будет так, хотя я думаю, что во многих случаях можно сконструировать данные, которые в конце приведут к переходу в конечную позицию, так что это не является сильной мерой смягчения. В любом случае, иногда такие проблемы возникают естественным образом и остаются незамеченными, так что это позволит их выявлять. Дальнейшее обсуждение этого вопроса находится в issue #3Bundle будет иметь свою длину, сохраняемую отдельно. Это практически убивает весь класс ошибок, о которых я сообщал в Google в частном порядке с 2014 года, опубликовал описание в 2017 году и сам код примерно год спустя. Если я правильно считаю, это будет 8 лет жизни класса ошибок (честно говоря, я понятия не имею, много это или нет, хотя все еще могут существовать варианты эксплойтов, не связанные с Bundle, например, именно этот (хотя именно этот уже был исправлен)).не равен null, он используется вместоиз(проверканичего не делает, потому что когдане находит класс, он выбрасывает исключение, а не возвращает null)](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=3457-3462;drc=58787794eb5c879f6e39ee58c0071b36e337f8e3). Однако обходной путь довольно прост: нам просто нужно обернутьв какой-нибудь, который выполняет без указания. Здесь и пригодится описанный выше класс system_serverParcelPackageManagerExceptionClassNotFoundExceptionClipDataIntenthandleReceiver