Ad Hide

CVE-2016-5195: Dirty COW explained

Just recently CVE-2016-5195 or 'Dirty COW' was fully disclosed. This is a Linux Kernel 2.6.22 < 3.9 (x86/x64) race condition which can lead to local privilege escalation. Meaning any user (or any malicious application for that matter) on a vulnerable linux host can access root, unauthorized. This exploit is very serious due to many reasons. First, the problem lies deep within the linux kernel, this is the base which almost every linux distro is built on, from the last nine years or so. Including Ubuntu, CentOS, RedHat and Debian. Second, it's a very easy and reliable exploit to implement in malicious payloads. And finally, it has been found in the wild by Phil Oester which means it has probably been used for years. The original coder of the exploit is unkown which makes this so much more serious than it already is.

Altough the severity of this exploit, it's very fascinating to see why this problem exists and how it was solved. While researching this exploit I tested it multiple times on different linux distro's all with succes on every try (expect one time the host crashed, but still delivered on the next reboot). The screenshots provided in this post are from a fresh Ubuntu 15.10 image. So without any further ado, here are the technicalities.

In action
On linux you have some files which are owned by root and as any other user you can read these files, but not write to them. An example of this is 'ping'. Ping is an application owned by root, but executable by any other user. So if we could somehow edit the files of the ping command and add a reverse shell payload in this code and we execute a 'ping' command again we get a reverse shell with root permissions! Here is the proof-of-concept in action.
You'll see that the original text of "You can't overwrite this file\!" got changed to "Yes I can\!rite to this file\!". So if we read the file first and add to the payload we can copy the original and successfully add to a file.

Notice the end of the file gets clipped. When using this PoC keep in mind the end string can't be longer than the original. Also, this is a race condition which means it could be successful at any time between the 100000000 cycles of the PoC. Anyhow, in my cases the change was instantly every time so I ctrl C'd my way trough the exploit instead of waiting for the 100000000 cycles to complete.
So we've successfully edited a root file as a standard user. This is BAD.(or great?)

Let's dive in to the source code of the exploit and start with the main() function.

On line 87 we open the root file as read-only (O_RDONLY flag). On line 88 we get link the status of this file to 'st' with a pointer. And after that store the name of the root_file is 'name'. Pretty basic stuff.

On line 101 we create a new mapping in the virtual address space of the calling process. This means the opened file 'f' from line 87 gets a new address in the memory. When mapping the file to the memory address we don't put the file in our RAM but we make a 'connection' to the place where the file is stored on our drive. So we are reading directly from our drive, not a copy of it in our RAM (the conventional way). In the manual of mmap we find:

The 'addr' field is set to NULL so the kernel chooses the new memory address. The 'length' parameter is set to 'st.size' so the size of the root_file. Then we have the 'prot' parameter, again a read-only flag (PROT_READ). Now the most important field 'flags'. Here we give the flag 'MAP_PRIVATE'. This makes the mapped memory field 'copy-on-write' (this is where the COW of 'Dirty COW' originated). With the COW flag we tell the memory that if we want to edit the file, it will create a copy for us to write to, NOT the original file. After that we have the 'fd' field where our file is specified and 'offset' where the memory should start reading, in our case, 0.

Now, CVE-2016-5195 is a race condition. This means that in normal circumstances the bug shouldn't happen but by forcing 2 threads to do what they do (we'll see those in a second) and repeating this over and over again, we try to 'race' certain conditions where you are writing a 'copy' file en at the same time tell the memory of the 'copy' it's not needed anymore and tricking the kernel into writing the root_file.
First of, we'll be looking into the 'madviseThread' method, this one gets the parameter with the root_file.

This is the most important line of the madviseThread. 'madvise' system call is used to give advice or directions to the kernel on how to handle given address ranges. Here we give the kernel advice flag 'MADV_DONTNEED', this means we tell the kernel the address range is not needed anymore and the kernel can free the resources associated with it. This address range is now considered 'dirty' (the first part of 'Dirty COW'). When using a block of memory is flagged with a dirty bit, the address range still has to be written to the underlying file (the root_file) but is in a cached state where it can be propagated accordingly, i.e. save the file to the right place on the disk. But with the 'MADV_DONTNEED' flag we tell the kernel it can be thrown away without writing to the drive. When you want to re-use 'MADV_DONTNEED' flagged memory 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.

Now let's take a look at the procselfmemThread.
At line 61 we open a 'pseudo-file' named /proc/self/mem as read-only. In linux, most recourses are managed as 'pseudo-files'. You can read more about this over here. These pseudo-files are actual resources being used in the runtime. So if you read the file /proc/self/mem you get a full dump of the current memory allocated for the application.
On line 67 we set our cursor at the beginning of the mapped file so if we write the new string on line 68 we are actually writing over the mapped memory of the file which creates a copy of that file to write to because of the COW flag.

If these two threads would run next to each other there would not be a problem.
But if you keep repeating them over and over again in different threads they will not be running synchronised so the race begins.

When the write() is performed while the memory is being cleared the kernel seems to write over the original file, allowing us to successfully write over a write protected file owned by root. This phenomenon was theorized by Linus Torvalds many years ago and attempted to fix it without succes, but since CPU's got faster and faster the race condition became easier to trigger and thus CVE-2016-5195 happened.

In the git repo of the linux kernel we can see how Mr. Torvalds fixed the issue. The full repo can be visited over here.

We can see he added a new flag FOLL_COW that only triggers when the full COW cycle is completed. Meaning the race condition is eliminated.

Final note
Dirty COW exploit impacts an enormous amount of devices. Even android devices have been confirmed to be affected. Every user of a linux powered device should be aware of this and update the kernel ASAP. I tried clarify this exploit as much as possible so if you've read this far I hope you learned as much as I did! For more information please visit Dirty COW's website and their GIT repo.


Get notified!

Join the list and get notified when new blog posts are added!