CVE-2022-0995[half]


环境搭建

漏洞影响范围:~ linux kernel 5.17-rc7

选择使用Linux-5.10

看源码确认配置:

CONFIG_CONFIGFS_FS=y
CONFIG_SECURITYFS=y
CONFIG_NET_SCHED=y
CONFIG_DEBUG_INFO=y
CONFIG_USER_NS=y #支持新的namespace
CONFIG_USERFAULTFD=y #支持userfaultfd
CONFIG_WATCH_QUEUE=y

CONFIG_SLAB_FREELIST_RANDOM=y
CONFIG_CHECKPOINT_RESTORE #msg_msg copy生效/不管用

漏洞成因分析

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项的大小,计算这个变长结构的长度的;🤔

漏洞成因总结如下:

漏洞利用

  1. 首先创建一系列msg队列,在每一个队列上先后发送0x60、0x400的消息:

  1. 释放其中一个0x60的msg_msg,然后用watch_filter占位;

  2. 之后利用watch_filter的oob-write,越界写一个bit;

  1. 利用msgrcv,将双指针指向的0x400的msg_msg释放掉,造成msg_msg的UAF;

  1. 用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提权;

原语构造

  1. 首先创建一个pipe;vulpipe[2](要用pipe2+flags==0x80,原因见后面分析)

  2. pipe要有wqueue,否则会直接error:

  1. 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 &&
tf[i].info_mask & WATCH_INFO_LENGTH == 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的长度;

这里是两个漏洞🤔🤔:

  1. 前面分析的条件不匹配,似乎就是个简单的越界读,通过q,因为q的迭代是新的条件,和原来分配的条件不一致;
  2. 而写的这个越界,其实是这个bit范围直接出了问题。。。

总结

因此要想成功利用本漏洞,需要:

  1. pipe2(vulpipe, 0x80)创建带有wqueue的pipe;
  2. 每一个filter.filters[i], filter.filters[i].info_filter = 0;以及filter.filters[i].info_mask = 0;保证不会error,满足条件一;
  3. 能够过nrfilters++的,满足filter.filters[i].type == [0...0x80);
  4. 构造越界的,满足filter.filters[i].type == [0x80...0x400)
  5. 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的;

步骤一:喷射msg_msg

参考

https://arttnba3.cn/2022/04/06/CVE-0X08-CVE-2022-0995/


文章作者: q1ming
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 q1ming !
  目录