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

Commit f3324a2a authored by NeilBrown's avatar NeilBrown Committed by Trond Myklebust
Browse files

NFS: support RCU_WALK in nfs_permission()



nfs_permission makes two calls which are not always safe in RCU_WALK,
rpc_lookup_cred and nfs_do_access.

The second can easily be made rcu-safe by aborting with -ECHILD before
making the RPC call.

The former can be made rcu-safe by calling rpc_lookup_cred_nonblock()
instead.
As this will almost always succeed, we use it even when RCU_WALK
isn't being used as it still saves some spinlocks in a common case.
We only fall back to rpc_lookup_cred() if rpc_lookup_cred_nonblock()
fails and MAY_NOT_BLOCK isn't set.

This optimisation (always trying rpc_lookup_cred_nonblock()) is
particularly important when a security module is active.
In that case inode_permission() may return -ECHILD from
security_inode_permission() even though ->permission() succeeded in
RCU_WALK mode.
This leads to may_lookup() retrying inode_permission after performing
unlazy_walk().  The spinlock that rpc_lookup_cred() takes is often
more expensive than anything security_inode_permission() does, so that
spinlock becomes the main bottleneck.

Signed-off-by: default avatarNeilBrown <neilb@suse.de>
Signed-off-by: default avatarTrond Myklebust <trond.myklebust@primarydata.com>
parent bd956080
Loading
Loading
Loading
Loading
+20 −8
Original line number Diff line number Diff line
@@ -2316,6 +2316,10 @@ static int nfs_do_access(struct inode *inode, struct rpc_cred *cred, int mask)
	if (status == 0)
		goto out_cached;

	status = -ECHILD;
	if (mask & MAY_NOT_BLOCK)
		goto out;

	/* Be clever: ask server to check for all possible rights */
	cache.mask = MAY_EXEC | MAY_WRITE | MAY_READ;
	cache.cred = cred;
@@ -2392,15 +2396,23 @@ int nfs_permission(struct inode *inode, int mask)
	if (!NFS_PROTO(inode)->access)
		goto out_notsup;

	if (mask & MAY_NOT_BLOCK)
		return -ECHILD;

	/* Always try fast lookups first */
	rcu_read_lock();
	cred = rpc_lookup_cred_nonblock();
	if (!IS_ERR(cred))
		res = nfs_do_access(inode, cred, mask|MAY_NOT_BLOCK);
	else
		res = PTR_ERR(cred);
	rcu_read_unlock();
	if (res == -ECHILD && !(mask & MAY_NOT_BLOCK)) {
		/* Fast lookup failed, try the slow way */
		cred = rpc_lookup_cred();
		if (!IS_ERR(cred)) {
			res = nfs_do_access(inode, cred, mask);
			put_rpccred(cred);
		} else
			res = PTR_ERR(cred);
	}
out:
	if (!res && (mask & MAY_EXEC) && !execute_ok(inode))
		res = -EACCES;