inet: frags: annotate races around fqdir->dead and fqdir->high_thresh
commit 91341fa0003befd097e190ec2a4bf63ad957c49a upstream.
Both fields can be read/written without synchronization,
add proper accessors and documentation.
Fixes: d5dd88794a
("inet: fix various use-after-free in defrags units")
Signed-off-by: Eric Dumazet <edumazet@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
967ec4b059
commit
577d3c5291
@ -116,8 +116,15 @@ int fqdir_init(struct fqdir **fqdirp, struct inet_frags *f, struct net *net);
|
|||||||
|
|
||||||
static inline void fqdir_pre_exit(struct fqdir *fqdir)
|
static inline void fqdir_pre_exit(struct fqdir *fqdir)
|
||||||
{
|
{
|
||||||
fqdir->high_thresh = 0; /* prevent creation of new frags */
|
/* Prevent creation of new frags.
|
||||||
fqdir->dead = true;
|
* Pairs with READ_ONCE() in inet_frag_find().
|
||||||
|
*/
|
||||||
|
WRITE_ONCE(fqdir->high_thresh, 0);
|
||||||
|
|
||||||
|
/* Pairs with READ_ONCE() in inet_frag_kill(), ip_expire()
|
||||||
|
* and ip6frag_expire_frag_queue().
|
||||||
|
*/
|
||||||
|
WRITE_ONCE(fqdir->dead, true);
|
||||||
}
|
}
|
||||||
void fqdir_exit(struct fqdir *fqdir);
|
void fqdir_exit(struct fqdir *fqdir);
|
||||||
|
|
||||||
|
@ -67,7 +67,8 @@ ip6frag_expire_frag_queue(struct net *net, struct frag_queue *fq)
|
|||||||
struct sk_buff *head;
|
struct sk_buff *head;
|
||||||
|
|
||||||
rcu_read_lock();
|
rcu_read_lock();
|
||||||
if (fq->q.fqdir->dead)
|
/* Paired with the WRITE_ONCE() in fqdir_pre_exit(). */
|
||||||
|
if (READ_ONCE(fq->q.fqdir->dead))
|
||||||
goto out_rcu_unlock;
|
goto out_rcu_unlock;
|
||||||
spin_lock(&fq->q.lock);
|
spin_lock(&fq->q.lock);
|
||||||
|
|
||||||
|
@ -204,9 +204,9 @@ void inet_frag_kill(struct inet_frag_queue *fq)
|
|||||||
/* The RCU read lock provides a memory barrier
|
/* The RCU read lock provides a memory barrier
|
||||||
* guaranteeing that if fqdir->dead is false then
|
* guaranteeing that if fqdir->dead is false then
|
||||||
* the hash table destruction will not start until
|
* the hash table destruction will not start until
|
||||||
* after we unlock. Paired with inet_frags_exit_net().
|
* after we unlock. Paired with fqdir_pre_exit().
|
||||||
*/
|
*/
|
||||||
if (!fqdir->dead) {
|
if (!READ_ONCE(fqdir->dead)) {
|
||||||
rhashtable_remove_fast(&fqdir->rhashtable, &fq->node,
|
rhashtable_remove_fast(&fqdir->rhashtable, &fq->node,
|
||||||
fqdir->f->rhash_params);
|
fqdir->f->rhash_params);
|
||||||
refcount_dec(&fq->refcnt);
|
refcount_dec(&fq->refcnt);
|
||||||
@ -321,9 +321,11 @@ static struct inet_frag_queue *inet_frag_create(struct fqdir *fqdir,
|
|||||||
/* TODO : call from rcu_read_lock() and no longer use refcount_inc_not_zero() */
|
/* TODO : call from rcu_read_lock() and no longer use refcount_inc_not_zero() */
|
||||||
struct inet_frag_queue *inet_frag_find(struct fqdir *fqdir, void *key)
|
struct inet_frag_queue *inet_frag_find(struct fqdir *fqdir, void *key)
|
||||||
{
|
{
|
||||||
|
/* This pairs with WRITE_ONCE() in fqdir_pre_exit(). */
|
||||||
|
long high_thresh = READ_ONCE(fqdir->high_thresh);
|
||||||
struct inet_frag_queue *fq = NULL, *prev;
|
struct inet_frag_queue *fq = NULL, *prev;
|
||||||
|
|
||||||
if (!fqdir->high_thresh || frag_mem_limit(fqdir) > fqdir->high_thresh)
|
if (!high_thresh || frag_mem_limit(fqdir) > high_thresh)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
rcu_read_lock();
|
rcu_read_lock();
|
||||||
|
@ -144,7 +144,8 @@ static void ip_expire(struct timer_list *t)
|
|||||||
|
|
||||||
rcu_read_lock();
|
rcu_read_lock();
|
||||||
|
|
||||||
if (qp->q.fqdir->dead)
|
/* Paired with WRITE_ONCE() in fqdir_pre_exit(). */
|
||||||
|
if (READ_ONCE(qp->q.fqdir->dead))
|
||||||
goto out_rcu_unlock;
|
goto out_rcu_unlock;
|
||||||
|
|
||||||
spin_lock(&qp->q.lock);
|
spin_lock(&qp->q.lock);
|
||||||
|
Loading…
Reference in New Issue
Block a user