Linux内核分析 | CVE-2017-1000112(UDP Fragment Offload)
Posted 看雪学院
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux内核分析 | CVE-2017-1000112(UDP Fragment Offload)相关的知识,希望对你有一定的参考价值。
本文为看雪论坛优秀文章
看雪论坛作者ID:ScUpax0s
CVE 基础
TCP/IP
UFO机制
UDP corking机制
4.4.0内核下 ip_append_data 源码分析
基本流程
ip_append_data的准备工作
int ip_append_data(struct sock *sk, struct flowi4 *fl4,
int getfrag(void *from, char *to, int offset, int len,
int odd, struct sk_buff *skb),
void *from, int length, int transhdrlen,
struct ipcm_cookie *ipc, struct rtable **rtp,
unsigned int flags)
{
struct inet_sock *inet = inet_sk(sk);
int err;
if (flags&MSG_PROBE) //是否开启MSG_PROBE
return 0;
if (skb_queue_empty(&sk->sk_write_queue)) {
err = ip_setup_cork(sk, &inet->cork.base, ipc, rtp);
if (err)
return err;
} else {
/*队列不为空,则使用上次的路由,IP选项,以及分片长度 */
transhdrlen = 0;
}
return __ip_append_data(sk, fl4, &sk->sk_write_queue, &inet->cork.base,
sk_page_frag(sk), getfrag,
from, length, transhdrlen, flags);
}
__ip_append_data
static int __ip_append_data(struct sock *sk,
struct flowi4 *fl4,
struct sk_buff_head *queue,
struct inet_cork *cork,
struct page_frag *pfrag,
int getfrag(void *from, char *to, int offset,
int len, int odd, struct sk_buff *skb),
void *from, int length, int transhdrlen,
)
{
struct inet_sock *inet = inet_sk(sk);
struct sk_buff *skb; //分配一个新的sk_buff准备将它放入sk_write_queue队列,稍后函数为数据加入IP头信息即可往下传输
struct ip_options *opt = cork->opt;
int hh_len;
int exthdrlen;
int mtu;
int copy;
int err;
int offset = 0;
unsigned int maxfraglen, fragheaderlen, maxnonfragsize;
int csummode = CHECKSUM_NONE;
struct rtable *rt = (struct rtable *)cork->dst;
u32 tskey = 0;
skb = skb_peek_tail(queue); //获取skb队列的尾结点。
exthdrlen = !skb ? rt->dst.header_len : 0;
mtu = cork->fragsize;
if (cork->tx_flags & SKBTX_ANY_SW_TSTAMP &&
sk->sk_tsflags & SOF_TIMESTAMPING_OPT_ID)
tskey = sk->sk_tskey++;
hh_len = LL_RESERVED_SPACE(rt->dst.dev); //获取链路层header长度
fragheaderlen = sizeof(struct iphdr) + (opt ? opt->optlen : 0); //IP头部长度
maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen; //最大IP头部长度,考虑了对齐
maxnonfragsize = ip_sk_ignore_df(sk) ? 0xFFFF : mtu; //是否超过64k?最大为64k。
if (cork->length + length > maxnonfragsize - fragheaderlen) {
ip_local_error(sk, EMSGSIZE, fl4->daddr, inet->inet_dport,
mtu - (opt ? opt->optlen : 0));
return -EMSGSIZE;
}
/*
* transhdrlen > 0 means that this is the first fragment and we wish
* it won't be fragmented in the future.
transhdrlen!=0说明ip_append_data工作在第一个片段
transhdrlen =0说明ip_append_data没有工作在第一个片段
*/
if (transhdrlen &&
length + fragheaderlen <= mtu &&
rt->dst.dev->features & NETIF_F_V4_CSUM &&
!(flags & MSG_MORE) &&
!exthdrlen)
csummode = CHECKSUM_PARTIAL; //校验和计算?
cork->length += length; //软木塞长度更新
if (((length > mtu) || (skb && skb_is_gso(skb))) &&
(sk->sk_protocol == IPPROTO_UDP) &&
(rt->dst.dev->features & NETIF_F_UFO) && !rt->dst.header_len &&
(sk->sk_type == SOCK_DGRAM)) {
/*
对于一个UDP包如果满足:
1.要发送的数据长度大于mtu,需要进行分片
2.开启了UFO支持
*/
err = ip_ufo_append_data(sk, queue, getfrag, from, length,
hh_len, fragheaderlen, transhdrlen,
maxfraglen, flags); //调用支持UFO机制的ip_ufo_append_data
if (err)
goto error;
return 0;
}
/* So, what's going on in the loop below?
*
* We use calculated fragment length to generate chained skb,
* each of segments is IP fragment ready for sending to network after
* adding appropriate IP header.
*/
//如果skb为空,即sk_buff队列此时为空,那么跳转到alloc_new_skb。
if (!skb)
goto alloc_new_skb;
copy 代表的是最后一个skb的剩余空间。
copy<0
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)//当skb存在时,需要计算究竟要从上一个skb中拿多长的数据到下一个新的skb
fraggap = skb_prev->len - maxfraglen; //这里其实就是负的copy
else
fraggap = 0;
/*
* If remaining data exceeds the mtu,
* we know we need more fragment(s).
*/
datalen = length + fraggap;
if (datalen > mtu - fragheaderlen)
datalen = maxfraglen - fragheaderlen;
fraglen = datalen + fragheaderlen;
if ((flags & MSG_MORE) &&
!(rt->dst.dev->features&NETIF_F_SG))
alloclen = mtu; //最大分配大小
else
alloclen = fraglen; //确切分配大小,fraglen = datalen + fragheaderlen
alloclen += exthdrlen;
/* The last fragment gets additional space at tail.
* Note, with MSG_MORE we overallocate on fragments,
* because we have no idea what fragment will be
* the last.
*/
if (datalen == length + fraggap)
alloclen += rt->dst.trailer_len;
if (transhdrlen) {
//如果是第一个分片
//调用sock_alloc_send_skb分配新的skb
skb = sock_alloc_send_skb(sk,
alloclen + hh_len + 15,
(flags & MSG_DONTWAIT), &err);
} else {
//如果不是第一个分片
skb = NULL;
if (atomic_read(&sk->sk_wmem_alloc) <=
2 * sk->sk_sndbuf)
skb = sock_wmalloc(sk,
alloclen + hh_len + 15, 1,
sk->sk_allocation);
if (unlikely(!skb))
err = -ENOBUFS;
}
if (!skb)
goto error; //分配失败跳转到error
/*
* Fill in the control structures
*/
skb->ip_summed = csummode;
skb->csum = 0;
skb_reserve(skb, hh_len);
/* only the initial fragment is time stamped */
skb_shinfo(skb)->tx_flags = cork->tx_flags;
cork->tx_flags = 0;
skb_shinfo(skb)->tskey = tskey;
tskey = 0;
/*
* Find where to start putting bytes.
*/
data = skb_put(skb, fraglen + exthdrlen);//预留L2,L3首部空间
skb_set_network_header(skb, exthdrlen); //设置L3层的指针
skb->transport_header = (skb->network_header +
fragheaderlen);
data += fragheaderlen + exthdrlen;
if (fraggap) { //填充原来skb的尾部
skb->csum = skb_copy_and_csum_bits(
skb_prev, maxfraglen,
data + transhdrlen, fraggap, 0);
// skb_copy_and_csum_bits函数将数据从第一个创建的sk_buff复制到新分配的sk_buff
skb_prev->csum = csum_sub(skb_prev->csum,
skb->csum);
data += fraggap;
pskb_trim_unique(skb_prev, maxfraglen);
}
copy = datalen - transhdrlen - fraggap;
if (copy > 0 && getfrag(from, data + transhdrlen, offset, copy, fraggap, skb) < 0) {
err = -EFAULT;
kfree_skb(skb);
goto error;
}
offset += copy;
length -= datalen - fraggap;
transhdrlen = 0;
exthdrlen = 0;
csummode = CHECKSUM_NONE;
/*
* Put the packet on the pending queue.
*/
__skb_queue_tail(queue, skb); //把新skb插入skb队列队尾
continue;
}
漏洞分析与利用
poc/exp
poc/exp分析
int size = SHINFO_OFFSET + sizeof(struct skb_shared_info);
int rv = send(s, buffer, size, MSG_MORE);
int val = 1;
rv = setsockopt(s, SOL_SOCKET, SO_NO_CHECK, &val, sizeof(val));
send(s, buffer, 1, 0); //第二次send的size为1
close(s);
static inline int ip_ufo_append_data(struct sock *sk,
struct sk_buff_head *queue,
int getfrag(void *from, char *to, int offset, int len,
int odd, struct sk_buff *skb),
void *from, int length, int hh_len, int fragheaderlen,
int transhdrlen, int maxfraglen, unsigned int flags)
{
struct sk_buff *skb;
int err;
/* There is support for UDP fragmentation offload by network
* device, so create one single skb packet containing complete
* udp datagram
*/
skb = skb_peek_tail(queue); //取skb队列的队尾
if (!skb) {
skb = sock_alloc_send_skb(sk,
hh_len + fragheaderlen + transhdrlen + 20,
(flags & MSG_DONTWAIT), &err);
if (!skb)
return err;
/* reserve space for Hardware header */
skb_reserve(skb, hh_len);
/* create space for UDP/IP header */
skb_put(skb, fragheaderlen + transhdrlen);
/* initialize network header pointer */
skb_reset_network_header(skb);
/* initialize protocol header pointer */
skb->transport_header = skb->network_header + fragheaderlen;
skb->csum = 0;
__skb_queue_tail(queue, skb);
} else if (skb_is_gso(skb)) {
goto append;
}
skb->ip_summed = CHECKSUM_PARTIAL;
/* specify the length of each IP datagram fragment */
skb_shinfo(skb)->gso_size = maxfraglen - fragheaderlen;
skb_shinfo(skb)->gso_type = SKB_GSO_UDP;
append:
return skb_append_datato_frags(sk, skb, getfrag, from,
(length - transhdrlen));
}
struct skb_shared_info {
unsigned char nr_frags;
__u8 tx_flags;
unsigned short gso_size;
/* Warning: this field is not always filled in (UFO)! */
unsigned short gso_segs;
unsigned short gso_type;
struct sk_buff *frag_list;
struct skb_shared_hwtstamps hwtstamps;
u32 tskey;
__be32 ip6_frag_id;
/*
* Warning : all fields before dataref are cleared in __alloc_skb()
*/
atomic_t dataref;
/* Intermediate layers must ensure that destructor_arg
* remains valid until skb destructor */
void * destructor_arg;
/* must be last field, see pskb_expand_head() */
skb_frag_t frags[MAX_SKB_FRAGS];
};
copy = mtu - skb->len;
if(copy<0){
char *data;
unsigned int datalen;
unsigned int fraglen;
unsigned int fraggap;
unsigned int alloclen;
struct sk_buff *skb_prev = skb;
fraggap = skb_prev->len - maxfraglen;//这里其实就是负的copy
datalen = length + fraggap;
skb_copy_and_csum_bits(skb_prev, maxfraglen,data + transhdrlen, fraggap, 0);
}
struct ubuf_info {
void (*callback)(struct ubuf_info *, bool zerocopy_success);
void *ctx;
unsigned long desc;
};
漏洞补丁
datagram exceeds MTU, consistently choose UFO or fragmentation.
split across multiple skbs, do not consider ufo.
IPv6 does not have a sendpage implementation to modify.
in udp_send_skb.
参考
看雪ID:ScUpax0s
https://bbs.pediy.com/user-home-876323.htm
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!
以上是关于Linux内核分析 | CVE-2017-1000112(UDP Fragment Offload)的主要内容,如果未能解决你的问题,请参考以下文章
《Linux内核分析》 第六节 分析Linux内核创建一个新进程的过程
linux内核分析第六周-分析Linux内核创建一个新进程的过程