CVE-2023-6817


思维导图

源码分析

pipapo_walk

源码链接:https://elixir.bootlin.com/linux/v5.15.185/source/net/netfilter/nft_set_pipapo.c#L2041

static void nft_pipapo_walk(const struct nft_ctx *ctx, struct nft_set *set,
struct nft_set_iter *iter)
{
struct nft_pipapo *priv = nft_set_priv(set);
const struct nft_pipapo_match *m;
const struct nft_pipapo_field *f;
int i, r;

WARN_ON_ONCE(iter->type != NFT_ITER_READ &&
iter->type != NFT_ITER_UPDATE);

rcu_read_lock();
if (iter->type == NFT_ITER_READ) //如果是只读操作就直接用match
m = rcu_dereference(priv->match);
else
m = priv->clone; //写操作需要用clone副本

if (unlikely(!m))
goto out;

for (i = 0, f = m->f; i < m->field_count - 1; i++, f++) //最终i==field_count,f指向最后一个nft_pipapo_field
;

for (r = 0; r < f->rules; r++) { //处理最后一个field中的每一个rule
struct nft_pipapo_elem *e;
struct nft_set_elem elem;

if (r < f->rules - 1 && f->mt[r + 1].e == f->mt[r].e) //如果当前rule不是同一个elem中的最后一个,就continue
continue;

if (iter->count < iter->skip) //可能是有一个跳过的值
goto cont;

e = f->mt[r].e; //每次就处理一遍elem

if (!nft_set_elem_active(&e->ext, iter->genmask)) //CVE-2023-6817
goto cont;

elem.priv = e;

iter->err = iter->fn(ctx, set, iter, &elem); //对elem进行处理
if (iter->err < 0)
goto out;

cont:
iter->count++;
}

out:
rcu_read_unlock();
}

总结一下,pipapo_walk函数的作用就是以一个iter作为参数的集合传进来,然后遍历pipapo_set中的每一个elem,并且各自调用iter中的回调函数fn一次;

下面看一下哪些函数会调用到walk,以及传入的回调函数指针fn都是什么:

nft_map_deactivate:

nf_tables_bind_set:

nft_map_activate:

nf_tables_dump_set:

nft_set_flush:

nft_setelem_flush

static int nft_setelem_flush(const struct nft_ctx *ctx,
struct nft_set *set,
const struct nft_set_iter *iter,
struct nft_set_elem *elem)
{
struct nft_trans *trans;
int err;

trans = nft_trans_alloc_gfp(ctx, NFT_MSG_DELSETELEM, //分配一个NFT_MSG_DELSETELEM的trans
sizeof(struct nft_trans_elem), GFP_ATOMIC);
if (!trans)
return -ENOMEM;

if (!set->ops->flush(ctx->net, set, elem->priv)) {
err = -ENOENT;
goto err1;
}
set->ndeact++;

nft_setelem_data_deactivate(ctx->net, set, elem); //将elem标记为不活跃
nft_trans_elem_set(trans) = set;
nft_trans_elem(trans) = *elem; //将elem加入到trans中,到了ss->commit中就会释放掉
nft_trans_commit_list_add_tail(ctx->net, trans);

return 0;
err1:
kfree(trans);
return err;
}

nft_pipapo_flush

