CVE-2016-10229分析
漏洞描述
Linux kernel是美國Linux基金會發布的操作系統Linux所使用的內核。Linux kernel 4.5之前的版本中的udp.c文件存在安全漏洞,Linux內核中的udp.c允許遠程攻擊者通過UDP流量執行任意代碼,這些流量會在執行具有MSG_PEEK標志的recv系統調用時觸發不安全的第二次校驗和計算,遠程攻擊者可精心構造數據執行任意代碼,進一步導致本地提權,屬于高危漏洞。但由于現實情況中,基于UDP協議的服務時MSG_PEEK標志在實際使用的情況較少,受該遠程命令執行漏洞危害影響群體范圍有限。
該漏洞是來自谷歌的Eric Dumazet發現的,他說漏洞源于2015年年末的一個Linux內核補丁。
代碼分析
*__skb_recv_datagram里判斷peek,會使得skb->users加1.
https://android.googlesource.com/kernel/msm/+/android-7.1.2_r0.7/net/core/datagram.c
194 *peeked = skb->peeked; 195 if (flags & MSG_PEEK) { 196 if (_off >= skb->len && (skb->len || _off || 197 skb->peeked)) { 198 _off -= skb->len; 199 continue; 200 } 201 skb->peeked = 1; 202 atomic_inc(&skb->users);skb_shared(skb)為假,skb被多個對象引用,不會將csum結果存到skb上,所以后面的代碼會再次計算。https://android.googlesource.com/kernel/msm/+/android-7.1.2_r0.7/net/core/datagram.c
__sum16 __skb_checksum_complete(struct sk_buff *skb) {__wsum csum;__sum16 sum;csum = skb_checksum(skb, 0, skb->len, 0);/* skb->csum holds pseudo checksum */sum = csum_fold(csum_add(skb->csum, csum));if (likely(!sum)) {if (unlikely(skb->ip_summed == CHECKSUM_COMPLETE) &&!skb->csum_complete_sw)netdev_rx_csum_fault(skb->dev);}if (!skb_shared(skb)) {/* Save full packet checksum */skb->csum = csum;skb->ip_summed = CHECKSUM_COMPLETE;skb->csum_complete_sw = 1;skb->csum_valid = !sum;}return sum; } 1401 /** 1402 * skb_shared - is the buffer shared 1403 * @skb: buffer to check 1404 * 1405 * Returns true if more than one person has a reference to this 1406 * buffer. 1407 */ 1408 static inline int skb_shared(const struct sk_buff *skb) 1409 { 1410 return atomic_read(&skb->users) != 1; 1411 }這里udp_lib_checksum_complete會去計算一次校驗和,然后skb_csum_unnecessary會去判斷是否計算過,但是由于MSG_PEEK的原因,會跳到skb_copy_and_csum_datagram_iovec。
1279 if (copied < ulen || UDP_SKB_CB(skb)->partial_cov) { 1280 if (udp_lib_checksum_complete(skb)) 1281 goto csum_copy_err; 1282 } 1283 1284 if (skb_csum_unnecessary(skb)) 1285 err = skb_copy_datagram_iovec(skb, sizeof(struct udphdr), 1286 msg->msg_iov, copied); 1287 else { 1288 err = skb_copy_and_csum_datagram_iovec(skb, 1289 sizeof(struct udphdr), 1290 msg->msg_iov); 1291 1292 if (err == -EINVAL) 1293 goto csum_copy_err; 1294 }2875static inline int skb_csum_unnecessary(const struct sk_buff *skb) 2876{ 2877 return ((skb->ip_summed & CHECKSUM_UNNECESSARY) || skb->csum_valid); 2878}skb_copy_and_csum_datagram_iovec沒有考慮用戶層buf的長度,在復制時會出現越界。所以有問題不是因為計算校驗和有問題,而是因為跳到了一個不安全的復制函數里(而跳到這里的結果是第一次計算的校驗和結果沒有存,導致需要再計算)。這里由于iov->iov_len < chunk所以會到skb_copy_datagram_iovec。
/*** skb_copy_and_csum_datagram_iovec - Copy and checksum skb to user iovec.* @skb: skbuff* @hlen: hardware length* @iov: io vector** Caller _must_ check that skb will fit to this iovec.** Returns: 0 - success.* -EINVAL - checksum failure.* -EFAULT - fault during copy. Beware, in this case iovec* can be modified!*/ int skb_copy_and_csum_datagram_iovec(struct sk_buff *skb,int hlen, struct iovec *iov) {__wsum csum;int chunk = skb->len - hlen;if (!chunk)return 0;/* Skip filled elements.* Pretty silly, look at memcpy_toiovec, though 8)*/while (!iov->iov_len)iov++;if (iov->iov_len < chunk) {if (__skb_checksum_complete(skb))goto csum_error;if (skb_copy_datagram_iovec(skb, hlen, iov, chunk))goto fault;} else {csum = csum_partial(skb->data, hlen, skb->csum);if (skb_copy_and_csum_datagram(skb, hlen, iov->iov_base,chunk, &csum))goto fault;if (csum_fold(csum))goto csum_error;if (unlikely(skb->ip_summed == CHECKSUM_COMPLETE))netdev_rx_csum_fault(skb->dev);iov->iov_len -= chunk;iov->iov_base += chunk;}return 0; csum_error:return -EINVAL; fault:return -EFAULT; } 311/** 312 * skb_copy_datagram_iovec - Copy a datagram to an iovec. 313 * @skb: buffer to copy 314 * @offset: offset in the buffer to start copying from 315 * @to: io vector to copy to 316 * @len: amount of data to copy from buffer to iovec 317 * 318 * Note: the iovec is modified during the copy. 319 */ 320int skb_copy_datagram_iovec(const struct sk_buff *skb, int offset, 321 struct iovec *to, int len) 322{ 323 int start = skb_headlen(skb); 324 int i, copy = start - offset; 325 struct sk_buff *frag_iter; 326 327 trace_skb_copy_datagram_iovec(skb, len); 328 329 /* Copy header. */ //復制udp頭 330 if (copy > 0) { 331 if (copy > len) 332 copy = len; 333 if (memcpy_toiovec(to, skb->data + offset, copy)) 334 goto fault; 335 if ((len -= copy) == 0) 336 return 0; 337 offset += copy; 338 } 339 340 /* Copy paged appendix. Hmm... why does this look so complicated? */ 341 for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { 342 int end; 343 const skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; 344 345 WARN_ON(start > offset + len); 346 347 end = start + skb_frag_size(frag); 348 if ((copy = end - offset) > 0) { 349 int err; 350 u8 *vaddr; 351 struct page *page = skb_frag_page(frag); 352 353 if (copy > len) 354 copy = len; 355 vaddr = kmap(page); 356 err = memcpy_toiovec(to, vaddr + frag->page_offset + 357 offset - start, copy); 358 kunmap(page); 359 if (err) 360 goto fault; 361 if (!(len -= copy)) 362 return 0; 363 offset += copy; 364 } 365 start = end; 366 } 367 368 skb_walk_frags(skb, frag_iter) { //復制分片 369 int end; 370 371 WARN_ON(start > offset + len); 372 373 end = start + frag_iter->len; 374 if ((copy = end - offset) > 0) { 375 if (copy > len) 376 copy = len; 377 if (skb_copy_datagram_iovec(frag_iter, 378 offset - start, 379 to, copy)) 380 goto fault; 381 if ((len -= copy) == 0) 382 return 0; 383 offset += copy; 384 } 385 start = end; 386 } 387 if (!len) 388 return 0; 389 390fault: 391 return -EFAULT; 392} 393EXPORT_SYMBOL(skb_copy_datagram_iovec);memcpy_toiovec會通過copy_to_user向用戶態的iov寫數據。memcpy_toiovec會去判斷iov->iov_len的剩余長度,并且只將數據放在剩余的空間里,那又如何越界? 但是這里不會去判斷iov的個數,這里可以造成越界寫用戶數據。
30/* 31 * Copy kernel to iovec. Returns -EFAULT on error. 32 * 33 * Note: this modifies the original iovec. 34 */ 35 36int memcpy_toiovec(struct iovec *iov, unsigned char *kdata, int len) 37{ 38 while (len > 0) { 39 if (iov->iov_len) { 40 int copy = min_t(unsigned int, iov->iov_len, len); 41 if (copy_to_user(iov->iov_base, kdata, copy)) 42 return -EFAULT; 43 kdata += copy; 44 len -= copy; 45 iov->iov_len -= copy; 46 iov->iov_base += copy; 47 } 48 iov++; 49 } 50 51 return 0; 52} 53EXPORT_SYMBOL(memcpy_toiovec);在/include/linux/kernel.h
746#define min_t(type, x, y) ({ \ 747 type __min1 = (x); \ 748 type __min2 = (y); \ 749 __min1 < __min2 ? __min1: __min2; }) 750引起
由另外一個patch引起?https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=89c22d8c3b27
diff --git a/net/core/datagram.c b/net/core/datagram.c index 4e9a3f6..4967262 100644 --- a/net/core/datagram.c +++ b/net/core/datagram.c @@ -657,7 +657,8 @@ __sum16 __skb_checksum_complete_head(struct sk_buff *skb, int len)!skb->csum_complete_sw)netdev_rx_csum_fault(skb->dev);} - skb->csum_valid = !sum; + if (!skb_shared(skb)) + skb->csum_valid = !sum;return sum;}EXPORT_SYMBOL(__skb_checksum_complete_head); @@ -677,11 +678,13 @@ __sum16 __skb_checksum_complete(struct sk_buff *skb)netdev_rx_csum_fault(skb->dev);}- /* Save full packet checksum */ - skb->csum = csum; - skb->ip_summed = CHECKSUM_COMPLETE; - skb->csum_complete_sw = 1; - skb->csum_valid = !sum; + if (!skb_shared(skb)) { + /* Save full packet checksum */ + skb->csum = csum; + skb->ip_summed = CHECKSUM_COMPLETE; + skb->csum_complete_sw = 1; + skb->csum_valid = !sum; + }return sum;}利用方式
遠程執行確實可以做到,可以在寫用戶態。通過root權限的應用,也可以提權,但是在內核態下不能做到代碼任意執行。
參考
CVE-2016-10229 Linux remote code execution flaw potentially exposes systems at risk of hack
UDP 收發
UDP 校驗和
struct sk_buff結構體詳解
msghdr 結構體
提到的另一個修改,增加了一個用戶層buf長度的參數。?https://patchwork.ozlabs.org/patch/530642/
原文地址: https://xweiwei.github.io/post/cve_2016_10229_analysis/總結
以上是生活随笔為你收集整理的CVE-2016-10229分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 区块链去中心化有那么有用吗?
- 下一篇: 带你全面了解比特黄金(bitcoin g