{
	"id": "c95b853c-b690-412c-b3f1-f9cec8261fac",
	"created_at": "2026-04-06T00:11:03.211836Z",
	"updated_at": "2026-04-10T03:22:02.404315Z",
	"deleted_at": null,
	"sha1_hash": "f656f8d2a57c342ba2b22ca4a462c95d724ede88",
	"title": "OATmeal on the Universal Cereal Bus: Exploiting Android phones over USB",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 75895,
	"plain_text": "OATmeal on the Universal Cereal Bus: Exploiting Android phones\r\nover USB\r\nArchived: 2026-04-05 19:04:14 UTC\r\nPosted by Jann Horn, Google Project Zero\r\nRecently, there has been some attention around the topic of physical attacks on smartphones, where an attacker\r\nwith the ability to connect USB devices to a locked phone attempts to gain access to the data stored on the device.\r\nThis blogpost describes how such an attack could have been performed against Android devices (tested with a\r\nPixel 2).\r\nAfter an Android phone has been unlocked once on boot (on newer devices, using the \"Unlock for all features and\r\ndata\" screen; on older devices, using the \"To start Android, enter your password\" screen), it retains the encryption\r\nkeys used to decrypt files in kernel memory even when the screen is locked, and the encrypted filesystem areas or\r\npartition(s) stay accessible. Therefore, an attacker who gains the ability to execute code on a locked device in a\r\nsufficiently privileged context can not only backdoor the device, but can also directly access user data.\r\n(Caveat: We have not looked into what happens to work profile data when a user who has a work profile toggles\r\noff the work profile.)\r\nThe bug reports referenced in this blogpost, and the corresponding proof-of-concept code, are available at:\r\nThese issues were fixed as CVE-2018-9445 (fixed at patch level 2018-08-01) and CVE-2018-9488 (fixed at patch\r\nlevel 2018-09-01).\r\nThe attack surface\r\nMany Android phones support USB host mode (often using OTG adapters). This allows phones to connect to\r\nmany types of USB devices (this list isn't necessarily complete):\r\nUSB sticks: When a USB stick is inserted into an Android phone, the user can copy files between the\r\nsystem and the USB stick. Even if the device is locked, Android versions before P will still attempt to\r\nmount the USB stick. (Android 9, which was released after these issues were reported, has logic in vold\r\nthat blocks mounting USB sticks while the device is locked.)\r\nUSB keyboards and mice: Android supports using external input devices instead of using the touchscreen.\r\nThis also works on the lockscreen (e.g. for entering the PIN).\r\nUSB ethernet adapters: When a USB ethernet adapter is connected to an Android phone, the phone will\r\nattempt to connect to a wired network, using DHCP to obtain an IP address. This also works if the phone is\r\nlocked.\r\nhttps://googleprojectzero.blogspot.com/2018/09/oatmeal-on-universal-cereal-bus.html\r\nPage 1 of 11\n\nThis blogpost focuses on USB sticks. Mounting an untrusted USB stick offers nontrivial attack surface in highly\r\nprivileged system components: The kernel has to talk to the USB mass storage device using a protocol that\r\nincludes a subset of SCSI, parse its partition table, and interpret partition contents using the kernel's filesystem\r\nimplementation; userspace code has to identify the filesystem type and instruct the kernel to mount the device to\r\nsome location. On Android, the userspace implementation for this is mostly in vold (one of the processes that are\r\nconsidered to have kernel-equivalent privileges), which uses separate processes in restrictive SELinux domains to\r\ne.g. determine the filesystem types of partitions on USB sticks.\r\nThe bug (part 1): Determining partition attributes\r\nWhen a USB stick has been inserted and vold has determined the list of partitions on the device, it attempts to\r\nidentify three attributes of each partition: Label (a user-readable string describing the partition), UUID (a unique\r\nidentifier that can be used to determine whether the USB stick is one that has been inserted into the device before),\r\nand filesystem type. In the modern GPT partitioning scheme, these attributes can mostly be stored in the partition\r\ntable itself; however, USB sticks tend to use the MBR partition scheme instead, which can not store UUIDs and\r\nlabels. For normal USB sticks, Android supports both the MBR partition scheme and the GPT partition scheme.\r\nTo provide the ability to label partitions and assign UUIDs to them even when the MBR partition scheme is used,\r\nfilesystems implement a hack: The filesystem header contains fields for these attributes, allowing an\r\nimplementation that has already determined the filesystem type and knows the filesystem header layout of the\r\nspecific filesystem to extract this information in a filesystem-specific manner. When vold wants to determine\r\nlabel, UUID and filesystem type, it invokes /system/bin/blkid in the blkid_untrusted SELinux domain, which does\r\nexactly this: First, it attempts to identify the filesystem type using magic numbers and (failing that) some\r\nheuristics, and then, it extracts the label and UUID. It prints the results to stdout in the following format:\r\n/dev/block/sda1: LABEL=\"\u003clabel\u003e\" UUID=\"\u003cuuid\u003e\" TYPE=\"\u003ctype\u003e\"\r\nHowever, the version of blkid used by Android did not escape the label string, and the code responsible for parsing\r\nblkid's output only scanned for the first occurrences of UUID=\" and TYPE=\". Therefore, by creating a partition\r\nwith a crafted label, it was possible to gain control over the UUID and type strings returned to vold, which would\r\notherwise always be a valid UUID string and one of a fixed set of type strings.\r\nThe bug (part 2): Mounting the filesystem\r\nWhen vold has determined that a newly inserted USB stick with an MBR partition table contains a partition of\r\ntype vfat that the kernel's vfat filesystem implementation should be able to mount, PublicVolume::doMount()\r\nconstructs a mount path based on the filesystem UUID, then attempts to ensure that the mountpoint directory\r\nexists and has appropriate ownership and mode, and then attempts to mount over that directory:\r\n   if (mFsType != \"vfat\") {\r\n       LOG(ERROR) \u003c\u003c getId() \u003c\u003c \" unsupported filesystem \" \u003c\u003c mFsType;\r\n       return -EIO;\r\n   }\r\nhttps://googleprojectzero.blogspot.com/2018/09/oatmeal-on-universal-cereal-bus.html\r\nPage 2 of 11\n\nif (vfat::Check(mDevPath)) {\r\n       LOG(ERROR) \u003c\u003c getId() \u003c\u003c \" failed filesystem check\";\r\n       return -EIO;\r\n   }\r\n   // Use UUID as stable name, if available\r\n   std::string stableName = getId();\r\n   if (!mFsUuid.empty()) {\r\n       stableName = mFsUuid;\r\n   }\r\n   mRawPath = StringPrintf(\"/mnt/media_rw/%s\", stableName.c_str());\r\n   [...]\r\n   if (fs_prepare_dir(mRawPath.c_str(), 0700, AID_ROOT, AID_ROOT)) {\r\n       PLOG(ERROR) \u003c\u003c getId() \u003c\u003c \" failed to create mount points\";\r\n       return -errno;\r\n   }\r\n   if (vfat::Mount(mDevPath, mRawPath, false, false, false,\r\n           AID_MEDIA_RW, AID_MEDIA_RW, 0007, true)) {\r\n       PLOG(ERROR) \u003c\u003c getId() \u003c\u003c \" failed to mount \" \u003c\u003c mDevPath;\r\n       return -EIO;\r\n   }\r\nThe mount path is determined using a format string, without any sanity checks on the UUID string that was\r\nprovided by blkid. Therefore, an attacker with control over the UUID string can perform a directory traversal\r\nattack and cause the FAT filesystem to be mounted outside of /mnt/media_rw.\r\nThis means that if an attacker inserts a USB stick with a FAT filesystem whose label string is 'UUID=\"../##' into a\r\nlocked phone, the phone will mount that USB stick to /mnt/##.\r\nHowever, this straightforward implementation of the attack has several severe limitations; some of them can be\r\novercome, others worked around:\r\nhttps://googleprojectzero.blogspot.com/2018/09/oatmeal-on-universal-cereal-bus.html\r\nPage 3 of 11\n\nLabel string length: A FAT filesystem label is limited to 11 bytes. An attacker attempting to perform a\r\nstraightforward attack needs to use the six bytes 'UUID=\"' to start the injection, which leaves only five\r\ncharacters for the directory traversal - insufficient to reach any interesting point in the mount hierarchy.\r\nThe next section describes how to work around that.\r\nSELinux restrictions on mountpoints: Even though vold is considered to be kernel-equivalent, a SELinux\r\npolicy applies some restrictions on what vold can do. Specifically, the mounton permission is restricted to a\r\nset of permitted labels.\r\nWritability requirement: fs_prepare_dir() fails if the target directory is not mode 0700 and chmod() fails.\r\nRestrictions on access to vfat filesystems: When a vfat filesystem is mounted, all of its files are labeled as\r\nu:object_r:vfat:s0. Even if the filesystem is mounted in a place from which important code or data is\r\nloaded, many SELinux contexts won't be permitted to actually interact with the filesystem - for example,\r\nthe zygote and system_server aren't allowed to do so. On top of that, processes that don't have sufficient\r\nprivileges to bypass DAC checks also need to be in the media_rw group. The section \"Dealing with\r\nSELinux: Triggering the bug twice\" describes how these restrictions can be avoided in the context of this\r\nspecific bug.\r\nExploitation: Chameleonic USB mass storage\r\nAs described in the previous section, a FAT filesystem label is limited to 11 bytes. blkid supports a range of other\r\nfilesystem types that have significantly longer label strings, but if you used such a filesystem type, you'd then have\r\nto make it past the fsck check for vfat filesystems and the filesystem header checks performed by the kernel when\r\nmounting a vfat filesystem. The vfat kernel filesystem doesn't require a fixed magic value right at the start of the\r\npartition, so this might theoretically work somehow; however, because several of the values in a FAT filesystem\r\nheader are actually important for the kernel, and at the same time, blkid also performs some sanity checks on\r\nsuperblocks, the PoC takes a different route.\r\nAfter blkid has read parts of the filesystem and used them to determine the filesystem's type, label and UUID,\r\nfsck_msdos and the in-kernel filesystem implementation will re-read the same data, and those repeated reads\r\nactually go through to the storage device. The Linux kernel caches block device pages when userspace directly\r\ninteracts with block devices, but __blkdev_put() removes all cached data associated with a block device when the\r\nlast open file referencing the device is closed.\r\nA physical attacker can abuse this by attaching a fake storage device that returns different data for multiple reads\r\nfrom the same location. This allows us to present, for example, a romfs header with a long label string to blkid\r\nwhile presenting a perfectly normal vfat filesystem to fsck_msdos and the in-kernel filesystem implementation.\r\nThis is relatively simple to implement in practice thanks to Linux' built-in support for device-side USB. Andrzej\r\nPietrasiewicz's talk \"Make your own USB gadget\" is a useful introduction to this topic. Basically, the kernel ships\r\nwith implementations for device-side USB mass storage, HID devices, ethernet adapters, and more; using a\r\nrelatively simple pseudo-filesystem-based configuration interface, you can configure a composite gadget that\r\nprovides one or multiple of these functions, potentially with multiple instances, to the connected device. The\r\nhttps://googleprojectzero.blogspot.com/2018/09/oatmeal-on-universal-cereal-bus.html\r\nPage 4 of 11\n\nhardware you need is a system that runs Linux and supports device-side USB; for testing this attack, a Raspberry\r\nPi Zero W was used.\r\nThe f_mass_storage gadget function is designed to use a normal file as backing storage; to be able to interactively\r\nrespond to requests from the Android phone, a FUSE filesystem is used as backing storage instead, using the\r\ndirect_io option / the FOPEN_DIRECT_IO flag to ensure that our own kernel doesn't add unwanted caching.\r\nAt this point, it is already possible to implement an attack that can steal, for example, photos stored on external\r\nstorage. Luckily for an attacker, immediately after a USB stick has been mounted,\r\ncom.android.externalstorage/.MountReceiver is launched, which is a process whose SELinux domain permits\r\naccess to USB devices. So after a malicious FAT partition has been mounted over /data (using the label string\r\n'UUID=\"../../data'), the zygote forks off a child with appropriate SELinux context and group membership to permit\r\naccesses to USB devices. This child then loads bytecode from /data/dalvik-cache/, permitting us to take control\r\nover com.android.externalstorage, which has the necessary privileges to exfiltrate external storage contents.\r\nHowever, for an attacker who wants to access not just photos, but things like chat logs or authentication\r\ncredentials stored on the device, this level of access should normally not be sufficient on its own.\r\nDealing with SELinux: Triggering the bug twice\r\nThe major limiting factor at this point is that, even though it is possible to mount over /data, a lot of the highly-privileged code running on the device is not permitted to access the mounted filesystem. However, one highly-privileged service does have access to it: vold.\r\nvold actually supports two types of USB sticks, PublicVolume and PrivateVolume. Up to this point, this blogpost\r\nfocused on PublicVolume; from here on, PrivateVolume becomes important.\r\nA PrivateVolume is a USB stick that must be formatted using a GUID Partition Table. It must contain a partition\r\nthat has type UUID kGptAndroidExpand (193D1EA4-B3CA-11E4-B075-10604B889DCF), which contains a dm-crypt-encrypted ext4 (or f2fs) filesystem. The corresponding key is stored at\r\n/data/misc/vold/expand_{partGuid}.key, where {partGuid} is the partition GUID from the GPT table as a\r\nnormalized lowercase hexstring.\r\nAs an attacker, it normally shouldn't be possible to mount an ext4 filesystem this way because phones aren't\r\nusually set up with any such keys; and even if there is such a key, you'd still have to know what the correct\r\npartition GUID is and what the key is. However, we can mount a vfat filesystem over /data/misc and put our own\r\nkey there, for our own GUID. Then, while the first malicious USB mass storage device is still connected, we can\r\nconnect a second one that is mounted as PrivateVolume using the keys vold will read from the first USB mass\r\nstorage device. (Technically, the ordering in the last sentence isn't entirely correct - actually, the exploit provides\r\nboth mass storage devices as a single composite device at the same time, but stalls the first read from the second\r\nmass storage device to create the desired ordering.)\r\nBecause PrivateVolume instances use ext4, we can control DAC ownership and permissions on the filesystem; and\r\nthanks to the way a PrivateVolume is integrated into the system, we can even control SELinux labels on that\r\nfilesystem.\r\nhttps://googleprojectzero.blogspot.com/2018/09/oatmeal-on-universal-cereal-bus.html\r\nPage 5 of 11\n\nIn summary, at this point, we can mount a controlled filesystem over /data, with arbitrary file permissions and\r\narbitrary SELinux contexts. Because we control file permissions and SELinux contexts, we can allow any process\r\nto access files on our filesystem - including mapping them with PROT_EXEC.\r\nInjecting into zygote\r\nThe zygote process is relatively powerful, even though it is not listed as part of the TCB. By design, it runs with\r\nUID 0, can arbitrarily change its UID, and can perform dynamic SELinux transitions into the SELinux contexts of\r\nsystem_server and normal apps. In other words, the zygote has access to almost all user data on the device.\r\nWhen the 64-bit zygote starts up on system boot, it loads code from /data/dalvik-cache/arm64/system@framework@boot*.{art,oat,vdex}. Normally, the oat file (which contains an ELF library\r\nthat will be loaded with dlopen()) and the vdex file are symlinks to files on the immutable /system partition; only\r\nthe art file is actually stored on /data. But we can instead make system@framework@boot.art and\r\nsystem@framework@boot.vdex symlinks to /system (to get around some consistency checks without knowing\r\nexactly which Android build is running on the device) while placing our own malicious ELF library at\r\nsystem@framework@boot.oat (with the SELinux context that the legitimate oat file would have). Then, by\r\nplacing a function with __attribute__((constructor)) in our ELF library, we can get code execution in the zygote as\r\nsoon as it calls dlopen() on startup.\r\nThe missing step at this point is that when the attack is performed, the zygote is already running; and this attack\r\nonly works while the zygote is starting up.\r\nCrashing the system\r\nThis part is a bit unpleasant.\r\nWhen a critical system component (in particular, the zygote or system_server) crashes (which you can simulate on\r\nan eng build using kill), Android attempts to automatically recover from the crash by restarting most userspace\r\nprocesses (including the zygote). When this happens, the screen first shows the boot animation for a bit, followed\r\nby the lock screen with the \"Unlock for all features and data\" prompt that normally only shows up after boot.\r\nHowever, the key material for accessing user data is still present at this point, as you can verify if ADB is on by\r\nrunning \"ls /sdcard\" on the device.\r\nThis means that if we can somehow crash system_server, we can then inject code into the zygote during the\r\nfollowing userspace restart and will be able to access user data on the device.\r\nOf course, mounting our own filesystem over /data is very crude and makes all sorts of things fail, but\r\nsurprisingly, the system doesn't immediately fall over - while parts of the UI become unusable, most places have\r\nsome error handling that prevents the system from failing so clearly that a restart happens.\r\nAfter some experimentation, it turned out that Android's code for tracking bandwidth usage has a safety check: If\r\nthe network usage tracking code can't write to disk and \u003e=2MiB (mPersistThresholdBytes) of network traffic have\r\nbeen observed since the last successful write, a fatal exception is thrown. This means that if we can create some\r\nsort of network connection to the device and then send it \u003e=2MiB worth of ping flood, then trigger a stats\r\nhttps://googleprojectzero.blogspot.com/2018/09/oatmeal-on-universal-cereal-bus.html\r\nPage 6 of 11\n\nwriteback by either waiting for a periodic writeback or changing the state of a network interface, the device will\r\nreboot.\r\nTo create a network connection, there are two options:\r\nConnect to a wifi network. Before Android 9, even when the device is locked, it is normally possible to\r\nconnect to a new wifi network by dragging down from the top of the screen, tapping the drop-down below\r\nthe wifi symbol, then tapping on the name of an open wifi network. (This doesn't work for networks\r\nprotected with WPA, but of course an attacker can make their own wifi network an open one.) Many\r\ndevices will also just autoconnect to networks with certain names.\r\nConnect to an ethernet network. Android supports USB ethernet adapters and will automatically connect to\r\nethernet networks.\r\nFor testing the exploit, a manually-created connection to a wifi network was used; for a more reliable and user-friendly exploit, you'd probably want to use an ethernet connection.\r\nAt this point, we can run arbitrary native code in zygote context and access user data; but we can't yet read out the\r\nraw disk encryption key, directly access the underlying block device, or take a RAM dump (although at this point,\r\nhalf the data that would've been in a RAM dump is probably gone anyway thanks to the system crash). If we want\r\nto be able to do those things, we'll have to escalate our privileges a bit more.\r\nFrom zygote to vold\r\nEven though the zygote is not supposed to be part of the TCB, it has access to the CAP_SYS_ADMIN capability\r\nin the initial user namespace, and the SELinux policy permits the use of this capability. The zygote uses this\r\ncapability for the mount() syscall and for installing a seccomp filter without setting the NO_NEW_PRIVS flag.\r\nThere are multiple ways to abuse CAP_SYS_ADMIN; in particular, on the Pixel 2, the following ways seem\r\nviable:\r\nYou can install a seccomp filter without NO_NEW_PRIVS, then perform an execve() with a privilege\r\ntransition (SELinux exec transition, setuid/setgid execution, or execution with permitted file capability set).\r\nThe seccomp filter can then force specific syscalls to fail with error number 0 - which e.g. in the case of\r\nopen() means that the process will believe that the syscall succeeded and allocated file descriptor 0. This\r\nattack works here, but is a bit messy.\r\nYou can instruct the kernel to use a file you control as high-priority swap device, then create memory\r\npressure. Once the kernel writes stack or heap pages from a sufficiently privileged process into the swap\r\nfile, you can edit the swapped-out memory, then let the process load it back. Downsides of this technique\r\nare that it is very unpredictable, it involves memory pressure (which could potentially cause the system to\r\nkill processes you want to keep, and probably destroys many forensic artifacts in RAM), and requires some\r\nway to figure out which swapped-out pages belong to which process and are used for what. This requires\r\nthe kernel to support swap.\r\nYou can use pivot_root() to replace the root directory of either the current mount namespace or a newly\r\ncreated mount namespace, bypassing the SELinux checks that would have been performed for mount().\r\nhttps://googleprojectzero.blogspot.com/2018/09/oatmeal-on-universal-cereal-bus.html\r\nPage 7 of 11\n\nDoing it for a new mount namespace is useful if you only want to affect a child process that elevates its\r\nprivileges afterwards. This doesn't work if the root filesystem is a rootfs filesystem. This is the technique\r\nused here.\r\nIn recent Android versions, the mechanism used to create dumps of crashing processes has changed: Instead of\r\nasking a privileged daemon to create a dump, processes execute one of the helpers /system/bin/crash_dump64 and\r\n/system/bin/crash_dump32, which have the SELinux label u:object_r:crash_dump_exec:s0. Currently, when a file\r\nwith such a label is executed by any SELinux domain, an automatic domain transition to the crash_dump domain\r\nis triggered (which automatically implies setting the AT_SECURE flag in the auxiliary vector, instructing the\r\nlinker of the new process to be careful with environment variables like LD_PRELOAD):\r\ndomain_auto_trans(domain, crash_dump_exec, crash_dump);\r\nAt the time this bug was reported, the crash_dump domain had the following SELinux policy:\r\n[...]\r\nallow crash_dump {\r\n domain\r\n -init\r\n -crash_dump\r\n -keystore\r\n -logd\r\n}:process { ptrace signal sigchld sigstop sigkill };\r\n[...]\r\nr_dir_file(crash_dump, domain)\r\n[...]\r\nThis policy permitted crash_dump to attach to processes in almost any domain via ptrace() (providing the ability\r\nto take over the process if the DAC controls permit it) and allowed it to read properties of any process in procfs.\r\nThe exclusion list for ptrace access lists a few TCB processes; but notably, vold was not on the list. Therefore, if\r\nwe can execute crash_dump64 and somehow inject code into it, we can then take over vold.\r\nNote that the ability to actually ptrace() a process is still gated by the normal Linux DAC checks, and crash_dump\r\ncan't use CAP_SYS_PTRACE or CAP_SETUID. If a normal app managed to inject code into crash_dump64, it\r\nstill wouldn't be able to leverage that to attack system components because of the UID mismatch.\r\nIf you've been reading carefully, you might now wonder whether we could just place our own binary with context\r\nu:object_r:crash_dump_exec:s0 on our fake /data filesystem, and then execute that to gain code execution in the\r\ncrash_dump domain. This doesn't work because vold - very sensibly - hardcodes the MS_NOSUID flag when\r\nhttps://googleprojectzero.blogspot.com/2018/09/oatmeal-on-universal-cereal-bus.html\r\nPage 8 of 11\n\nmounting USB storage devices, which not only degrades the execution of classic setuid/setgid binaries, but also\r\ndegrades the execution of files with file capabilities and executions that would normally involve automatic\r\nSELinux domain transitions (unless the SELinux policy explicitly opts out of this behavior by granting\r\nPROCESS2__NOSUID_TRANSITION).\r\nTo inject code into crash_dump64, we can create a new mount namespace with unshare() (using our\r\nCAP_SYS_ADMIN capability), then call pivot_root() to point the root directory of our process into a directory we\r\nfully control, and then execute crash_dump64. Then the kernel parses the ELF headers of crash_dump64, reads\r\nthe path to the linker (/system/bin/linker64), loads the linker into memory from that path (relative to the process\r\nroot, so we can supply our own linker here), and executes it.\r\nAt this point, we can execute arbitrary code in crash_dump context and escalate into vold from there,\r\ncompromising the TCB. At this point, Android's security policy considers us to have kernel-equivalent privileges;\r\nhowever, to see what you'd have to do from here to gain code execution in the kernel, this blogpost goes a bit\r\nfurther.\r\nFrom vold to init context\r\nIt doesn't look like there is an easy way to get from vold into the real init process; however, there is a way into the\r\ninit SELinux context. Looking through the SELinux policy for allowed transitions into init context, we find the\r\nfollowing policy:\r\ndomain_auto_trans(kernel, init_exec, init)\r\nThis means that if we can get code running in kernel context to execute a file we control labeled init_exec, on a\r\nfilesystem that wasn't mounted with MS_NOSUID, then our file will be executed in init context.\r\nThe only code that is running in kernel context is the kernel, so we have to get the kernel to execute the file for us.\r\nLinux has a mechanism called \"usermode helpers\" that can do this: Under some circumstances, the kernel will\r\ndelegate actions (such as creating coredumps, loading key material into the kernel, performing DNS lookups, ...)\r\nto userspace code. In particular, when a nonexistent key is looked up (e.g. via request_key()), /sbin/request-key\r\n(hardcoded, can only be changed to a different static path at kernel build time with\r\nCONFIG_STATIC_USERMODEHELPER_PATH) will be invoked.\r\nBeing in vold, we can simply mount our own ext4 filesystem over /sbin without MS_NOSUID, then call\r\nrequest_key(), and the kernel invokes our request-key in init context.\r\nThe exploit stops at this point; however, the following section describes how you could build on it to gain code\r\nexecution in the kernel.\r\nFrom init context to the kernel\r\nFrom init context, it is possible to transition into modprobe or vendor_modprobe context by executing an\r\nappropriately labeled file after explicitly requesting a domain transition (note that this is domain_trans(), which\r\npermits a transition on exec, not domain_auto_trans(), which automatically performs a transition on exec):\r\nhttps://googleprojectzero.blogspot.com/2018/09/oatmeal-on-universal-cereal-bus.html\r\nPage 9 of 11\n\ndomain_trans(init, { rootfs toolbox_exec }, modprobe)\r\ndomain_trans(init, vendor_toolbox_exec, vendor_modprobe)\r\nmodprobe and vendor_modprobe have the ability to load kernel modules from appropriately labeled files:\r\nallow modprobe self:capability sys_module;\r\nallow modprobe { system_file }:system module_load;\r\nallow vendor_modprobe self:capability sys_module;\r\nallow vendor_modprobe { vendor_file }:system module_load;\r\nAndroid nowadays doesn't require signatures for kernel modules:\r\nwalleye:/ # zcat /proc/config.gz | grep MODULE\r\nCONFIG_MODULES_USE_ELF_RELA=y\r\nCONFIG_MODULES=y\r\n# CONFIG_MODULE_FORCE_LOAD is not set\r\nCONFIG_MODULE_UNLOAD=y\r\nCONFIG_MODULE_FORCE_UNLOAD=y\r\nCONFIG_MODULE_SRCVERSION_ALL=y\r\n# CONFIG_MODULE_SIG is not set\r\n# CONFIG_MODULE_COMPRESS is not set\r\nCONFIG_MODULES_TREE_LOOKUP=y\r\nCONFIG_ARM64_MODULE_CMODEL_LARGE=y\r\nCONFIG_ARM64_MODULE_PLTS=y\r\nCONFIG_RANDOMIZE_MODULE_REGION_FULL=y\r\nCONFIG_DEBUG_SET_MODULE_RONX=y\r\nTherefore, you could execute an appropriately labeled file to execute code in modprobe context, then load an\r\nappropriately labeled malicious kernel module from there.\r\nLessons learned\r\nNotably, this attack crosses two weakly-enforced security boundaries: The boundary from blkid_untrusted to vold\r\n(when vold uses the UUID provided by blkid_untrusted in a pathname without checking that it resembles a valid\r\nhttps://googleprojectzero.blogspot.com/2018/09/oatmeal-on-universal-cereal-bus.html\r\nPage 10 of 11\n\nUUID) and the boundary from the zygote to the TCB (by abusing the zygote's CAP_SYS_ADMIN capability).\r\nSoftware vendors have, very rightly, been stressing for quite some time that it is important for security researchers\r\nto be aware of what is, and what isn't, a security boundary - but it is also important for vendors to decide where\r\nthey want to have security boundaries and then rigorously enforce those boundaries. Unenforced security\r\nboundaries can be of limited use - for example, as a development aid while stronger isolation is in development -,\r\nbut they can also have negative effects by obfuscating how important a component is for the security of the overall\r\nsystem.\r\nIn this case, the weakly-enforced security boundary between vold and blkid_untrusted actually contributed to the\r\nvulnerability, rather than mitigating it. If the blkid code had run in the vold process, it would not have been\r\nnecessary to serialize its output, and the injection of a fake UUID would not have worked.\r\nSource: https://googleprojectzero.blogspot.com/2018/09/oatmeal-on-universal-cereal-bus.html\r\nhttps://googleprojectzero.blogspot.com/2018/09/oatmeal-on-universal-cereal-bus.html\r\nPage 11 of 11",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"MITRE"
	],
	"references": [
		"https://googleprojectzero.blogspot.com/2018/09/oatmeal-on-universal-cereal-bus.html"
	],
	"report_names": [
		"oatmeal-on-universal-cereal-bus.html"
	],
	"threat_actors": [],
	"ts_created_at": 1775434263,
	"ts_updated_at": 1775791322,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/f656f8d2a57c342ba2b22ca4a462c95d724ede88.pdf",
		"text": "https://archive.orkl.eu/f656f8d2a57c342ba2b22ca4a462c95d724ede88.txt",
		"img": "https://archive.orkl.eu/f656f8d2a57c342ba2b22ca4a462c95d724ede88.jpg"
	}
}