netfilter: nf_tables: fix *leak* when expr clone fail
When nft_expr_clone failed, a series of problems will happen:
1. module refcnt will leak, we call __module_get at the beginning but
we forget to put it back if ops->clone returns fail
2. memory will be leaked, if clone fail, we just return NULL and forget
to free the alloced element
3. set->nelems will become incorrect when set->size is specified. If
clone fail, we should decrease the set->nelems
Now this patch fixes these problems. And fortunately, clone fail will
only happen on counter expression when memory is exhausted.
Fixes: 086f332167
("netfilter: nf_tables: add clone interface to expression operations")
Signed-off-by: Liping Zhang <zlpnobody@gmail.com>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
This commit is contained in:
parent
bb6a6e8e09
commit
61f9e2924f
|
@ -542,7 +542,8 @@ void *nft_set_elem_init(const struct nft_set *set,
|
||||||
const struct nft_set_ext_tmpl *tmpl,
|
const struct nft_set_ext_tmpl *tmpl,
|
||||||
const u32 *key, const u32 *data,
|
const u32 *key, const u32 *data,
|
||||||
u64 timeout, gfp_t gfp);
|
u64 timeout, gfp_t gfp);
|
||||||
void nft_set_elem_destroy(const struct nft_set *set, void *elem);
|
void nft_set_elem_destroy(const struct nft_set *set, void *elem,
|
||||||
|
bool destroy_expr);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* struct nft_set_gc_batch_head - nf_tables set garbage collection batch
|
* struct nft_set_gc_batch_head - nf_tables set garbage collection batch
|
||||||
|
@ -693,7 +694,6 @@ static inline int nft_expr_clone(struct nft_expr *dst, struct nft_expr *src)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
__module_get(src->ops->type->owner);
|
|
||||||
if (src->ops->clone) {
|
if (src->ops->clone) {
|
||||||
dst->ops = src->ops;
|
dst->ops = src->ops;
|
||||||
err = src->ops->clone(dst, src);
|
err = src->ops->clone(dst, src);
|
||||||
|
@ -702,6 +702,8 @@ static inline int nft_expr_clone(struct nft_expr *dst, struct nft_expr *src)
|
||||||
} else {
|
} else {
|
||||||
memcpy(dst, src, src->ops->size);
|
memcpy(dst, src, src->ops->size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
__module_get(src->ops->type->owner);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3452,14 +3452,15 @@ void *nft_set_elem_init(const struct nft_set *set,
|
||||||
return elem;
|
return elem;
|
||||||
}
|
}
|
||||||
|
|
||||||
void nft_set_elem_destroy(const struct nft_set *set, void *elem)
|
void nft_set_elem_destroy(const struct nft_set *set, void *elem,
|
||||||
|
bool destroy_expr)
|
||||||
{
|
{
|
||||||
struct nft_set_ext *ext = nft_set_elem_ext(set, elem);
|
struct nft_set_ext *ext = nft_set_elem_ext(set, elem);
|
||||||
|
|
||||||
nft_data_uninit(nft_set_ext_key(ext), NFT_DATA_VALUE);
|
nft_data_uninit(nft_set_ext_key(ext), NFT_DATA_VALUE);
|
||||||
if (nft_set_ext_exists(ext, NFT_SET_EXT_DATA))
|
if (nft_set_ext_exists(ext, NFT_SET_EXT_DATA))
|
||||||
nft_data_uninit(nft_set_ext_data(ext), set->dtype);
|
nft_data_uninit(nft_set_ext_data(ext), set->dtype);
|
||||||
if (nft_set_ext_exists(ext, NFT_SET_EXT_EXPR))
|
if (destroy_expr && nft_set_ext_exists(ext, NFT_SET_EXT_EXPR))
|
||||||
nf_tables_expr_destroy(NULL, nft_set_ext_expr(ext));
|
nf_tables_expr_destroy(NULL, nft_set_ext_expr(ext));
|
||||||
|
|
||||||
kfree(elem);
|
kfree(elem);
|
||||||
|
@ -3812,7 +3813,7 @@ void nft_set_gc_batch_release(struct rcu_head *rcu)
|
||||||
|
|
||||||
gcb = container_of(rcu, struct nft_set_gc_batch, head.rcu);
|
gcb = container_of(rcu, struct nft_set_gc_batch, head.rcu);
|
||||||
for (i = 0; i < gcb->head.cnt; i++)
|
for (i = 0; i < gcb->head.cnt; i++)
|
||||||
nft_set_elem_destroy(gcb->head.set, gcb->elems[i]);
|
nft_set_elem_destroy(gcb->head.set, gcb->elems[i], true);
|
||||||
kfree(gcb);
|
kfree(gcb);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(nft_set_gc_batch_release);
|
EXPORT_SYMBOL_GPL(nft_set_gc_batch_release);
|
||||||
|
@ -4030,7 +4031,7 @@ static void nf_tables_commit_release(struct nft_trans *trans)
|
||||||
break;
|
break;
|
||||||
case NFT_MSG_DELSETELEM:
|
case NFT_MSG_DELSETELEM:
|
||||||
nft_set_elem_destroy(nft_trans_elem_set(trans),
|
nft_set_elem_destroy(nft_trans_elem_set(trans),
|
||||||
nft_trans_elem(trans).priv);
|
nft_trans_elem(trans).priv, true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
kfree(trans);
|
kfree(trans);
|
||||||
|
@ -4171,7 +4172,7 @@ static void nf_tables_abort_release(struct nft_trans *trans)
|
||||||
break;
|
break;
|
||||||
case NFT_MSG_NEWSETELEM:
|
case NFT_MSG_NEWSETELEM:
|
||||||
nft_set_elem_destroy(nft_trans_elem_set(trans),
|
nft_set_elem_destroy(nft_trans_elem_set(trans),
|
||||||
nft_trans_elem(trans).priv);
|
nft_trans_elem(trans).priv, true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
kfree(trans);
|
kfree(trans);
|
||||||
|
|
|
@ -44,18 +44,22 @@ static void *nft_dynset_new(struct nft_set *set, const struct nft_expr *expr,
|
||||||
®s->data[priv->sreg_key],
|
®s->data[priv->sreg_key],
|
||||||
®s->data[priv->sreg_data],
|
®s->data[priv->sreg_data],
|
||||||
timeout, GFP_ATOMIC);
|
timeout, GFP_ATOMIC);
|
||||||
if (elem == NULL) {
|
if (elem == NULL)
|
||||||
if (set->size)
|
goto err1;
|
||||||
atomic_dec(&set->nelems);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
ext = nft_set_elem_ext(set, elem);
|
ext = nft_set_elem_ext(set, elem);
|
||||||
if (priv->expr != NULL &&
|
if (priv->expr != NULL &&
|
||||||
nft_expr_clone(nft_set_ext_expr(ext), priv->expr) < 0)
|
nft_expr_clone(nft_set_ext_expr(ext), priv->expr) < 0)
|
||||||
return NULL;
|
goto err2;
|
||||||
|
|
||||||
return elem;
|
return elem;
|
||||||
|
|
||||||
|
err2:
|
||||||
|
nft_set_elem_destroy(set, elem, false);
|
||||||
|
err1:
|
||||||
|
if (set->size)
|
||||||
|
atomic_dec(&set->nelems);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void nft_dynset_eval(const struct nft_expr *expr,
|
static void nft_dynset_eval(const struct nft_expr *expr,
|
||||||
|
|
|
@ -120,7 +120,7 @@ out:
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
err2:
|
err2:
|
||||||
nft_set_elem_destroy(set, he);
|
nft_set_elem_destroy(set, he, true);
|
||||||
err1:
|
err1:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -332,7 +332,7 @@ static int nft_hash_init(const struct nft_set *set,
|
||||||
|
|
||||||
static void nft_hash_elem_destroy(void *ptr, void *arg)
|
static void nft_hash_elem_destroy(void *ptr, void *arg)
|
||||||
{
|
{
|
||||||
nft_set_elem_destroy((const struct nft_set *)arg, ptr);
|
nft_set_elem_destroy((const struct nft_set *)arg, ptr, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void nft_hash_destroy(const struct nft_set *set)
|
static void nft_hash_destroy(const struct nft_set *set)
|
||||||
|
|
|
@ -266,7 +266,7 @@ static void nft_rbtree_destroy(const struct nft_set *set)
|
||||||
while ((node = priv->root.rb_node) != NULL) {
|
while ((node = priv->root.rb_node) != NULL) {
|
||||||
rb_erase(node, &priv->root);
|
rb_erase(node, &priv->root);
|
||||||
rbe = rb_entry(node, struct nft_rbtree_elem, node);
|
rbe = rb_entry(node, struct nft_rbtree_elem, node);
|
||||||
nft_set_elem_destroy(set, rbe);
|
nft_set_elem_destroy(set, rbe, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue