| From foo@baz Wed Nov 21 13:17:56 CET 2018 |
| From: Eric Dumazet <edumazet@google.com> |
| Date: Thu, 8 Nov 2018 17:34:27 -0800 |
| Subject: inet: frags: better deal with smp races |
| |
| From: Eric Dumazet <edumazet@google.com> |
| |
| [ Upstream commit 0d5b9311baf27bb545f187f12ecfd558220c607d ] |
| |
| Multiple cpus might attempt to insert a new fragment in rhashtable, |
| if for example RPS is buggy, as reported by 배석진 in |
| https://patchwork.ozlabs.org/patch/994601/ |
| |
| We use rhashtable_lookup_get_insert_key() instead of |
| rhashtable_insert_fast() to let cpus losing the race |
| free their own inet_frag_queue and use the one that |
| was inserted by another cpu. |
| |
| Fixes: 648700f76b03 ("inet: frags: use rhashtables for reassembly units") |
| Signed-off-by: Eric Dumazet <edumazet@google.com> |
| Reported-by: 배석진 <soukjin.bae@samsung.com> |
| Signed-off-by: David S. Miller <davem@davemloft.net> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| net/ipv4/inet_fragment.c | 28 +++++++++++++++------------- |
| 1 file changed, 15 insertions(+), 13 deletions(-) |
| |
| --- a/net/ipv4/inet_fragment.c |
| +++ b/net/ipv4/inet_fragment.c |
| @@ -180,21 +180,22 @@ static struct inet_frag_queue *inet_frag |
| } |
| |
| static struct inet_frag_queue *inet_frag_create(struct netns_frags *nf, |
| - void *arg) |
| + void *arg, |
| + struct inet_frag_queue **prev) |
| { |
| struct inet_frags *f = nf->f; |
| struct inet_frag_queue *q; |
| - int err; |
| |
| q = inet_frag_alloc(nf, f, arg); |
| - if (!q) |
| + if (!q) { |
| + *prev = ERR_PTR(-ENOMEM); |
| return NULL; |
| - |
| + } |
| mod_timer(&q->timer, jiffies + nf->timeout); |
| |
| - err = rhashtable_insert_fast(&nf->rhashtable, &q->node, |
| - f->rhash_params); |
| - if (err < 0) { |
| + *prev = rhashtable_lookup_get_insert_key(&nf->rhashtable, &q->key, |
| + &q->node, f->rhash_params); |
| + if (*prev) { |
| q->flags |= INET_FRAG_COMPLETE; |
| inet_frag_kill(q); |
| inet_frag_destroy(q); |
| @@ -207,17 +208,18 @@ EXPORT_SYMBOL(inet_frag_create); |
| /* TODO : call from rcu_read_lock() and no longer use refcount_inc_not_zero() */ |
| struct inet_frag_queue *inet_frag_find(struct netns_frags *nf, void *key) |
| { |
| - struct inet_frag_queue *fq; |
| + struct inet_frag_queue *fq = NULL, *prev; |
| |
| rcu_read_lock(); |
| - fq = rhashtable_lookup(&nf->rhashtable, key, nf->f->rhash_params); |
| - if (fq) { |
| + prev = rhashtable_lookup(&nf->rhashtable, key, nf->f->rhash_params); |
| + if (!prev) |
| + fq = inet_frag_create(nf, key, &prev); |
| + if (prev && !IS_ERR(prev)) { |
| + fq = prev; |
| if (!atomic_inc_not_zero(&fq->refcnt)) |
| fq = NULL; |
| - rcu_read_unlock(); |
| - return fq; |
| } |
| rcu_read_unlock(); |
| - return inet_frag_create(nf, key); |
| + return fq; |
| } |
| EXPORT_SYMBOL(inet_frag_find); |