RPC: Fix two potential races in put_rpccred
authorTrond Myklebust <Trond.Myklebust@netapp.com>
Thu, 3 Dec 2009 13:10:17 +0000 (08:10 -0500)
committerTrond Myklebust <Trond.Myklebust@netapp.com>
Thu, 3 Dec 2009 13:10:17 +0000 (08:10 -0500)
It is possible for rpcauth_destroy_credcache() to cause the rpc credentials
to be unhashed while put_rpccred is waiting for the rpc_credcache_lock on
another cpu. Should this happen, then we can end up calling
hlist_del_rcu(&cred->cr_hash) a second time in put_rpccred, thus causing
list corruption.

Should the credential actually be hashed, it is also possible for
rpcauth_lookup_credcache to find and reference it before we get round to
unhashing it. In this case, the call to rpcauth_unhash_cred will fail, and
so we should just exit without destroying the cred.

Reported-by: Neil Brown <neilb@suse.de>
Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
net/sunrpc/auth.c

index 54a4e04..7ee6f7e 100644 (file)
@@ -123,16 +123,19 @@ rpcauth_unhash_cred_locked(struct rpc_cred *cred)
        clear_bit(RPCAUTH_CRED_HASHED, &cred->cr_flags);
 }
 
-static void
+static int
 rpcauth_unhash_cred(struct rpc_cred *cred)
 {
        spinlock_t *cache_lock;
+       int ret;
 
        cache_lock = &cred->cr_auth->au_credcache->lock;
        spin_lock(cache_lock);
-       if (atomic_read(&cred->cr_count) == 0)
+       ret = atomic_read(&cred->cr_count) == 0;
+       if (ret)
                rpcauth_unhash_cred_locked(cred);
        spin_unlock(cache_lock);
+       return ret;
 }
 
 /*
@@ -446,31 +449,35 @@ void
 put_rpccred(struct rpc_cred *cred)
 {
        /* Fast path for unhashed credentials */
-       if (test_bit(RPCAUTH_CRED_HASHED, &cred->cr_flags) != 0)
-               goto need_lock;
-
-       if (!atomic_dec_and_test(&cred->cr_count))
+       if (test_bit(RPCAUTH_CRED_HASHED, &cred->cr_flags) == 0) {
+               if (atomic_dec_and_test(&cred->cr_count))
+                       cred->cr_ops->crdestroy(cred);
                return;
-       goto out_destroy;
-need_lock:
+       }
+
        if (!atomic_dec_and_lock(&cred->cr_count, &rpc_credcache_lock))
                return;
        if (!list_empty(&cred->cr_lru)) {
                number_cred_unused--;
                list_del_init(&cred->cr_lru);
        }
-       if (test_bit(RPCAUTH_CRED_UPTODATE, &cred->cr_flags) == 0)
-               rpcauth_unhash_cred(cred);
        if (test_bit(RPCAUTH_CRED_HASHED, &cred->cr_flags) != 0) {
-               cred->cr_expire = jiffies;
-               list_add_tail(&cred->cr_lru, &cred_unused);
-               number_cred_unused++;
-               spin_unlock(&rpc_credcache_lock);
-               return;
+               if (test_bit(RPCAUTH_CRED_UPTODATE, &cred->cr_flags) != 0) {
+                       cred->cr_expire = jiffies;
+                       list_add_tail(&cred->cr_lru, &cred_unused);
+                       number_cred_unused++;
+                       goto out_nodestroy;
+               }
+               if (!rpcauth_unhash_cred(cred)) {
+                       /* We were hashed and someone looked us up... */
+                       goto out_nodestroy;
+               }
        }
        spin_unlock(&rpc_credcache_lock);
-out_destroy:
        cred->cr_ops->crdestroy(cred);
+       return;
+out_nodestroy:
+       spin_unlock(&rpc_credcache_lock);
 }
 EXPORT_SYMBOL_GPL(put_rpccred);