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. */
};

补丁分析:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=3037933448f60f9acb705997eae62013ecb81e0d

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 数组越界漏洞的更多相关文章

  1. iOS如何彻底避免数组越界

    我们先来看看有可能会出现的数组越界Crash的地方: ? 1 2 3 4 5 6 7 - (void)tableView:(UITableView *)tableView didSelectRowAt ...

  2. Objective-c防止数组越界而崩溃(全局效果)

    数组越界其实是很基本的问题,但是解决起来除了count的判断,还有每个调用的时候都要去判断一遍 对于不明确的数据总会有崩溃的风险 然而 每次调用都判断 那是太累了 so ..runtime&c ...

  3. 数组越界保护与消息传递black机制

    数组越界保护if(index.row <= [array count]) 发送消息[[NSNotificationCenter defaultCenter]     postNotificati ...

  4. 解决Android时时更新listview数组越界问题

    时时更新数据一般出现在金融.股票行业对数据的准确性要求极高情况下使用. 先来看看下面一段代码, public class MainActivity extends Activity { private ...

  5. Android 【问题汇总】列表数组越界的问题

    遇到了一个诡异的问题,ListView发生数组越界(偶尔会),程序崩溃. 错误信息如下: W/dalvikvm( ): threadid=: thread exiting with uncaught ...

  6. iOS 数组越界 Crash加工经验

    我们先来看看有可能会出现的数组越界Crash的地方. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSInd ...

  7. Java中的数组越界问题

    Java中数组初始化和OC其实是一样的,分为动态初始化和静态初始化, 动态初始化:指定长度,由系统给出初始化值 静态初始化:给出初始化值,由系统给出长度 在我们使用数组时最容易出现的就是数组越界问题, ...

  8. java.sql.SQLException之数组越界

    java.sql.SQLException之数组越界 1.具体错误如下: (1)java.sql.SQLException:Parameter index out of range(0<1) ( ...

  9. 一次数组越界的bug经历

    数组和指针都是C里面的好东西,但是一旦使用不当,真的会让人抓狂. 下面是写程序时遇到的一次数组越界的经历,感觉对以后写程序有点启发,所以记录下来. 起因: 我想用OLED动态显示一组浮点数,而且浮点数 ...

  10. .NET 下 模拟数组越界

    前面一篇文章提到过 数组越界行为,虽然编译器为我们做了大量的检查工作让我们避免这些错误. 但是我觉得还是有必要模拟一下数组越界,感受一下这个错误. 那么对于.NET来说我们怎么来模拟数组越界呢? 一. ...

随机推荐

  1. 应聘软件测试 HR 会问到哪些问题?收藏这一篇就够了!

    1.你还有收到其他offer吗? 其实hr问你offer情况,是对你感兴趣,想要进一步了解你,看下你的市场竞争力. 但注意不要太坦诚的说:我还没有offer或者收到两个offer还想对比对比:也不要撒 ...

  2. Spring事务传播机制(最全示例)

    我们在使用Spring框架进行开发时,经常在service层写很多方法,而且这些方法都是带事务的,那么Spring的事务怎么在多个方法之间传播呢?今天我们就仔细聊一聊. Spring的事务传播机制主要 ...

  3. Java日期时间API系列14-----Jdk8中java.time包中的新的日期时间API类,java日期计算1,获取年月日时分秒等

    通过Java日期时间API系列8-----Jdk8中java.time包中的新的日期时间API类的LocalDate源码分析 ,可以看出java8设计非常好,实现接口Temporal, Tempora ...

  4. dotnet 泛型委托 ACTION FUNC

    void Main() { // 泛型委托 ACTION FUNC // 3. 创建委托实例 TestDele<string> testDele = new TestDele<str ...

  5. 前端 面试 html css 如何让一个盒子水平垂直居中?

    方法1  使用子绝父相 定位  推荐 说明: 让父元素相对定位,因为要让子元素以父元素为参考对象,如果父元素不设置定位,子元素的参考对象就是整个页面document: 子元素绝对定位,top:50%: ...

  6. vue本地项目启动时遇到coreJs相关报错问题处理

    启动项目的时候报错 : 是因为core.js这个包丢失,需要大家重新下载即可 : yarn add core-js

  7. CSP模拟10--总结

    今天是我第一次给模拟赛写正规总结--因为今天的题真的受不了了 四道数学题,一点都不拖泥带水的纯血数学题! T1.黑暗型高松灯 shit 本来是一道放在T4防AK的题,结果学长为了 恶心 锻炼一下我们, ...

  8. 云原生的 WebAssembly 能取代 Docker 吗?

    WebAssembly 是一个可移植.体积小.加载快并且兼容 Web 的全新格式.由于 WebAssembly 具有很高的安全性,可移植性,效率和轻量级功能,因此它是应用程序安全沙箱方案的理想选择.现 ...

  9. Saas多租户数据权限设计(参考RuoYi)

    导航 引子 场景梳理 基于角色的访问控制(RBAC) 多租户系统的权限设计 RuoYi系统的数据权限设计 最终设计方案 参考 本文首发<智客工坊-Saas多租户数据权限设计(参考RuoYi)&g ...

  10. 霍夫(Hough)直线变换(直线检测)

    0 原理 霍夫变换在检测各种形状的的技术中非常流行,如果你要检测的形状可以用数学表达式写出,你就可以是使用霍夫变换检测它.及时要检测的形状存在一点破坏或者扭曲也可以使用.我们下面就看看如何使用霍夫变换 ...