diff options
author | Liping Zhang <zlpnobody@gmail.com> | 2016-10-22 06:51:25 -0400 |
---|---|---|
committer | Pablo Neira Ayuso <pablo@netfilter.org> | 2016-10-27 12:20:45 -0400 |
commit | 61f9e2924f4981d626b3a931fed935f2fa3cb4de (patch) | |
tree | 77d9f1c293690b43f9d858294e3164b464edd5fa | |
parent | bb6a6e8e091353770074608c1d1bfde0e20b8154 (diff) |
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: 086f332167d6 ("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>
-rw-r--r-- | include/net/netfilter/nf_tables.h | 6 | ||||
-rw-r--r-- | net/netfilter/nf_tables_api.c | 11 | ||||
-rw-r--r-- | net/netfilter/nft_dynset.c | 16 | ||||
-rw-r--r-- | net/netfilter/nft_set_hash.c | 4 | ||||
-rw-r--r-- | net/netfilter/nft_set_rbtree.c | 2 |
5 files changed, 23 insertions, 16 deletions
diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h index 5031e072567b..741dcded5b4f 100644 --- a/include/net/netfilter/nf_tables.h +++ b/include/net/netfilter/nf_tables.h | |||
@@ -542,7 +542,8 @@ void *nft_set_elem_init(const struct nft_set *set, | |||
542 | const struct nft_set_ext_tmpl *tmpl, | 542 | const struct nft_set_ext_tmpl *tmpl, |
543 | const u32 *key, const u32 *data, | 543 | const u32 *key, const u32 *data, |
544 | u64 timeout, gfp_t gfp); | 544 | u64 timeout, gfp_t gfp); |
545 | void nft_set_elem_destroy(const struct nft_set *set, void *elem); | 545 | void nft_set_elem_destroy(const struct nft_set *set, void *elem, |
546 | bool destroy_expr); | ||
546 | 547 | ||
547 | /** | 548 | /** |
548 | * struct nft_set_gc_batch_head - nf_tables set garbage collection batch | 549 | * 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 | { | 694 | { |
694 | int err; | 695 | int err; |
695 | 696 | ||
696 | __module_get(src->ops->type->owner); | ||
697 | if (src->ops->clone) { | 697 | if (src->ops->clone) { |
698 | dst->ops = src->ops; | 698 | dst->ops = src->ops; |
699 | err = src->ops->clone(dst, src); | 699 | 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 | } else { | 702 | } else { |
703 | memcpy(dst, src, src->ops->size); | 703 | memcpy(dst, src, src->ops->size); |
704 | } | 704 | } |
705 | |||
706 | __module_get(src->ops->type->owner); | ||
705 | return 0; | 707 | return 0; |
706 | } | 708 | } |
707 | 709 | ||
diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index 24db22257586..86e48aeb20be 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c | |||
@@ -3452,14 +3452,15 @@ void *nft_set_elem_init(const struct nft_set *set, | |||
3452 | return elem; | 3452 | return elem; |
3453 | } | 3453 | } |
3454 | 3454 | ||
3455 | void nft_set_elem_destroy(const struct nft_set *set, void *elem) | 3455 | void nft_set_elem_destroy(const struct nft_set *set, void *elem, |
3456 | bool destroy_expr) | ||
3456 | { | 3457 | { |
3457 | struct nft_set_ext *ext = nft_set_elem_ext(set, elem); | 3458 | struct nft_set_ext *ext = nft_set_elem_ext(set, elem); |
3458 | 3459 | ||
3459 | nft_data_uninit(nft_set_ext_key(ext), NFT_DATA_VALUE); | 3460 | nft_data_uninit(nft_set_ext_key(ext), NFT_DATA_VALUE); |
3460 | if (nft_set_ext_exists(ext, NFT_SET_EXT_DATA)) | 3461 | if (nft_set_ext_exists(ext, NFT_SET_EXT_DATA)) |
3461 | nft_data_uninit(nft_set_ext_data(ext), set->dtype); | 3462 | nft_data_uninit(nft_set_ext_data(ext), set->dtype); |
3462 | if (nft_set_ext_exists(ext, NFT_SET_EXT_EXPR)) | 3463 | if (destroy_expr && nft_set_ext_exists(ext, NFT_SET_EXT_EXPR)) |
3463 | nf_tables_expr_destroy(NULL, nft_set_ext_expr(ext)); | 3464 | nf_tables_expr_destroy(NULL, nft_set_ext_expr(ext)); |
3464 | 3465 | ||
3465 | kfree(elem); | 3466 | kfree(elem); |
@@ -3812,7 +3813,7 @@ void nft_set_gc_batch_release(struct rcu_head *rcu) | |||
3812 | 3813 | ||
3813 | gcb = container_of(rcu, struct nft_set_gc_batch, head.rcu); | 3814 | gcb = container_of(rcu, struct nft_set_gc_batch, head.rcu); |
3814 | for (i = 0; i < gcb->head.cnt; i++) | 3815 | for (i = 0; i < gcb->head.cnt; i++) |
3815 | nft_set_elem_destroy(gcb->head.set, gcb->elems[i]); | 3816 | nft_set_elem_destroy(gcb->head.set, gcb->elems[i], true); |
3816 | kfree(gcb); | 3817 | kfree(gcb); |
3817 | } | 3818 | } |
3818 | EXPORT_SYMBOL_GPL(nft_set_gc_batch_release); | 3819 | EXPORT_SYMBOL_GPL(nft_set_gc_batch_release); |
@@ -4030,7 +4031,7 @@ static void nf_tables_commit_release(struct nft_trans *trans) | |||
4030 | break; | 4031 | break; |
4031 | case NFT_MSG_DELSETELEM: | 4032 | case NFT_MSG_DELSETELEM: |
4032 | nft_set_elem_destroy(nft_trans_elem_set(trans), | 4033 | nft_set_elem_destroy(nft_trans_elem_set(trans), |
4033 | nft_trans_elem(trans).priv); | 4034 | nft_trans_elem(trans).priv, true); |
4034 | break; | 4035 | break; |
4035 | } | 4036 | } |
4036 | kfree(trans); | 4037 | kfree(trans); |
@@ -4171,7 +4172,7 @@ static void nf_tables_abort_release(struct nft_trans *trans) | |||
4171 | break; | 4172 | break; |
4172 | case NFT_MSG_NEWSETELEM: | 4173 | case NFT_MSG_NEWSETELEM: |
4173 | nft_set_elem_destroy(nft_trans_elem_set(trans), | 4174 | nft_set_elem_destroy(nft_trans_elem_set(trans), |
4174 | nft_trans_elem(trans).priv); | 4175 | nft_trans_elem(trans).priv, true); |
4175 | break; | 4176 | break; |
4176 | } | 4177 | } |
4177 | kfree(trans); | 4178 | kfree(trans); |
diff --git a/net/netfilter/nft_dynset.c b/net/netfilter/nft_dynset.c index bfdb689664b0..31ca94793aa9 100644 --- a/net/netfilter/nft_dynset.c +++ b/net/netfilter/nft_dynset.c | |||
@@ -44,18 +44,22 @@ static void *nft_dynset_new(struct nft_set *set, const struct nft_expr *expr, | |||
44 | ®s->data[priv->sreg_key], | 44 | ®s->data[priv->sreg_key], |
45 | ®s->data[priv->sreg_data], | 45 | ®s->data[priv->sreg_data], |
46 | timeout, GFP_ATOMIC); | 46 | timeout, GFP_ATOMIC); |
47 | if (elem == NULL) { | 47 | if (elem == NULL) |
48 | if (set->size) | 48 | goto err1; |
49 | atomic_dec(&set->nelems); | ||
50 | return NULL; | ||
51 | } | ||
52 | 49 | ||
53 | ext = nft_set_elem_ext(set, elem); | 50 | ext = nft_set_elem_ext(set, elem); |
54 | if (priv->expr != NULL && | 51 | if (priv->expr != NULL && |
55 | nft_expr_clone(nft_set_ext_expr(ext), priv->expr) < 0) | 52 | nft_expr_clone(nft_set_ext_expr(ext), priv->expr) < 0) |
56 | return NULL; | 53 | goto err2; |
57 | 54 | ||
58 | return elem; | 55 | return elem; |
56 | |||
57 | err2: | ||
58 | nft_set_elem_destroy(set, elem, false); | ||
59 | err1: | ||
60 | if (set->size) | ||
61 | atomic_dec(&set->nelems); | ||
62 | return NULL; | ||
59 | } | 63 | } |
60 | 64 | ||
61 | static void nft_dynset_eval(const struct nft_expr *expr, | 65 | static void nft_dynset_eval(const struct nft_expr *expr, |
diff --git a/net/netfilter/nft_set_hash.c b/net/netfilter/nft_set_hash.c index 3794cb2fc788..88d9fc8343e7 100644 --- a/net/netfilter/nft_set_hash.c +++ b/net/netfilter/nft_set_hash.c | |||
@@ -120,7 +120,7 @@ out: | |||
120 | return true; | 120 | return true; |
121 | 121 | ||
122 | err2: | 122 | err2: |
123 | nft_set_elem_destroy(set, he); | 123 | nft_set_elem_destroy(set, he, true); |
124 | err1: | 124 | err1: |
125 | return false; | 125 | return false; |
126 | } | 126 | } |
@@ -332,7 +332,7 @@ static int nft_hash_init(const struct nft_set *set, | |||
332 | 332 | ||
333 | static void nft_hash_elem_destroy(void *ptr, void *arg) | 333 | static void nft_hash_elem_destroy(void *ptr, void *arg) |
334 | { | 334 | { |
335 | nft_set_elem_destroy((const struct nft_set *)arg, ptr); | 335 | nft_set_elem_destroy((const struct nft_set *)arg, ptr, true); |
336 | } | 336 | } |
337 | 337 | ||
338 | static void nft_hash_destroy(const struct nft_set *set) | 338 | static void nft_hash_destroy(const struct nft_set *set) |
diff --git a/net/netfilter/nft_set_rbtree.c b/net/netfilter/nft_set_rbtree.c index 38b5bda242f8..36493a7cae88 100644 --- a/net/netfilter/nft_set_rbtree.c +++ b/net/netfilter/nft_set_rbtree.c | |||
@@ -266,7 +266,7 @@ static void nft_rbtree_destroy(const struct nft_set *set) | |||
266 | while ((node = priv->root.rb_node) != NULL) { | 266 | while ((node = priv->root.rb_node) != NULL) { |
267 | rb_erase(node, &priv->root); | 267 | rb_erase(node, &priv->root); |
268 | rbe = rb_entry(node, struct nft_rbtree_elem, node); | 268 | rbe = rb_entry(node, struct nft_rbtree_elem, node); |
269 | nft_set_elem_destroy(set, rbe); | 269 | nft_set_elem_destroy(set, rbe, true); |
270 | } | 270 | } |
271 | } | 271 | } |
272 | 272 | ||