
Writeup and exploit for CVE-2024-34740, integer overflow in Android's BinaryXmlSerializer to system_server file write and then to system_server code execution from normal installed app

Fixes for issue described here appeared under CVE-2024-34740 / A-307288067:
Inside Android system_server, many services store their state across reboots in XML files
$ adb shell su 0 find /data/system -name '*.xml' | sort
/data/system/appops_accesses.xml
/data/system/cachequota.xml
/data/system/device_policies.xml
/data/system/device_policy_state.xml
/data/system/display-manager-state.xml
/data/system/input-manager-state.xml
/data/system/inputmethod/subtypes.xml
/data/system/install_sessions.xml
/data/system/job/jobs_1000.xml
/data/system/job/jobs_10131.xml
/data/system/log-files.xml
/data/system/netpolicy.xml
/data/system/notification_policy.xml
/data/system/overlays.xml
/data/system/packages.xml
/data/system/package-watchdog.xml
/data/system/sensor_privacy_impl.xml
/data/system/sensor_privacy.xml
/data/system/shortcut_service.xml
/data/system/users/0/app_idle_stats.xml
/data/system/users/0/appwidgets.xml
/data/system/users/0/package-restrictions.xml
/data/system/users/0/settings_global.xml
/data/system/users/0/settings_secure.xml
/data/system/users/0/settings_system.xml
/data/system/users/0/wallpaper_info.xml
/data/system/users/0.xml
/data/system/users/userlist.xml
/data/system/watchlist_settings.xml
Historically these have been plain text XML files with indentation, which allowed developers easy reading of them, however in Android 12 new binary version of that format was introduced, citing 1.5% of all time spent by system_server being spent on these XML operations
It should be noted that this format is only used internally by system and has files with magic value "ABX\x00". It is different from format used inside APKs for AndroidManifest.xml, res/xml/*.xml, res/layout/*.xml, etc. which has no explicit "magic value", however usually starts 0300 0800 (which is header with type=RES_XML_TYPE and headerSize=8)
Whenever system reads one of these internal state XML files, it uses "ABX\0" magic value in file to choose either parser for Binary XML file or regular XML parser. Whenever these files are saved as Binary XML is controlled by system property and is enabled by default
When Binary XML files are in use, you can read their contents for example through adb shell su 0 abx2xml /data/system/packages.xml -
One of things this binary format does is offering typed accessors, so serializer offers attributeInt(String namespace, String name, int value) method, which writes value as binary integer, avoiding round-trip through String which would be new allocation and subsequent object for Garbage Collection
Another type that can be directly serialized is byte array
@Override
public XmlSerializer attributeBytesBase64(String namespace, String name, byte[] value)
throws IOException {
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
mOut.writeByte(ATTRIBUTE | TYPE_BYTES_BASE64);
mOut.writeInternedUTF(name);
mOut.writeShort(value.length);
mOut.write(value);
return this;
}
There's also similar method attributeBytesHex which only differs by TYPE_* tag written. That tag is used by abx2xml tool to convert byte array to appropriate String representation
mOut is instance of FastDataOutput, which provides functions of Java's DataOutputStream. writeByte/writeShort/writeInt/writeUTF/write use same format as standard DataOutputStream
Similarly to Parcel, if something mismatches during write/read subsequently read data will be taken from wrong offsets, however unlike Parcel, mistakes with usage of BinaryXmlSerializer/BinaryXmlPullParser don't give attacker ability to arbitrarily tamper read data (attacker cannot introduce new tag/attribute names/values in that case)
Mistakes within BinaryXmlSerializer class itself or in FastDataOutput however do
In above method if we'd try to write byte array with length 65536, we'll write length with writeShort(), which will effectively write 0, after which actual array contents will be written
In order to exploit that mismatch, we'll need to choose some file where we'll be able to inject arbitrary byte array to attributeBytesBase64 or attributeBytesHex as well as modification of that file will be valuable for attacker
PackageInstaller class offers ability to prepare package for installation. Without needing any permissions, any app can write new APK to be installed into temporary directory. Once everything necessary for installation has been written, installing app can commit() PackageInstaller.Session, which means it won't be able to do any further changes to installation files and Session is ready for either user approval or actual installation
State of these operations is stored in /data/system/install_sessions.xml. Installer app can for example download half of large APK into temporary directory created by Package Manager Service for its PackageInstaller.Session, then after reboot resume download, write remaining half and commit installation
One of possibilities is writing data into install_sessions.xml to mark session as staged, which means it'll be installed after next boot