环境搭建
漏洞影响范围:~ linux kernel 5.17-rc7
选择使用Linux-5.10
看源码确认配置:
CONFIG_CONFIGFS_FS=y |
漏洞成因分析
https://elixir.bootlin.com/linux/v5.10/source/fs/pipe.c
首先对一个管道进行ioctl,cmd为IOC_WATCH_QUEUE_SET_FILTER:
进入到watch_queue_set_filter函数https://elixir.bootlin.com/linux/v5.10/source/kernel/watch_queue.c#L286:
tf直接从memdup_user而来:
然后是wfilter:
所以应该就是分配了sizeof(filter) * nr_filter字节的空间;
set_bit的作用是将指定位设置为1:
watch_filter结构如下,漏洞是其watch_type_filter成员发生了1bit溢出写:
分配点和越界写的位置如下:https://elixir.bootlin.com/linux/v5.10/source/kernel/watch_queue.c#L331
这里搜一下struct_size这个宏的含义:https://zhuanlan.zhihu.com/p/484786193
其含义应该是计算struct watch_filter中除了filters[]数组的size,然后加上对应count数量个filters项的大小,计算这个变长结构的长度的;🤔
漏洞成因总结如下:
漏洞利用
- 首先创建一系列msg队列,在每一个队列上先后发送0x60、0x400的消息:
释放其中一个0x60的msg_msg,然后用watch_filter占位;
之后利用watch_filter的oob-write,越界写一个bit;
- 利用msgrcv,将双指针指向的0x400的msg_msg释放掉,造成msg_msg的UAF;
- 用sk_buff占位这个uaf的msg_msg,利用sk_buff占位msg_msg,并伪造其结构,造成一个可以越界读的msg_msg,用来泄露内核“堆”地址;
6. 利用下边的msg_msg读,导致msg_msg再次被释放;
7. 然后用pipe_buffer占位;
8. 之后读sk_buff泄露pipe_buffer中的内容,并导致uaf的pipe_buffer;
9. 写sk_buff,再次分配pipe_buffer占用的空间,覆盖写pipe_buffer;
10. close掉pipe_buffer提权;
原语构造
首先创建一个pipe;vulpipe[2](要用pipe2+flags==0x80,原因见后面分析)
pipe要有wqueue,否则会直接error:
ioctl(vulpipe[1], 0x5761, &filter);
IOC_WATCH_QUEUE_SET_FILTER的值是0x5761,第三个参数是filter;
构造要求:
计算的nr_filter直接决定wfilter中filters数组的长度:
然后要求set_bit的迭代次数要大:
也就首先满足:(否则会直接error)
(tf[i].info_filter & ~tf[i].info_mask) == 0 && |
然后这个:(continue)(希望continue的次数比下面多)
tf[i].type >= sizeof(wfilter->type_filter) * 8 |
最后这个:(continue)(希望continue的次数比上面少)
tf[i].type >= sizeof(wfilter->type_filter) * BITS_PER_LONG // BITS_PER_LONG == 64 |
满足2的次数要比满足3多,即2continue的次数多,2迭代的次数就少,然后分配的空间就会比set_bit迭代的少;
所以就是在sizeof(wfilter->type_filter) * 8
和sizeof(wfilter->type_filter) * 64
之间;
下面看一下tf的具体结构:
条件一绕过
WATCH_INFO_LENGTH:
因此,tf[i].info_filter和tf[i].info_mask全设置成0即可;
条件二和条件三
type_filter的长度应该是0x10🤔
因此这个type就应该在:0x80和0x400之间就可以造成误差;
漏洞结构体的长度
笔者此时做实验的nr_filters是0,因此结构体的原始长度是0x18;
每一个filter的长度应该是0x10:
因此我们的结构是这个样子的:(注意0x18其实是0x20对齐了的)
越界写什么
似乎每次都是从wfilter->type_filter开始写,最长可以写0x400bit,也就是0x80的长度;
这里是两个漏洞🤔🤔:
- 前面分析的条件不匹配,似乎就是个简单的越界读,通过q,因为q的迭代是新的条件,和原来分配的条件不一致;
- 而写的这个越界,其实是这个bit范围直接出了问题。。。
总结
因此要想成功利用本漏洞,需要:
pipe2(vulpipe, 0x80)
创建带有wqueue的pipe;- 每一个filter.filters[i],
filter.filters[i].info_filter = 0;
以及filter.filters[i].info_mask = 0;
保证不会error,满足条件一; - 能够过
nrfilters++
的,满足filter.filters[i].type == [0...0x80)
; - 构造越界的,满足
filter.filters[i].type == [0x80...0x400)
ioctl(vulpipe[1], 0x5761, &filter);
触发漏洞;
wqueue的创建
https://elixir.bootlin.com/linux/v5.10/source/kernel/watch_queue.c#L648
https://elixir.bootlin.com/linux/v5.10/source/fs/pipe.c#L903
这个标志位是0x80:
回溯flags:
所以要用pipe2+flags才能行!😮
实操
通过合法的filter的数量来操控所分配空间的大小;
每一个type理论上都有越界写的可能;
(这里要小心tf可能带来的分配噪声):
其实是可以搞到不同cache的;