Linux內核AF_PACKET原生套接字漏洞(CVE-2020-14386)分析

發布時間 2020-09-22

漏洞背景


近日,Openwall社區上公開了一個Linux內核AF_PACKET原生套接字內存破壞漏洞。根據細節描述,該漏洞出現在net/packet/af_packet.c中,由整數溢出導致越界寫,可以通過它進行權限提升。該漏洞危害評級為高,編號為CVE-2020-14386。


受影響產品和緩解措施


1、受影響產品


該漏洞影響Linux發行版高于4.6的內核版本,包括:

  • Ubuntu Bionic (18.04) and newer

  • Debian 9

  • Debian 10

  • CentOS 8/RHEL 8

2、緩解措施


(1)修補系統

上游內核補丁如下:

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=acf69c946233259ab4d64f8869d4037a198c7f06


(2)關閉CAP_NET_RAW功能

針對RHEL8,具體關閉步驟如下:

# echo"user.max_user_namespaces=0" > /etc/sysctl.d/userns.conf

# sysctl -p/etc/sysctl.d/userns.conf


(3)針對一些受影響的容器產品,同樣采取關閉CAP_NET_RAW功能進行緩解

Kubernetes Pod安全策略:配置Pod安全策略以刪除運行容器中的CAP_NET_RAW功能,參考鏈接:https://cloud.google.com/kubernetes-engine/docs/security-bulletins。


相關概念


1、AF_PACKET套接字


網絡協議棧中,原始套接字是一個特殊的套接字類型,從實現上可以分為兩類,一類為鏈路層原始套接字;另一類為網絡層原始套接字。鏈路層原始套接字可直接用于接收和發送鏈路層的MAC幀,在發送時需要調用者自行構造和封裝MAC首部。鏈路層原始套接字調用socket()函數創建。第一個參數指定地址簇類型為AF_PACKET,第二個參數套接字類型為SOCK_RAW或SOCK_DGRAM,當類型指定為SOCK_RAW時,套接字接收和發送的數據都是從MAC首部開始的。在發送時需要由調用者從MAC首部開始構造和封裝報文數據。


2、PACKET_MMAP


僅依靠AF_PACKET過濾數據包是非常低效的,內核又提供了PACKET_MMAP支持。PACKET_MMAP在內核空間中分配一塊環形內核緩沖區,用戶空間通過mmap將該內核緩沖區映射出來。收到的數據包拷貝到環形內核緩沖區中,用戶層可以直接操作數據,通過內核空間和用戶空間共享的緩沖區起到減少數據拷貝的作用,提高處理效率。


PACKET_MMAP實現過程


通過setsockopt()函數設置環形緩沖區,option參數設置為PACKET_RX_RING或PACKET_TX_RING。為了方便內核與用戶層管理和交互環形緩沖區中的數據幀,內核定義了TPACKET_HEADER結構體,該結構體存儲著一些元信息如套接字地址信息、時間戳以及環形緩沖區管理信息等。如果通過setsockopt()函數設置了PACKET_VNET_HDR選項,還需添加一個virtio_net_hdr結構體。一個數據幀包含兩個部分,第一部分為TPACKET_HEADER,第二部分為Data,而且要保證頁面對齊,如下圖所示:

目前TPACKET_HEADER存在三個版本,每個版本長度略有不同。對于v1和v2,收發環形緩沖區用tpacket_req結構體管理,該結構體包含四個數據域:分別為內存塊的大小和數量、每個數據幀的大小和數據幀總數。如下圖所示:



捕獲的frame被劃分為多個block,每個block是一塊物理上連續的內存區域,有tp_block_size/tp_frame_size個frame,block的總數是tp_block_nr。例如,tp_block_size = 4096,tp_frame_size = 2048,tp_block_nr = 4,tp_frame_nr = 8。得到的緩沖區結構如下圖所示:



每個frame必須放在一個block中,每個block保存整數個frame,也就是說一個frame不能跨越兩個block。在用戶層映射環形緩沖區可以直接使用mmap()函數。雖然環形緩沖區在內核中是由多個block組成的,但是映射后它們在用戶空間中是連續的。


漏洞分析


該漏洞具體出現在tpacket_rcv()函數中,該函數是基于PACKET_MMAP的數據包接收函數。具體功能實現如下代碼所示:



行2226到行2228,如果sk_type為SOCK_DGRAM,表示不需要自行構造MAC首部,由內核填充,則macoff等于netoff,大小為TPACKET_ALIGN(tp_hdr_len)+ 16 + tp_reserve。如果sk_type為SOCK_RAW,則進入行2230,表示需要自行構造MAC首部。行2231到行2233,首先計算netoff,大小為TPACKET_ALIGN(tp_hdrlen +(maclen < 16 ?16 : maclen)) + tp_reserve。行2234到行2237,如果設置了PACKET_VNET_HDR選項,還需加上一個virtio_net_hdr結構體的大小,然后設置do_vnet為真。行2238,計算macoff


由于macoff、netoff以及maclen被定義為unsigned short類型,最大值為0xffff。而tp_reserve被定義為unsigned int類型,最大值為0xffffffff,而且大小可以通過setsockopt()函數進行設置,如下代碼所示:



因此,在計算netoff時,可以通過控制tp_reserve造成整數溢出,進而計算出錯誤的macoff。當執行到如下代碼時:



行2287,調用virtio_net_hdr_from_skb()函數從sk_buff中拷貝數據,該函數第二個參數為h.raw + macoff – sizeof(struct virtio_net_hdr),h.raw為tpacket_rcv_uhdr類型的指針,指向環形緩沖區的frame,由于macoff是可控的,可以讓maoff小于sizeof(struct virtio_net_hdr),導致向前越界寫,最多可寫入sizeof(struct virtio_net_hdr)個字節。根據提供的PoC,調試代碼如下圖所示:



rdx中存放著TPACKET_ALIGN(tp_hdrlen+(maclen < 16 ? 16 : maclen)),大小為0x50。rbp+0x4e4處存放著po->tp_reserve,大小為0x0000ffb4。相加后,整數上溢后,rdx為0x0004。當執行到越界訪問時,具體如下:



R9存放著h.raw指針,rdx存放著macoff,virtio_net_hdr結構體大小為0xa。如下圖所示:



發生內存訪問錯誤,造成系統崩潰。


參考鏈接:


[1] https://blog.csdn.net/sinat_20184565/article/details/82788387

[2] https://www.openwall.com/lists/oss-security/2020/09/03/3

[3] https://elixir.bootlin.com/linux/v5.6/source/Documentation/networking/packet_mmap.txt

[4] https://sysdig.com/blog/cve-2020-14386-falco/

[5] https://bugzilla.redhat.com/show_bug.cgi?id=1875699#c9