CVE-2023-31436 数组越界漏洞
CVE-2023-31436 数组越界漏洞
drawio: CVE-2023-31436.drawio
漏洞分析
在 qfq_change_class 里面如果用户态没有提供 TCA_QFQ_LMAX,就会取网卡的 mtu 作为 lmax 且不做校验,loopback 网卡的 mtu 可以被设置为 2^31-1
// qfq_change_class() in net/sched/sch_qfq.c
// ..
	if (tb[TCA_QFQ_LMAX]) {
		lmax = nla_get_u32(tb[TCA_QFQ_LMAX]);
		if (lmax < QFQ_MIN_LMAX || lmax > (1UL << QFQ_MTU_SHIFT)) {
			pr_notice("qfq: invalid max length %u\n", lmax);
			return -EINVAL;
		}
	} else
		lmax = psched_mtu(qdisc_dev(sch)); // [1]
// ..
		qfq_init_agg(q, new_agg, lmax, weight); // [2]
	}
// ..
	qfq_add_to_agg(q, new_agg, cl); // [3]
qfq_update_agg 中会使用 lmax ,代码通过 lmax 计算 i ,然后将 q->groups[i] 的地址保存到 agg->grp,之后就会通过 agg->grp 使用该对象。
// qfq_update_agg() in net/sched/sch_qfq.c
	agg->budgetmax = new_num_classes * agg->lmax;
	new_agg_weight = agg->class_weight * new_num_classes;
	agg->inv_w = ONE_FP/new_agg_weight;
	if (agg->grp == NULL) {
		int i = qfq_calc_index(agg->inv_w, agg->budgetmax,
				       q->min_slot_shift);
		agg->grp = &q->groups[i]; // [4]
	}
查看结构体定义可以知道 q->groups 的大小为 25,控制 i 大于 25 就会越界访问。
#define QFQ_MAX_INDEX		24
struct qfq_sched {
	struct tcf_proto __rcu *filter_list;
	struct tcf_block	*block;
	struct Qdisc_class_hash clhash;
	u64			oldV, V;	/* Precise virtual times. */
	struct qfq_aggregate	*in_serv_agg;   /* Aggregate being served. */
	u32			wsum;		/* weight sum */
	u32			iwsum;		/* inverse weight sum */
	unsigned long bitmaps[QFQ_MAX_STATE];	    /* Group bitmaps. */
	struct qfq_group groups[QFQ_MAX_INDEX + 1]; /* The groups. */
	u32 min_slot_shift;	/* Index of the group-0 bit in the bitmaps. */
	u32 max_agg_classes;		/* Max number of classes per aggr. */
	struct hlist_head nonfull_aggs; /* Aggs with room for more classes. */
};
diff --git a/net/sched/sch_qfq.c b/net/sched/sch_qfq.c
index cf5ebe43b3b4e..02098a02943eb 100644
--- a/net/sched/sch_qfq.c
+++ b/net/sched/sch_qfq.c
@@ -421,15 +421,16 @@ static int qfq_change_class(struct Qdisc *sch, u32 classid, u32 parentid,
 	} else
 		weight = 1;
-	if (tb[TCA_QFQ_LMAX]) {
+	if (tb[TCA_QFQ_LMAX])
 		lmax = nla_get_u32(tb[TCA_QFQ_LMAX]);
-		if (lmax < QFQ_MIN_LMAX || lmax > (1UL << QFQ_MTU_SHIFT)) {
-			pr_notice("qfq: invalid max length %u\n", lmax);
-			return -EINVAL;
-		}
-	} else
+	else
 		lmax = psched_mtu(qdisc_dev(sch));
+	if (lmax < QFQ_MIN_LMAX || lmax > (1UL << QFQ_MTU_SHIFT)) {
+		pr_notice("qfq: invalid max length %u\n", lmax);
+		return -EINVAL;
+	}
+
 	inv_w = ONE_FP / weight;
 	weight = ONE_FP / inv_w;
