This summer, I utilized ForAllSecure Mayhem, a next-generation fuzz testing solution, to analyze software that are heavily used. I felt these types of components in particular deserve more scrutiny from a security perspective. It is often believed that software that is frequently reused is more secure, because it has been reviewed by many developers. However, this thought is a misconception.
I chose to analyze Das U-Boot, a bootloader common on embedded devices, including Amazon Kindles, ARM Chromebooks, networking hardware, and more. In addition to U-Boot being widely used, my friend Benjamin Lim recently found this bug in U-Boot, which indicated to me that there might be more bugs to uncover.
What is Das U-Boot?
A bootloader is usually the first thing that runs on a system. It sets up the hardware, so it can load the next stage of software, such as an operating system like Linux. U-Boot accesses several layers when loading an image from disk, a storage mechanism where data is recorded. While images can be loaded in other ways, such as from a network, we’ll focus on images loaded from disk:
- Hardware. Storage devices, such as USB or i2c, have a hardware-specific driver and can host multiple types of devices.
- Block. Generic routines that talk with the USB or i2c driver to read blocks of data from the storage device.
- Partition: Disks are often divided into logical partitions. A typical partition scheme would have several partitions -- one for boot images, one for read-only data, one for permanent storage of settings and user data, a copy of known-good firmware for recovery and so on. U-Boot needs to know where each partition starts and ends.
- Filesystem: Each partition has a filesystem, such as ext4, btrfs, or FAT. One of these will contain the kernel and initramfs.
U-Boot's filesystem-drivers are important to secure for a couple reasons:
- They are a relatively easy attack path. Plug in a flash drive or SD card, and the attack lies dormant until the target attempts to access it. The target might attempt to load a signed recovery image, for example, at which point the attack will be initiated.
- If the filesystem is attacked, it compromises any trusted-boot. The attacker can get full control of the CPU, and modify anything they want.
Unleashing Mayhem on U-Boot
Although the hardware and block layers are at a low enough level to require specific hardware -- meaning it’s harder to analyze in isolation -- the partition and filesystem layers are easier. U-Boot has a convenient sandbox mode, which lets users compile it as a Linux user-space program. This allows us and Mayhem to test outside of an embedded system, which is perfect for efficient dynamic analysis without requiring complicated harnesses.
In the first hour of analysis, Mayhem found five divide-by-zeros triggered by invalid ext filesystems. I left it running overnight, and by the next day, it had found a recursive stack overflow in the DOS partition parser, a pair of buffer-overflows in ext4, and a double-free in ext4. The divide-by-zeros aren't severe, but the other bugs lead to memory corruption. Memory corruption is the contents of a memory location is unintendedly alters due to a programming error. Depending on specific hardware and U-Boot configuration, they may lead to code execution. Code execution is when a nefarious actor is able to execute arbitrary commands or code on a target machine or in a target process without authorization. After disclosing these to U-Boot these vulnerabilities were assigned:
My favorite bug of the crowd is the DOS partition overflow (CVE-2019-13103). The basic idea here is simple: if a DOS partition table refers to itself, it can cause infinite recursion. Infinite recursion causes the stack to grow each time the function calls itself. As with the other vulnerabilities, this is especially problematic in a bootloader like U-Boot since memory protections haven't been set up yet -- meaning the CPU will assume anything is readable, writable, and executable. This means that memory corruption can be disastrous, because code regions can be modified or control flow simply transferred to writable buffers.
After Mayhem found the bug it was easy to analyze U-Boot to understand what was going on. An instance of this bug in the DOS partition handling can be seen in the code snippet below:
While writing this post, it occurred to me that I could also run Mayhem against a virtualized target. I compiled U-Boot for the ARM vexpress-a15 platform and ran it under QEMU. I attached GDB and tried to open a filesystem created by Mayhem. U-Boot hung, which is what I hypothesized would happen, so I took a look closer.
When I interrupted the hung process, the stack pointer was nearing the beginning of memory. I set a conditional breakpoint to just before it ran off the end and single-stepped. Upon closer inspection, I saw that the stack runs off into no-man’s-land. In QEMU, the stack is filled with 0s and doesn't throw any sort of error when accessed. Eventually, the program attempts to return, finds its return address to be 0, and starts executing from there, resulting in a hang.
At least in QEMU, the emulated ARM CPU is happy to execute a bunch of NOPs from this memory location, until, after many megabytes, it reaches some data and returns to 0 again. Depending on the exact system and software installed, something worse could happen. For example, other data in this part of the address space could get executed and lead to other anomalous behaviors, including the ability to run attacker provided code.
After uncovering these issues, I reached out to the maintainers of Das U-Boot, who worked with me to quickly fix these issues. A big thank you to the Das U-Boot team!
As you can see, analyzing software with Mayhem, or potentially other fuzzing tools, can lead to the discovery of deep, serious defects. The creators of U-Boot have already done the hard work of pulling out the filesystem parsing, so it can be run from userland on a standalone file, not only from data read off a physical disk. From that stage, loading the software into Mayhem is quite simple and finds real flaws quickly. As always, we highly recommend developers set up fuzzing on their own code to try to weed out bugs early.
For our technical readers, it may be interesting to contrast our findings to some of the other recent analyses of U-Boot. Unlike static analysis tools, Mayhem primarily focuses on searching for bugs on the attack surface that was selected--in this instance the filesystem-drivers. Further, Mayhem generates actual program inputs that trigger different conditions and take different paths within the application. Mayhem produces these inputs on its own, without needing to create rules or guidance from an expert on what to look for. The benefit is that for every bug, Mayhem is able to share back a test case to reproduce the issue that can be sent to the developer for easier and faster debugging.
Read featured article on ThreatPost.