CVE-2024-26809


环境搭建

version:Linux-v5.15.54

commit: 2f1f6bbfdcd97fa9a1cddf37a4dd85c0c334127d

config:defconfig+

CONFIG_USER_NS=y
CONFIG_SECURITY_SELINUX_DISABLE=y
# for debug
CONFIG_KALLSYMS=y
CONFIG_KALLSYMS_ALL=y
CONFIG_DEBUG_INFO_DWARF4=y
# for msg_msg copy
CONFIG_CHECKPOINT_RESTORE=y
# for syzkaller image
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
# for debug
CONFIG_KALLSYMS=y
CONFIG_KALLSYMS_ALL=y
CONFIG_DEBUG_INFO_DWARF4=y
# for msg_msg copy
CONFIG_CHECKPOINT_RESTORE=y
# for syzkaller image
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:

1749948592659

最后选用v6.1.79原因见后:

commit:cd6fed5f86432cd1eb4a7de3659e47a811b3b652

config:

CONFIG_USER_NS=y
CONFIG_SECURITY_SELINUX_DISABLE=y
# for debug
CONFIG_KALLSYMS=y
CONFIG_KALLSYMS_ALL=y
CONFIG_DEBUG_INFO_DWARF4=y
# for msg_msg copy
CONFIG_CHECKPOINT_RESTORE=y
# for syzkaller image
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);//free掉一个match的所有elem

#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); //free掉一个match的所有elem

#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;

//同一个elem只在最后一个的位置处理,确保elem只被free一次;
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

1749949277228

1749950205093

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

1750038548098

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

1750038591786

4.klen的问题

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

1750052763949

这就要我们保证klen==field*4;

而elem也需要长度和klen对应

漏洞利用

1750054271023

参考

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


文章作者: q1ming
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 q1ming !
  目录