环境搭建
version:Linux-v5.15.54
commit: 2f1f6bbfdcd97fa9a1cddf37a4dd85c0c334127d
config:defconfig+
CONFIG_USER_NS=y CONFIG_SECURITY_SELINUX_DISABLE=y
CONFIG_KALLSYMS=y CONFIG_KALLSYMS_ALL=y CONFIG_DEBUG_INFO_DWARF4=y
CONFIG_CHECKPOINT_RESTORE=y
CONFIG_CONFIGFS_FS=y CONFIG_SECURITYFS=y
CONFIG_SLAB_FREELIST_RANDOM=n CONFIG_SLAB_FREELIST_HARDENED=n CONFIG_SHUFFLE_PAGE_ALLOCATOR=n CONFIG_HARDENED_USERCOPY=n CONFIG_FORTIFY_SOURCE=n CONFIG_STATIC_USERMODEHELPER=n CONFIG_DEBUG_INFO_NONE=n CONFIG_RANDOMIZE_BASE=n
CONFIG_DEBUG_INFO=y
CONFIG_NETFILTER=y CONFIG_NF_TABLES=y
|
LTS版本环境搭建如下:
version:
Linux v6.1
commit:
7614896350aa20764c5eca527262d9eb0a57da63
|
config:
CONFIG_USER_NS=y CONFIG_SECURITY_SELINUX_DISABLE=y
CONFIG_KALLSYMS=y CONFIG_KALLSYMS_ALL=y CONFIG_DEBUG_INFO_DWARF4=y
CONFIG_CHECKPOINT_RESTORE=y
CONFIG_CONFIGFS_FS=y CONFIG_SECURITYFS=y
CONFIG_SLAB_FREELIST_RANDOM=n CONFIG_SLAB_FREELIST_HARDENED=n CONFIG_SHUFFLE_PAGE_ALLOCATOR=n CONFIG_HARDENED_USERCOPY=n CONFIG_FORTIFY_SOURCE=n CONFIG_STATIC_USERMODEHELPER=n CONFIG_DEBUG_INFO_NONE=n CONFIG_RANDOMIZE_BASE=n
CONFIG_DEBUG_INFO=y
CONFIG_NETFILTER=y CONFIG_NF_TABLES=y
CONFIG_NF_TABLES_INET=y
|
在Linux-v6.1中要配置好设备才能支持family:

最后选用v6.1.79原因见后:
commit:cd6fed5f86432cd1eb4a7de3659e47a811b3b652
config:
CONFIG_USER_NS=y CONFIG_SECURITY_SELINUX_DISABLE=y
CONFIG_KALLSYMS=y CONFIG_KALLSYMS_ALL=y CONFIG_DEBUG_INFO_DWARF4=y
CONFIG_CHECKPOINT_RESTORE=y
CONFIG_CONFIGFS_FS=y CONFIG_SECURITYFS=y
CONFIG_SLAB_FREELIST_RANDOM=n CONFIG_SLAB_FREELIST_HARDENED=n CONFIG_SHUFFLE_PAGE_ALLOCATOR=n CONFIG_HARDENED_USERCOPY=n CONFIG_FORTIFY_SOURCE=n CONFIG_STATIC_USERMODEHELPER=n CONFIG_DEBUG_INFO_NONE=n CONFIG_RANDOMIZE_BASE=n
CONFIG_DEBUG_INFO=y
CONFIG_NETFILTER=y CONFIG_NF_TABLES=y
CONFIG_NF_TABLES_INET=y
|
源码分析