static bool nft_pipapo_flush(const struct net *net, const struct nft_set *set,
void *elem)
{
struct nft_pipapo_elem *e = elem;

return pipapo_deactivate(net, set, (const u8 *)nft_set_ext_key(&e->ext),
&e->ext);
}
static void *pipapo_deactivate(const struct net *net, const struct nft_set *set,
const u8 *data, const struct nft_set_ext *ext)
{
struct nft_pipapo_elem *e;

e = pipapo_get(net, set, data, nft_genmask_next(net)); //如果pipapo_get的话,是不是
if (IS_ERR(e))
return NULL;

nft_set_elem_change_active(net, set, &e->ext);

return e;
}
static inline void nft_set_elem_change_active(const struct net *net,
const struct nft_set *set,
struct nft_set_ext *ext)
{
ext->genmask ^= nft_genmask_next(net);
}
static struct nft_pipapo_elem *pipapo_get(const struct net *net,
const struct nft_set *set,
const u8 *data, u8 genmask)
{
struct nft_pipapo_elem *ret = ERR_PTR(-ENOENT);
struct nft_pipapo *priv = nft_set_priv(set);
struct nft_pipapo_match *m = priv->clone;
unsigned long *res_map, *fill_map = NULL;
struct nft_pipapo_field *f;
int i;

res_map = kmalloc_array(m->bsize_max, sizeof(*res_map), GFP_ATOMIC);
if (!res_map) {
ret = ERR_PTR(-ENOMEM);
goto out;
}

fill_map = kcalloc(m->bsize_max, sizeof(*res_map), GFP_ATOMIC);
if (!fill_map) {
ret = ERR_PTR(-ENOMEM);
goto out;
}

memset(res_map, 0xff, m->bsize_max * sizeof(*res_map));

nft_pipapo_for_each_field(f, i, m) {
bool last = i == m->field_count - 1;
int b;

/* For each bit group: select lookup table bucket depending on
* packet bytes value, then AND bucket value
*/
if (f->bb == 8)
pipapo_and_field_buckets_8bit(f, res_map, data);
else if (f->bb == 4)
pipapo_and_field_buckets_4bit(f, res_map, data);
else
BUG();

data += f->groups / NFT_PIPAPO_GROUPS_PER_BYTE(f);

/* Now populate the bitmap for the next field, unless this is
* the last field, in which case return the matched 'ext'
* pointer if any.
*
* Now res_map contains the matching bitmap, and fill_map is the
* bitmap for the next field.
*/
next_match:
b = pipapo_refill(res_map, f->bsize, f->rules, fill_map, f->mt,
last);
if (b < 0)
goto out;

if (last) {
if (nft_set_elem_expired(&f->mt[b].e->ext) ||
(genmask &&
!nft_set_elem_active(&f->mt[b].e->ext, genmask)))
goto next_match;

ret = f->mt[b].e;
goto out;
}

data += NFT_PIPAPO_GROUPS_PADDING(f);

/* Swap bitmap indices: fill_map will be the initial bitmap for
* the next field (i.e. the new res_map), and res_map is
* guaranteed to be all-zeroes at this point, ready to be filled
* according to the next mapping table.
*/
swap(res_map, fill_map);
}

out:
kfree(fill_map);
kfree(res_map);
return ret;
}

nf_tables_delsetelem

static int nf_tables_delsetelem(struct sk_buff *skb,
const struct nfnl_info *info,
const struct nlattr * const nla[])
{
struct netlink_ext_ack *extack = info->extack;
u8 genmask = nft_genmask_next(info->net);
u8 family = info->nfmsg->nfgen_family;
struct net *net = info->net;
const struct nlattr *attr;
struct nft_table *table;
struct nft_set *set;
struct nft_ctx ctx;
int rem, err = 0;

table = nft_table_lookup(net, nla[NFTA_SET_ELEM_LIST_TABLE], family,
genmask, NETLINK_CB(skb).portid);
if (IS_ERR(table)) {
NL_SET_BAD_ATTR(extack, nla[NFTA_SET_ELEM_LIST_TABLE]);
return PTR_ERR(table);
}

set = nft_set_lookup(table, nla[NFTA_SET_ELEM_LIST_SET], genmask);
if (IS_ERR(set))
return PTR_ERR(set);

if (!list_empty(&set->bindings) &&
(set->flags & (NFT_SET_CONSTANT | NFT_SET_ANONYMOUS)))
return -EBUSY;

nft_ctx_init(&ctx, net, skb, info->nlh, family, table, NULL, nla);

if (!nla[NFTA_SET_ELEM_LIST_ELEMENTS])
return nft_set_flush(&ctx, set, genmask);

nla_for_each_nested(attr, nla[NFTA_SET_ELEM_LIST_ELEMENTS], rem) {
err = nft_del_setelem(&ctx, set, attr);
if (err == -ENOENT &&
NFNL_MSG_TYPE(info->nlh->nlmsg_type) == NFT_MSG_DESTROYSETELEM)
continue;

if (err < 0) {
NL_SET_BAD_ATTR(extack, attr);
break;
}
}
return err;
}