把 lmax 的校验移到了后面,确保从 mtu 取出的 lmax 也不能越界。
漏洞利用
通过漏洞我们可以让 agg->grp 指向 qfq_sched 对象后面的内存,然后利用 qfq_slot_insert 和 qfq_schedule_agg 可以实现越界写
static void qfq_slot_insert(struct qfq_group *grp, struct qfq_aggregate *agg,
			    u64 roundedS)
{
// ...
	hlist_add_head(&agg->next, &grp->slots[i]); // [1.1]
	__set_bit(slot, &grp->full_slots);          // [1.2]
}
static void qfq_schedule_agg(struct qfq_sched *q, struct qfq_aggregate *agg)
{
	struct qfq_group *grp = agg->grp;
// ...
	s = qfq_calc_state(q, grp);
	__set_bit(grp->index, &q->bitmaps[s]); // [2]
// ...
}
越界写原语解释:
- qfq_slot_insert:由于 grp 指向越界的内存, set_bit 的时候能越界写 bit,hlist_add_head 时可以越界写一个指针。
 - qfq_schedule_agg:由于 grp 指向越界的内存,通过堆风水可以控制 grp 对象内部的成员,从而控制 grp->index 在 set_bit 时实现对 q->bitmaps 的越界写。
 
由于 qfq_slot_insert 会写入指针且控制的 bit 有对齐的要求,所以作者采用了 qfq_schedule_agg 原语,这个原语比较简单,控制 grp->index 就可以实现越界写 bit.
struct qfq_sched {
	struct tcf_proto __rcu *filter_list;
	struct tcf_block	*block;
	struct Qdisc_class_hash clhash;
	u64			oldV, V;	/* Precise virtual times. */
	struct qfq_aggregate	*in_serv_agg;   /* Aggregate being served. */
	u32			wsum;		/* weight sum */
	u32			iwsum;		/* inverse weight sum */
	unsigned long bitmaps[QFQ_MAX_STATE];	    /* Group bitmaps. */
	struct qfq_group groups[QFQ_MAX_INDEX + 1]; /* The groups. */
	u32 min_slot_shift;	/* Index of the group-0 bit in the bitmaps. */
	u32 max_agg_classes;		/* Max number of classes per aggr. */
	struct hlist_head nonfull_aggs; /* Aggs with room for more classes. */
};
越界写 bit 的漏洞一般就是改 size 字段或者 指针域 构造 重叠对象,从而实现 UAF 或者类型混淆。
qfq_sched->nonfull_aggs 是一个很好的目标,原因是它 和 bitmaps 都在一个结构体内部,不需要额外的堆排布,该 list 在 qfq_find_agg 中被使用
static struct qfq_aggregate *qfq_find_agg(struct qfq_sched *q,
					  u32 lmax, u32 weight)
{
	struct qfq_aggregate *agg;
	hlist_for_each_entry(agg, &q->nonfull_aggs, nonfull_next)
		if (agg->lmax <span style="font-weight: bold;" class="mark"> lmax && agg->class_weight </span> weight)
			return agg;
	return NULL;
}
修改 nonfull_aggs 里面的指针,可以让 agg 指向其他区域,但是作者没有找到合适的布局对象控制 agg->lmax 和 agg->class_weight ,导致无法使用。
最终使用的策略是越界修改相邻 qdisc->filter_list ,让其指向其他的对象实现类型混淆:
PS: 控制 fake group 的 index,通过 qdisck #1 的 bitmaps 数组越界修改 qdisc #2 的 filter_list 指针。
dyn-kmalloc-8192 的堆喷对象为 qdisc_size_table .
// qdisc_get_stab() in net/sched/sch_api.c
	struct qdisc_size_table *stab;
// ..
	stab = kmalloc(struct_size(stab, data, tsize), GFP_KERNEL);