https://elixir.bootlin.com/linux/v5.15.54/source/net/netfilter/nft_set_pipapo.c#L2123
static void nft_pipapo_destroy(const struct nft_set *set) { struct nft_pipapo *priv = nft_set_priv(set); struct nft_pipapo_match *m; int cpu;
m = rcu_dereference_protected(priv->match, true); if (m) { rcu_barrier();
nft_set_pipapo_match_destroy(set, m);
#ifdef NFT_PIPAPO_ALIGN free_percpu(m->scratch_aligned); #endif for_each_possible_cpu(cpu) kfree(*per_cpu_ptr(m->scratch, cpu)); free_percpu(m->scratch); pipapo_free_fields(m); kfree(m); priv->match = NULL; }
if (priv->clone) { m = priv->clone;
if (priv->dirty) nft_set_pipapo_match_destroy(set, m);
#ifdef NFT_PIPAPO_ALIGN free_percpu(priv->clone->scratch_aligned); #endif for_each_possible_cpu(cpu) kfree(*per_cpu_ptr(priv->clone->scratch, cpu)); free_percpu(priv->clone->scratch);
pipapo_free_fields(priv->clone); kfree(priv->clone); priv->clone = NULL; } }
|
nft_set_pipapo_match_destroy
函数如下:
static void nft_set_pipapo_match_destroy(const struct nft_set *set, struct nft_pipapo_match *m) { struct nft_pipapo_field *f; int i, r;
for (i = 0, f = m->f; i < m->field_count - 1; i++, f++) ;
for (r = 0; r < f->rules; r++) { struct nft_pipapo_elem *e; if (r < f->rules - 1 && f->mt[r + 1].e == f->mt[r].e) continue;
e = f->mt[r].e;
nft_set_elem_destroy(set, e, true); } }
|
nft_set_elem_destroy函数如下:

可以看到elem其实是在批处理的过程中就被free掉了;
关于clone和dirty
struct nft_pipapo的定义如下:

在pipapo_init中会做如下初始化:

其中clone就是原来match的一个副本;
pipapo_commit则是用clone来更新match:

dirty则是用来标识match和clone是否不一致;
漏洞成因
就是希望在destroy的时候,dirty为true,但是clone和match中的elem并不是完全不同,这样两者相同的elem就会被double-free;
然后调查能够设置dirty为true的点一共有三个:
第一个是insert的时候:

会在执行完commit,但是似乎是全执行完才会commit:

一个是gc的时候:

还有一个是remove,但是注意remove之后会立即commit:

触发漏洞
destroy



这里有个问题,insert一个elem确实会设置dirty为true,但是当所有的批处理执行完之后会有一个ss->commit,此时会通过activate调用到pipapo_commit,最终完成clone的拷贝,然后将dirty设置为false;
所以想法是只能在同一个批处理中触发漏洞,但是delset并不会在批处理执行过程中调用destroy,只会在所有iov都被处理完了之后处理trans的时候才会调用pipapo_destroy,此时insert的trans会先于delset的trans执行,根本做不到啊。
问题总结
这里总结一下在6.1版本内核下需要注意的几个点:
1.family
2.NFT_SET_OBJRECT


3.关于我为什么触发不了
后来和其他师傅讨论后发现,原来在v6.1版本的内核中,会有如下调用链:
nf_tables_commit case NFT_MSG_NEWSETELEM nft_setelem_activate set->ops->activate => nft_pipapo_activate() pipapo_commit() priv->dirty = false
|
v6.1.0版本内核中,nft_pipapo_activate函数如下:https://elixir.bootlin.com/linux/v6.1/source/net/netfilter/nft_set_pipapo.c#L1675

但是到了v6.1.79版本的内核中,nft_pipapo_activate函数变成如下样子:https://elixir.bootlin.com/linux/v6.1.79/source/net/netfilter/nft_set_pipapo.c#L1762

4.klen的问题
在v6.1版本的内核中,nf_tables_newset函数会有这样一个检查:

这就要我们保证klen==field*4;
而elem也需要长度和klen对应
漏洞利用

参考
https://github.com/google/security-research/blob/master/pocs/linux/kernelctf/CVE-2024-26809_lts_cos/docs/vulnerability.md
https://github.com/google/security-research/blob/master/pocs/linux/kernelctf/CVE-2024-26809_lts_cos/docs/exploit.md