A mini-article about CVE-2017-6074 — a double-free vulnerability I found in the Linux kernel DCCP sockets. Contains a brief description of the LPE exploit I wrote for this bug. Originally posted as an announcement on the OSS-Security mailing list.

Overview

While fuzzing the Linux kernel with syzkaller, I found a double-free bug in the DCCP sockets. This bug can be exploited to gain kernel code execution from an unprivileged process.

The bug was likely introduced in the first kernel release with DCCP support: 2.6.14 from October 2005. However, the oldest kernel version that I checked to be vulnerable is 2.6.18 from September 2006.

Together with the network subsystem maintainers, I fixed the bug with 5edabca9d4cf (“dccp: fix freeing skb too early for IPV6_RECVPKTINFO”) committed on February 17th, 2017.

The kernel needs to be built with CONFIG_IP_DCCP enabled to be vulnerable. Many modern distributions enable this option by default.

Bug details

In the current DCCP implementation, an skb for a DCCP_PKT_REQUEST packet is forcibly freed via __kfree_skb() in dccp_rcv_state_process() if dccp_v6_conn_request() successfully returns.

However, if IPV6_RECVPKTINFO is set on a socket, the address of the skb is saved to ireq->pktopts and the ref count for skb is incremented in dccp_v6_conn_request() to mark that the skb is still in use. Nevertheless, the skb still gets freed in dccp_rcv_state_process().

This bug is technically a use-after-free followed by a double-free. The kernel frees the skb in dccp_rcv_state_process(), and then keeps using it until freeing again when destroying the socket due to inet6_destroy_sock(). There’s more going on under the hood, but this is the essential part.

The fix is to call consume_skb(), which accounts for skb->users, instead of doing goto discard and therefore calling __kfree_skb() in dccp_rcv_state_process().

Exploitation

I wrote a proof-of-concept exploit that gets root on Ubuntu 16.04 with the 4.4.0-62-generic kernel. The exploit includes SMEP and SMAP bypasses.

As I mentioned, the bug causes both a use-after-free and a double-free. The use-after-free happens on skb and skb->data, which are allocated and freed one after another. Exploiting this use-after-free allows overwriting skb or skb->data with arbitrary data.

Targeting the double-free, however, is more powerful. It allows controlling what object gets overwritten by turning the double-free into another use-after-free:

// The first free:
kfree(dccp_skb)

// Another object allocated on the same place as dccp_skb:
some_object = kmalloc()

// The second free; effectively frees some_object:
kfree(dccp_skb)

This way, I could get a use-after-free on some_object. As I could control what object that would be, I could overwrite its contents with arbitrary data by using some of the kernel slab spraying techniques. If the overwritten object had any triggerable function pointers, I could get to execute arbitrary code within the kernel.

To control the execution flow, I applied the same technique that I used for exploiting a double-free in the Linux kernel USB MIDI driver.

I overwrote skb->data, since it has skb_shared_info struct at the end. And skb_shared_info->destructor_arg->callback is a function pointer, which is triggered by skb_release_data. My exploit puts the ubuf_info struct for destructor_arg and the shellcode for getting root into userspace, points callback to the shellcode, and triggers it.

However, as the data is placed in userspace, the exploit gets detected by SMAP and SMEP.

I decided to disable these protections using the idea from the CVE-2016-8655 exploit by Philip Pettersson. Phillip’s technique relies on overwriting a packet_sock struct, which has a timer_list field deep inside of it. And timer_list contains both a callback and its argument.

I allocated packet_sock, overwrote the timer_list field, and scheduled the timer. I used native_write_cr4 as the callback and a fake CR4 value with both SMEP and SMAP bits disabled as its argument. Note that CVE-2016-8655 used a use-after-free on the packet_sock struct, but in my case, I made a use-after-free happen by leveraging a double-free.

The exploit is not very reliable, but I don’t want to spend any more time on it. The kernel can crash due to a memory corruption if it fails to reallocate objects in time or in the correct order. I, however, managed to make it work in three different environments (including two VMs and one physical machine).

💜 Thank you for reading!

Timeline

  • 15 Feb, 2017 — Bug reported to security@kernel.org
  • 16 Feb, 2017 — Patch submitted to netdev@
  • 17 Feb, 2017 — Mainline fix is committed
  • 18 Feb, 2017 — Notification sent to linux-distros@
  • 22 Feb, 2017 — Public announcement sent to oss-security@
  • 26 Feb, 2017 — Write-up and exploit published

🐱 About me

I’m a security researcher and a software engineer focusing on the Linux kernel.

I contributed to several security-related Linux kernel subsystems and tools: KASAN — a fast dynamic bug detector, syzkaller — a production-grade kernel fuzzer, and Arm Memory Tagging Extension — an exploit mitigation.

I also wrote a few Linux kernel exploits for the bugs I found.

Occasionally, I’m having fun with hardware hacking, teaching, and other random stuff.

Follow me @andreyknvl on Twitter or @xairy on LinkedIn for notifications about new articles and talks.