0x00 漏洞简介
Linux内核的netfilter:nf_tables组件中存在释放后使用漏洞,可被利用来实现本地权限提升。 nft_verdict_init() 函数允许在钩子判定中使用正值作为丢弃错误,因此当 NF_DROP 发出类似于 NF_ACCEPT 的丢弃错误时,nf_hook_slow() 函数可能会导致双重释放漏洞。
0x01 漏洞详情
nf_hook_slow()函数:该函数循环遍历链中的所有规则,并在NF_DROP发出时立即停止评估(返回函数) 。
在NF_DROP处理过程中,它释放数据包并允许用户使用设置返回值NF_GET_DROPERR()。NF_ACCEPT在处理时使用 drop 错误使函数返回NF_DROP。经过一系列分析后,发现了一个双重释放。
// looping over existing rules when skb triggers chainint nf_hook_slow(struct sk_buff *skb, struct nf_hook_state *state,
const struct nf_hook_entries *e, unsigned int s){
unsigned int verdict;
int ret;
// loop over every rule
for (; s < e->num_hook_entries; s++) {
// acquire rule's verdict
verdict = nf_hook_entry_hookfn(&e->hooks[s], skb, state);
switch (verdict & NF_VERDICT_MASK) {
case NF_ACCEPT:
break; // go to next rule
case NF_DROP:
kfree_skb_reason(skb, SKB_DROP_REASON_NETFILTER_DROP);
// check if the verdict contains a drop err
ret = NF_DROP_GETERR(verdict);
if (ret == 0)
ret = -EPERM;
// immediately return (do not evaluate other rules)
return ret;
// [snip] alternative verdict cases
default:
WARN_ON_ONCE(1);
return 0;
}
}
return 1;}
nf_hook_slow()内核函数,它迭代 nftables 规则。
当为 netfilter 挂钩创建判决对象时,内核允许正丢弃错误。这意味着攻击用户可能会导致以下情况,即从钩子/规则返回nf_hook_slow()时释放 skb 对象,然后返回,就像链中的每个钩子/规则都返回一样。这会导致调用者误解情况,并继续解析数据包并最终双重释放它。
// userland API (netlink-based) handler for initializing the verdict
static int nft_verdict_init(const struct nft_ctx *ctx, struct nft_data *data,
struct nft_data_desc *desc, const struct nlattr *nla)
{
u8 genmask = nft_genmask_next(ctx->net);
struct nlattr *tb[NFTA_VERDICT_MAX + 1];
struct nft_chain *chain;
int err;
// [snip] initialize memory
// malicious user: data->verdict.code = 0xffff0000
switch (data->verdict.code) {
default:
// data->verdict.code & NF_VERDICT_MASK == 0x0 (NF_DROP)
switch (data->verdict.code & NF_VERDICT_MASK) {
case NF_ACCEPT:
case NF_DROP:
case NF_QUEUE:
break; // happy-flow
default:
return -EINVAL;
}
fallthrough;
case NFT_CONTINUE:
case NFT_BREAK:
case NFT_RETURN:
break; // happy-flow
case NFT_JUMP:
case NFT_GOTO:
// [snip] handle cases
break;
}
// successfully set the verdict value to 0xffff0000
desc->len = sizeof(data->verdict);
return 0;
}
nft_verdict_init()内核函数,构造一个netfilter verdict对象
// looping over existing rules when skb triggers chainint nf_hook_slow(struct sk_buff *skb, struct nf_hook_state *state,
const struct nf_hook_entries *e, unsigned int s){
unsigned int verdict;
int ret;
for (; s < e->num_hook_entries; s++) {
// malicious rule: verdict = 0xffff0000
verdict = nf_hook_entry_hookfn(&e->hooks[s], skb, state);
// 0xffff0000 & NF_VERDICT_MASK == 0x0 (NF_DROP)
switch (verdict & NF_VERDICT_MASK) {
case NF_ACCEPT:
break;
case NF_DROP:
// first free of double-free
kfree_skb_reason(skb,
SKB_DROP_REASON_NETFILTER_DROP);
// NF_DROP_GETERR(0xffff0000) == 1 (NF_ACCEPT)
ret = NF_DROP_GETERR(verdict);
if (ret == 0)
ret = -EPERM;
// return NF_ACCEPT (continue packet handling)
return ret;
// [snip] alternative verdict cases
default:
WARN_ON_ONCE(1);
return 0;
}
}
return 1;}
nf_hook_slow()内核函数,它迭代 nftables 规则
static inline int NF_HOOK(uint8_t pf, unsigned int hook, struct net *net, struct sock *sk,
struct sk_buff *skb, struct net_device *in, struct net_device *out,
int (*okfn)(struct net *, struct sock *, struct sk_buff *)){
// results in nf_hook_slow() call
int ret = nf_hook(pf, hook, net, sk, skb, in, out, okfn);
// if skb passes rules, handle skb, and double-free it
if (ret == NF_ACCEPT)
ret = okfn(net, sk, skb);
return ret;}
NF_HOOK()内核函数,成功时调用回调函数
双重释放会影响slab缓存struct sk_buff中的对象skbuff_head_cache,以及动态大小的sk_buff->head对象,范围从kmalloc-256直接来自伙伴分配器的最多4个页面(65536字节)与ipv4数据包 。
该sk_buff->head对象kmalloc_reserve()通过__alloc_skb()。这允许我们分配动态大小的对象。因此,我们可以从分配器分配大小从 256 到 65536 字节的完整页面的slab对象。
漏洞详见:https://pwning.tech/nftables/
0x02 影响版本
该漏洞影响从(包括)v5.14 到(包括)v6.6 的版本,不包括修补分支 v5.15.149>、v6.1.76>、v6.6.15>。这些版本的补丁于 2024 年 2 月发布。底层漏洞影响从 v3.15 到 v6.8-rc1 的所有版本(不包括已修补的稳定分支)。
0x03 漏洞验证
验证环境: ubuntu 23.04
0x04参考链接
https://github.com/Notselwyn/CVE-2024-1086