nft_del_setelem:

static int nft_del_setelem(struct nft_ctx *ctx, struct nft_set *set,
const struct nlattr *attr)
{
struct nlattr *nla[NFTA_SET_ELEM_MAX + 1];
struct nft_set_ext_tmpl tmpl;
struct nft_set_elem elem;
struct nft_set_ext *ext;
struct nft_trans *trans;
u32 flags = 0;
int err;

err = nla_parse_nested_deprecated(nla, NFTA_SET_ELEM_MAX, attr,
nft_set_elem_policy, NULL);
if (err < 0)
return err;

err = nft_setelem_parse_flags(set, nla[NFTA_SET_ELEM_FLAGS], &flags);
if (err < 0)
return err;

if (!nla[NFTA_SET_ELEM_KEY] && !(flags & NFT_SET_ELEM_CATCHALL))
return -EINVAL;

if (!nft_setelem_valid_key_end(set, nla, flags))
return -EINVAL;

nft_set_ext_prepare(&tmpl);

if (flags != 0) {
err = nft_set_ext_add(&tmpl, NFT_SET_EXT_FLAGS);
if (err < 0)
return err;
}

if (nla[NFTA_SET_ELEM_KEY]) {
err = nft_setelem_parse_key(ctx, set, &elem.key.val,
nla[NFTA_SET_ELEM_KEY]);
if (err < 0)
return err;

err = nft_set_ext_add_length(&tmpl, NFT_SET_EXT_KEY, set->klen);
if (err < 0)
goto fail_elem;
}

if (nla[NFTA_SET_ELEM_KEY_END]) {
err = nft_setelem_parse_key(ctx, set, &elem.key_end.val,
nla[NFTA_SET_ELEM_KEY_END]);
if (err < 0)
goto fail_elem;

err = nft_set_ext_add_length(&tmpl, NFT_SET_EXT_KEY_END, set->klen);
if (err < 0)
goto fail_elem_key_end;
}

err = -ENOMEM;
elem.priv = nft_set_elem_init(set, &tmpl, elem.key.val.data,
elem.key_end.val.data, NULL, 0, 0,
GFP_KERNEL_ACCOUNT);
if (IS_ERR(elem.priv)) {
err = PTR_ERR(elem.priv);
goto fail_elem_key_end;
}

ext = nft_set_elem_ext(set, elem.priv);
if (flags)
*nft_set_ext_flags(ext) = flags;

trans = nft_trans_elem_alloc(ctx, NFT_MSG_DELSETELEM, set);
if (trans == NULL)
goto fail_trans;

err = nft_setelem_deactivate(ctx->net, set, &elem, flags); //[1]
if (err < 0)
goto fail_ops;

nft_setelem_data_deactivate(ctx->net, set, &elem);

nft_trans_elem(trans) = elem;
nft_trans_commit_list_add_tail(ctx->net, trans);
return 0;

fail_ops:
kfree(trans);
fail_trans:
kfree(elem.priv);
fail_elem_key_end:
nft_data_release(&elem.key_end.val, NFT_DATA_VALUE);
fail_elem:
nft_data_release(&elem.key.val, NFT_DATA_VALUE);
return err;
}

环境搭建

commit:

8bb7eca972ad531c9b149c0a51ab43a417385813

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

POC

nft_set_flush的调用链如下:



nf_tables_delsetelem
if (!nla[NFTA_SET_ELEM_LIST_ELEMENTS])
return nft_set_flush(&ctx, set, genmask);

不能flush两次,

参考

https://zone.ci/aliyun/ali_nvd/272415.html


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