kmalloc-128 的堆喷对象为 xdp_umem
// in include/net/xdp_sock.h, size = 112 bytes
struct xdp_umem {
	void *addrs;
	u64 size;
	u32 headroom;
	u32 chunk_size;
	u32 chunks;
	u32 npgs;
	struct user_struct *user;
	refcount_t users;
	u8 flags;
	bool zc;
	struct page **pgs;
	int id;
	struct list_head xsk_dma_list;
	struct work_struct work;
};
xdp_umem 的 addrs 字段和 tcf_proto 对象的 next 指针重叠,addrs指向的区域是和用户态进程的共享内存,所以让 filter_list 指向 xdp_umem 后,用户态就能看控制 next 指针指向的 tcf_proto 结构体。
// in include/net/sch_generic.h
struct tcf_proto {
	/* Fast access part */
	struct tcf_proto __rcu	*next;
	void __rcu		*root;
	/* called under RCU BH lock*/
	int			(*classify)(struct sk_buff *,
					    const struct tcf_proto *,
					    struct tcf_result *);
	__be16			protocol;
	/* All the rest */
	u32			prio;
	void			*data;
	const struct tcf_proto_ops	*ops;
	struct tcf_chain	*chain;
	/* Lock protects tcf_proto shared state and can be used by unlocked
	 * classifiers to protect their private data.
	 */
	spinlock_t		lock;
	bool			deleting;
	refcount_t		refcnt;
	struct rcu_head		rcu;
	struct hlist_node	destroy_ht_node;
};
在 __tcf_classify 里面会遍历 tcf_proto->next ,因此可以控制 next 指针指向的 tp ,通过 classify 函数指针实现控制流劫持做 ROP.
// in net/sched/sch_qfq.c
static struct qfq_class *qfq_classify(struct sk_buff *skb, struct Qdisc *sch,
				      int *qerr)
{
	struct qfq_sched *q = qdisc_priv(sch);
	struct tcf_proto *fl;
// ..
	fl = rcu_dereference_bh(q->filter_list);
	result = tcf_classify(skb, NULL, fl, &res, false);
// ..
}
// in net/sched/cls_api.c
static inline int __tcf_classify(struct sk_buff *skb,
				 const struct tcf_proto *tp,
				 const struct tcf_proto *orig_tp,
				 struct tcf_result *res,
				 bool compat_mode,
				 u32 *last_executed_chain)
{
// ..
	for (; tp; tp = rcu_dereference_bh(tp->next)) {
		__be16 protocol = skb_protocol(skb, false);
		int err;
		if (tp->protocol != protocol &&
		    tp->protocol != htons(ETH_P_ALL))
			continue;
		err = tp->classify(skb, tp, res);
// ..
}
漏洞利用的地址泄露是使用侧信道的方式实现的
https://github.com/IAIK/prefetch/blob/master/cacheutils.h
总结与思考
漏洞产生的原因可能是开发者不了解设备 mtu 可以被用户态设置的非常大从而越界,做变体分析或者审计其他网络子系统代码时可以借鉴该漏洞的思路,设备的一些属性可能是被用户态控制的。
漏洞利用方面是通过堆布局控制了越界访问的对象,然后控制其中的 index 成员实现越界写,这一步控制数据的思路和 CVE-2020-12352 是类似的。
越界修改 bit 劫持指针也是相对比较常见的思路,不过之前都是利用重叠对象实现 msg_msg 的 UAF,本文的利用思路则是利用类型混淆,控制 next 指针指向的对象,这提示我们可以多搜索、多尝试新的对象,打开思路。
利用侧信道的方式进行信息泄露,最近几年确实也有一些实际的案例,这个思路在真实场景下可能是一个不错的信息泄露策略。
参考地址
CVE-2023-31436 数组越界漏洞的更多相关文章
- iOS如何彻底避免数组越界
		
我们先来看看有可能会出现的数组越界Crash的地方: ? 1 2 3 4 5 6 7 - (void)tableView:(UITableView *)tableView didSelectRowAt ...
 - Objective-c防止数组越界而崩溃(全局效果)
		
数组越界其实是很基本的问题,但是解决起来除了count的判断,还有每个调用的时候都要去判断一遍 对于不明确的数据总会有崩溃的风险 然而 每次调用都判断 那是太累了 so ..runtime&c ...
 - 数组越界保护与消息传递black机制
		
数组越界保护if(index.row <= [array count]) 发送消息[[NSNotificationCenter defaultCenter] postNotificati ...
 - 解决Android时时更新listview数组越界问题
		
时时更新数据一般出现在金融.股票行业对数据的准确性要求极高情况下使用. 先来看看下面一段代码, public class MainActivity extends Activity { private ...
 - Android 【问题汇总】列表数组越界的问题
		
遇到了一个诡异的问题,ListView发生数组越界(偶尔会),程序崩溃. 错误信息如下: W/dalvikvm( ): threadid=: thread exiting with uncaught ...
 - iOS 数组越界 Crash加工经验
		
我们先来看看有可能会出现的数组越界Crash的地方. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSInd ...
 - Java中的数组越界问题
		
Java中数组初始化和OC其实是一样的,分为动态初始化和静态初始化, 动态初始化:指定长度,由系统给出初始化值 静态初始化:给出初始化值,由系统给出长度 在我们使用数组时最容易出现的就是数组越界问题, ...
 - java.sql.SQLException之数组越界
		
java.sql.SQLException之数组越界 1.具体错误如下: (1)java.sql.SQLException:Parameter index out of range(0<1) ( ...
 - 一次数组越界的bug经历
		
数组和指针都是C里面的好东西,但是一旦使用不当,真的会让人抓狂. 下面是写程序时遇到的一次数组越界的经历,感觉对以后写程序有点启发,所以记录下来. 起因: 我想用OLED动态显示一组浮点数,而且浮点数 ...
 - .NET 下 模拟数组越界
		
前面一篇文章提到过 数组越界行为,虽然编译器为我们做了大量的检查工作让我们避免这些错误. 但是我觉得还是有必要模拟一下数组越界,感受一下这个错误. 那么对于.NET来说我们怎么来模拟数组越界呢? 一. ...
 
随机推荐
- 浅谈舞蹈链(DLX)
			
名字: \(DL\),\(Dancing\space Link\),舞蹈链,是由\(Donald\space Knuth\)提出的数据结构,用来优化 \(X\) 算法,所以叫\(DLX\) \(X\) ...
 - Spring —— bean生命周期
			
bean生命周期 生命周期:从创建到消亡的完整过程 bean生命周期:bean从创建到销毁的整体过程 bean生命周期控制:在bean创建后到销毁前做一些事情 方式一:配置控制生命周期 <b ...
 - [摘录] WebView2 与 JS 交互
			
https://docs.microsoft.com/zh-cn/microsoft-edge/webview2/gettingstarted/win32 步骤 5-脚本 托管应用还可以将 JavaS ...
 - 【问题解决】remote: parse error: Invalid numeric literal at line 1, column 20,解决思路
			
问题现象 某同事出现过同样的推送到git仓库报错的问题,报错信息详情如下: Delta compresion using up to 20 threads Compressing objects: 1 ...
 - python实战-编写请求方法重试(用途:请求重试、也可用于其他场景)、日志、执行耗时、手机号与邮箱校验装饰器
			
更新日志 2023.2.9 增加重试装饰器 防止函数原信息被改变使用:@functools.wraps(func)装饰执行函数 # _*_ coding: UTF-8 _*_ "" ...
 - composer 基础操作
			
一.composer入门 1.每次安装新的包文件,会更新/vendor/autoload.php文件 2.composer.lock与composer.json的关系 文件composer.lock会 ...
 - 2024 xp_CAPTCHA(瞎跑-白嫖版) 4.3最新版安装使用教程
			
前言 xp_CAPTCHA(瞎跑-白嫖版)是一个免费的burpsuite插件,具有自动化图形验证码识别的功能.在安装的过程中,我发现网上的教程基本都为其较早的版本,已经不具备参考价值.因而我写下本篇博 ...
 - 两台笔记本电脑实现同一wifi下虚拟主机网络实现互通
			
一台win笔记本 (安装vmware) 一台macbookpro 本人考虑到M1的macbook,无法安装vmware,这让我这个linux运维人员很是dan疼,没办法只能在自己的win笔记本上安装v ...
 - 共享存储ISCSI
			
建立共享iscsi磁盘组 资源环境 服务端:192.168.2.131 客户端:192.168.2.[110,169] 服务端磁盘: [root@centos ~]# lsblk NAME MAJ:M ...
 - manim边做边学--圆锥
			
Cone是Manim中专门用于创建和操控锥形几何对象的类. Cone允许用户定义锥体的底面半径.高度.颜色.不透明度等属性,并提供了一系列方法来操控这个锥体,如移动.缩放.旋转等. 通过这些属性和方法 ...