Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f0380f3d authored by Trond Myklebust's avatar Trond Myklebust
Browse files

RPC: Fix two potential races in put_rpccred



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: default avatarNeil Brown <neilb@suse.de>
Signed-off-by: default avatarTrond Myklebust <Trond.Myklebust@netapp.com>
parent feb8ca37
Loading
Loading
Loading
Loading
+23 −16
Original line number Diff line number Diff line
@@ -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) {
		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++;
		spin_unlock(&rpc_credcache_lock);
		return;
			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);