影響版本:Linux 5.8~5.17-rc7 5.17-rc8已修補(bǔ) / 5.16.15已修補(bǔ)挤安。評(píng)分只有 7.1 分。
測(cè)試版本:Linux-5.16.14(利用失斖裣荨)改用v5.11.22 exploit及測(cè)試環(huán)境下載地址—https://github.com/bsauce/kernel-exploit-factory
編譯選項(xiàng): CONFIG_WATCH_QUEUE
在編譯時(shí)將.config
中的CONFIG_E1000
和CONFIG_E1000E
,變更為=y官研。參考
$ wget https://mirrors.tuna.tsinghua.edu.cn/kernel/v5.x/linux-5.16.14.tar.xz
$ tar -xvf linux-5.16.20.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。
漏洞描述:watch_queue
事件通知子系統(tǒng)存在堆溢出阀参,漏洞函數(shù)是watch_queue_set_filter()肝集。內(nèi)核會(huì)對(duì)用戶傳入的 watch_notification_type_filter 類型的 filter 進(jìn)行兩次有效性檢查,第1次檢查是為了確定分配的內(nèi)存大小蛛壳,第2次是為了將用戶filter 存入該內(nèi)存杏瞻。但是兩次檢查不一致,導(dǎo)致分配空間過(guò)小衙荐,可溢出存入更多的 filter捞挥。可以利用第2次溢出忧吟,對(duì)相鄰的堆塊特定bit位置1砌函,接下來(lái)的利用方法和 CVE-2021-22555 一樣。
補(bǔ)丁:patch 修改了判斷條件溜族,兩處都判斷type是否大于等于2讹俊。
@@ -320,7 +319,7 @@ long watch_queue_set_filter(struct pipe_inode_info *pipe,
tf[i].info_mask & WATCH_INFO_LENGTH)
goto err_filter;
/* Ignore any unknown types */
- if (tf[i].type >= sizeof(wfilter->type_filter) * 8)
+ if (tf[i].type >= WATCH_TYPE__NR)
continue;
nr_filter++;
}
@@ -336,7 +335,7 @@ long watch_queue_set_filter(struct pipe_inode_info *pipe,
q = wfilter->filters;
for (i = 0; i < filter.nr_filters; i++) {
- if (tf[i].type >= sizeof(wfilter->type_filter) * BITS_PER_LONG)
+ if (tf[i].type >= WATCH_TYPE__NR)
continue;
q->type = tf[i].type;
保護(hù)機(jī)制:KASLR / SMEP / SMAP / KPTI
利用總結(jié):用的方法和 CVE-2021-22555 的方法一樣。
1. 背景知識(shí)
內(nèi)核通知機(jī)制:參見 watch_queue官方說(shuō)明煌抒,內(nèi)核的通用通知機(jī)制是基于pipe的仍劈,可以將內(nèi)核的通知消息拼接到用戶打開的管道中(編譯時(shí)開啟CONFIG_WATCH_QUEUE
)。采用特殊mode打開pipe寡壮,即可啟用該機(jī)制贩疙;內(nèi)核生成的消息會(huì)被保存到 pipe_buffer
結(jié)構(gòu)的 ring buffer
中;可調(diào)用 read()
來(lái)讀取該消息况既。pipe的所有者應(yīng)該告訴內(nèi)核这溅,哪些資源需要通過(guò)該管道進(jìn)行觀察,只有連接到該管道上的資源才會(huì)往里邊插入消息棒仍,需要注意的是一個(gè)資源可能會(huì)與多個(gè)管道綁定并同時(shí)將消息插入所有管道悲靴。
用戶管理 watch queue
:watch queue
是應(yīng)用程序分配的一段緩沖區(qū),用來(lái)記錄通知降狠,其實(shí)現(xiàn)代碼都在pipe驅(qū)動(dòng)中对竣,用戶可以通過(guò)兩個(gè)API 引用和丟棄引用 pipe文件描述符fd中的緩沖區(qū)對(duì)應(yīng)的 watch queue
庇楞。分別是 struct watch_queue *get_watch_queue(int fd)
/ void put_watch_queue(struct watch_queue *wqueue)
。
event filter:創(chuàng)建好 watch queue
之后否纬,用戶可以創(chuàng)建 filter 來(lái)限制接收的事件吕晌。用戶可傳入 watch_notification_filter -> watch_notification_type_filter 結(jié)構(gòu),這樣在內(nèi)核中就會(huì)創(chuàng)建相應(yīng)的filter临燃,漏洞就出在創(chuàng)建 filter 過(guò)程的代碼中睛驳。結(jié)構(gòu)中的成員含義可見以下的漏洞分析部分。
2. 漏洞分析
用戶調(diào)用 ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter)
來(lái)設(shè)置filter時(shí)會(huì)觸發(fā)漏洞膜廊。
調(diào)用關(guān)系:ioctl -> vfs_ioctl() -> pipe_ioctl() -> watch_queue_set_filter() -> __set_bit()
注意:vfs_ioctl() 中會(huì)調(diào)用 filp->f_op->unlocked_ioctl()
乏沸,該函數(shù)表的創(chuàng)建流程是 do_pipe2()
-> __do_pipe_flags()
-> create_pipe_files()
-> alloc_file_pseudo()
-> alloc_file()
。alloc_file()
分配一個(gè) file 結(jié)構(gòu)體并將其函數(shù)表設(shè)為上層調(diào)用傳入的函數(shù)表爪瓜,而在 create_pipe_files()
中傳入的函數(shù)表為 pipefifo_fops蹬跃。
long watch_queue_set_filter(struct pipe_inode_info *pipe,
struct watch_notification_filter __user *_filter)
{
struct watch_notification_type_filter *tf;
struct watch_notification_filter filter;
struct watch_type_filter *q;
struct watch_filter *wfilter;
struct watch_queue *wqueue = pipe->watch_queue;
int ret, nr_filter = 0, i;
...
if (copy_from_user(&filter, _filter, sizeof(filter)) != 0) // [1] 拷貝用戶傳入的 watch_notification_filter 結(jié)構(gòu)
return -EFAULT;
if (filter.nr_filters == 0 ||
filter.nr_filters > 16 ||
filter.__reserved != 0)
return -EINVAL;
tf = memdup_user(_filter->filters, filter.nr_filters * sizeof(*tf)); // [2] 分配臨時(shí)空間并拷貝用戶傳入的 filter
...
for (i = 0; i < filter.nr_filters; i++) {
if ((tf[i].info_filter & ~tf[i].info_mask) ||
tf[i].info_mask & WATCH_INFO_LENGTH)
goto err_filter;
/* Ignore any unknown types */
if (tf[i].type >= sizeof(wfilter->type_filter) * 8) // [3] 只計(jì)入 type 值小于 0x10*8 的數(shù)量, 后續(xù)根據(jù) nr_filter 分配空間
continue;
nr_filter++;
}
...
wfilter = kzalloc(struct_size(wfilter, filters, nr_filter), GFP_KERNEL); // [4] 根據(jù) nr_filter 值來(lái)分配空間,存儲(chǔ)filter
...
wfilter->nr_filters = nr_filter;
q = wfilter->filters;
for (i = 0; i < filter.nr_filters; i++) { // [5] 填充 wfilter->filters[]
if (tf[i].type >= sizeof(wfilter->type_filter) * BITS_PER_LONG) // [6] 漏洞點(diǎn), 這里只要 type < 0x10*64 (0x400) 就會(huì)存入, 之前判斷時(shí)是 0x80
continue;
q->type = tf[i].type; // [7] 溢出點(diǎn)1
q->info_filter = tf[i].info_filter;
q->info_mask = tf[i].info_mask;
q->subtype_filter[0] = tf[i].subtype_filter[0];
__set_bit(q->type, wfilter->type_filter); // [8] 溢出點(diǎn)2, 將wfilter->type_filter偏移q->type的bit位置為1, 可以溢出篡改指定bit
q++;
}
...
}
#define BITS_PER_LONG 64
#define BIT_MASK(nr) (UL(1) << ((nr) % BITS_PER_LONG))
#define BIT_WORD(nr) ((nr) / BITS_PER_LONG)
static inline void __set_bit(int nr, volatile unsigned long *addr)
{
unsigned long mask = BIT_MASK(nr); // 1 左移 (nr % 64)
unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr); // nr 除以 64bit, 但p是指向8字節(jié), 所以將修改偏移 (nr/64*8 = n/8) 處的字節(jié)
*p |= mask;
}
漏洞:內(nèi)核會(huì)對(duì)用戶傳入的 watch_notification_type_filter 類型的 filter 進(jìn)行兩次有效性檢查铆铆,第1次檢查是為了確定分配的內(nèi)存大械骸(見[3]
),第2次是為了將用戶filter 存入該內(nèi)存(見[6]
)薄货。問(wèn)題在于兩次檢查不一致翁都,第1次是計(jì)算type小于0x80的個(gè)數(shù),第2次卻是將type小于0x400的filter存入該內(nèi)存谅猾。所以當(dāng)type位于 0x80~0x400
之間時(shí)柄慰,實(shí)際存入的filter個(gè)數(shù)會(huì)大于分配的內(nèi)存,導(dǎo)致 [7]
和 [8]
都會(huì)溢出税娜。漏洞利用時(shí)采用的是第2處溢出坐搔,越界將指定的bit置1(將 wfilter->type_filter
偏移 q->type
的bit位置為1,而 wfilter->type_filter
位于 watch_filter
結(jié)構(gòu)的開頭敬矩,所以只要將 q->type
設(shè)置為固定的值薯蝎,就能將相鄰塊的固定偏移位 置為1)。
結(jié)構(gòu)關(guān)系:
- 用戶參數(shù) —— watch_notification_filter -> watch_notification_type_filter
- 內(nèi)核存儲(chǔ) —— watch_filter -> watch_type_filter
// (1) 用戶參數(shù)結(jié)構(gòu)體
struct watch_notification_filter {
__u32 nr_filters; /* Number of filters */
__u32 __reserved; /* Must be 0 */
struct watch_notification_type_filter filters[]; // <---------
};
struct watch_notification_type_filter {
__u32 type; // 要過(guò)濾的事件類型, eg, WATCH_TYPE_KEY_NOTIFY
__u32 info_filter; /* Filter on watch_notification::info */
__u32 info_mask; /* Mask of relevant bits in info_filter */
__u32 subtype_filter[8]; /* Bitmask of subtypes to filter on */
};
// (2) 內(nèi)核結(jié)構(gòu)體
struct watch_filter {
union {
struct rcu_head rcu;
unsigned long type_filter[2]; /* Bitmask of accepted types */
};
u32 nr_filters; /* Number of filters */
struct watch_type_filter filters[];
};
struct watch_type_filter { // size: 0x10
enum watch_notification_type type;
__u32 subtype_filter[1]; /* Bitmask of subtypes to filter on */
__u32 info_filter; /* Filter on watch_notification::info */
__u32 info_mask; /* Mask of relevant bits in info_filter */
};
3. 漏洞利用
漏洞利用:其實(shí)有兩處溢出谤绳,但作者用到了第2處溢出。作者傳入4個(gè)filter袒哥,其中3個(gè)有效缩筛,則在 [4]
處會(huì)申請(qǐng) 0x18+0x30
的內(nèi)存,實(shí)際申請(qǐng)到 kmalloc-96堡称。當(dāng)type值為 0x30a
時(shí)(96*8+0xa
)瞎抛,會(huì)將相鄰 kmalloc-96 的第10bit 置為1,也即將 0x0000
修改為 0x0400
却紧。
好處:一是只需要溢出1次桐臊,也即堆噴布置1次胎撤,提高利用成功率;二是可以直接采用CVE-2021-22555 的利用方法断凶,篡改 msg_msg->m_list.next
伤提。
利用過(guò)程:
- (1)堆布局:堆噴4096個(gè)
msg_msg
,主消息和輔助消息kmalloc-96 <-> kmalloc-1024
认烁; - (2)觸發(fā)OOB
- 釋放第0 / 1024 / 2048 / 3072 個(gè)主消息肿男;
- 觸發(fā)OOB溢出,漏洞對(duì)象位于kmalloc-96却嗡,可能將某個(gè)
msg_msg->m_list.next
的最低兩字節(jié)從 0x0000 修改為 0x0400舶沛; - 找到
msg_msg->m_list.next
被修改的msg_msg
,下標(biāo)記為victim_qid
窗价,指向的msg_msg
下標(biāo)記為real_qid
如庭;
- (3)構(gòu)造UAF:釋放下標(biāo)為
real_qid
的輔助消息B2(將下標(biāo)victim_qid
的輔助消息記為A2,下標(biāo)real_qid
的輔助消息記為B2)撼港; - (4)泄露UAF消息B2的地址
- 堆噴 16*128 個(gè)
sk_buff
占據(jù)剛才釋放的B2坪它,偽造A2的msg_msg->m_ts = 0xfd0
; - 利用A2進(jìn)行OOB read餐胀,泄露相鄰消息的
msg_msg->m_list.prev
(記為C1哟楷,相鄰輔助消息對(duì)應(yīng)的主消息地址); - 釋放
sk_buff
后再次堆噴sk_buff
否灾,偽造A2的msg_msg->m_ts = 0x1fc8
/msg_msg->next = C1
卖擅; - 利用A2泄露 C1 處的
msg_msg->m_list.next
(記為C2,相鄰輔助消息的地址)墨技,C2-0x400
即為B2的地址惩阶;
- 堆噴 16*128 個(gè)
- (5)泄露內(nèi)核基址
- 釋放
sk_buff
后再次堆噴sk_buff
,偽造A2-
msg_msg->m_list.next = msg_msg->m_list.prev = B+0x800
扣汪; -
msg_msg->type = 0x1337
断楷; -
msg_msg->m_ts = 0xfd0
; -
msg_msg->next = msg_msg->security = 0
崭别;
-
- 釋放下標(biāo)
victim_qid
的輔助消息A2冬筒; - 堆噴256個(gè)
pipe_buffer
占據(jù)A2; - 讀取
sk_buff
泄露pipe_buffer->ops
即可泄露內(nèi)核基址茅主;
- 釋放
- (6)劫持控制流
- 堆噴
sk_buff
舞痰,篡改pipe_buffer->ops
指向A2+0x290
; - 并偽造
release
指針為 pivot gadget诀姚,剩下的ROP chain放在pipe_buffer
上响牛,完成提權(quán)。
- 堆噴
問(wèn)題:在內(nèi)核版本 5.16.14 上始終沒(méi)有辦法使漏洞對(duì)象和 kmalloc-96
大小的 msg_msg
相鄰,跟之前調(diào)試 CVE-2022-0185 遇到的問(wèn)題一樣(希望之后能弄明白呀打,難道是account標(biāo)志導(dǎo)致矢赁?)。無(wú)奈贬丛,只能用 5.11.22 版本上提權(quán)了撩银。
參考
【kernel exploit】CVE-2021-22555 2字節(jié)堆溢出寫0漏洞提權(quán)分析
【CVE.0x08】CVE-2022-0995 漏洞復(fù)現(xiàn)及簡(jiǎn)要分析