Commit 61f9e292 authored by Liping Zhang's avatar Liping Zhang Committed by Pablo Neira Ayuso

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: 086f3321 ("netfilter: nf_tables: add clone interface to expression operations")
Signed-off-by: default avatarLiping Zhang <zlpnobody@gmail.com>
Signed-off-by: default avatarPablo Neira Ayuso <pablo@netfilter.org>
parent bb6a6e8e
...@@ -542,7 +542,8 @@ void *nft_set_elem_init(const struct nft_set *set, ...@@ -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) ...@@ -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) ...@@ -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, ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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, ...@@ -44,18 +44,22 @@ static void *nft_dynset_new(struct nft_set *set, const struct nft_expr *expr,
&regs->data[priv->sreg_key], &regs->data[priv->sreg_key],
&regs->data[priv->sreg_data], &regs->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 @@ static bool nft_hash_update(struct nft_set *set, const u32 *key, ...@@ -120,7 +120,7 @@ static bool nft_hash_update(struct nft_set *set, const u32 *key,
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, ...@@ -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) ...@@ -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);
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment