Commit bea92fd5 authored by Trond Myklebust's avatar Trond Myklebust Committed by Linus Torvalds

[PATCH] add proper NFSv3 permissions checking.

Add full support for the NFSv3 permissions checking. Ensures that we
work properly with NFSv3 servers that do uid/gid mapping and/or have
support for ACLs.

Permissions are cached in the struct nfs_inode in order to reduce the
number of RPC calls. The cache timeout period is given by the ordinary
attribute timeout.
parent 3322b8ec
......@@ -1080,38 +1080,70 @@ static int nfs_rename(struct inode *old_dir, struct dentry *old_dentry,
int
nfs_permission(struct inode *inode, int mask)
{
int error = vfs_permission(inode, mask);
if (!NFS_PROTO(inode)->access)
goto out;
struct nfs_access_cache *cache = &NFS_I(inode)->cache_access;
struct rpc_cred *cred;
int mode = inode->i_mode;
int res;
if (error == -EROFS)
goto out;
if (mask & MAY_WRITE) {
/*
*
* Nobody gets write access to a read-only fs.
*
*/
if (IS_RDONLY(inode) &&
(S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode)))
return -EROFS;
/*
* Trust UNIX mode bits except:
*
* 1) When override capabilities may have been invoked
* 2) When root squashing may be involved
* 3) When ACLs may overturn a negative answer */
if (!capable(CAP_DAC_OVERRIDE) && !capable(CAP_DAC_READ_SEARCH)
&& (current->fsuid != 0) && (current->fsgid != 0)
&& error != -EACCES)
goto out;
* Nobody gets write access to an immutable file.
*
*/
if (IS_IMMUTABLE(inode))
return -EACCES;
}
lock_kernel();
error = NFS_PROTO(inode)->access(inode, mask, 0);
if (error == -EACCES && NFS_CLIENT(inode)->cl_droppriv &&
current->uid != 0 && current->gid != 0 &&
(current->fsuid != current->uid || current->fsgid != current->gid))
error = NFS_PROTO(inode)->access(inode, mask, 1);
if (!NFS_PROTO(inode)->access)
goto out_notsup;
cred = rpcauth_lookupcred(NFS_CLIENT(inode)->cl_auth, 0);
if (cache->cred == cred
&& time_before(jiffies, cache->jiffies + NFS_ATTRTIMEO(inode))) {
if (!(res = cache->err)) {
/* Is the mask a subset of an accepted mask? */
if ((cache->mask & mask) == mask)
goto out;
} else {
/* ...or is it a superset of a rejected mask? */
if ((cache->mask & mask) == cache->mask)
goto out;
}
}
res = NFS_PROTO(inode)->access(inode, cred, mask);
if (!res || res == -EACCES)
goto add_cache;
out:
put_rpccred(cred);
unlock_kernel();
out:
return error;
return res;
out_notsup:
nfs_revalidate_inode(NFS_SERVER(inode), inode);
res = vfs_permission(inode, mask);
unlock_kernel();
return res;
add_cache:
cache->jiffies = jiffies;
if (cache->cred)
put_rpccred(cache->cred);
cache->cred = cred;
cache->mask = mask;
cache->err = res;
unlock_kernel();
return res;
}
/*
......
......@@ -125,8 +125,12 @@ nfs_delete_inode(struct inode * inode)
static void
nfs_clear_inode(struct inode *inode)
{
struct rpc_cred *cred = NFS_I(inode)->mm_cred;
struct nfs_inode *nfsi = NFS_I(inode);
struct rpc_cred *cred = nfsi->mm_cred;
if (cred)
put_rpccred(cred);
cred = nfsi->cache_access.cred;
if (cred)
put_rpccred(cred);
}
......@@ -722,6 +726,7 @@ __nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr)
NFS_ATTRTIMEO(inode) = NFS_MINATTRTIMEO(inode);
NFS_ATTRTIMEO_UPDATE(inode) = jiffies;
memset(NFS_COOKIEVERF(inode), 0, sizeof(NFS_COOKIEVERF(inode)));
NFS_I(inode)->cache_access.cred = NULL;
unlock_new_inode(inode);
} else
......@@ -1085,6 +1090,16 @@ __nfs_refresh_inode(struct inode *inode, struct nfs_fattr *fattr)
NFS_CACHE_ISIZE(inode) = new_size;
inode->i_size = new_isize;
if (inode->i_mode != fattr->mode ||
inode->i_uid != fattr->uid ||
inode->i_gid != fattr->gid) {
struct rpc_cred **cred = &NFS_I(inode)->cache_access.cred;
if (*cred) {
put_rpccred(*cred);
*cred = NULL;
}
}
inode->i_mode = fattr->mode;
inode->i_nlink = fattr->nlink;
inode->i_uid = fattr->uid;
......
......@@ -133,16 +133,22 @@ nfs3_proc_lookup(struct inode *dir, struct qstr *name,
}
static int
nfs3_proc_access(struct inode *inode, int mode, int ruid)
nfs3_proc_access(struct inode *inode, struct rpc_cred *cred, int mode)
{
struct nfs_fattr fattr;
struct nfs3_accessargs arg = {
fh: NFS_FH(inode),
.fh = NFS_FH(inode),
};
struct nfs3_accessres res = {
fattr: &fattr,
.fattr = &fattr,
};
struct rpc_message msg = {
.rpc_proc = NFS3PROC_ACCESS,
.rpc_argp = &arg,
.rpc_resp = &res,
.rpc_cred = cred
};
int status, flags;
int status;
dprintk("NFS call access\n");
fattr.valid = 0;
......@@ -160,8 +166,7 @@ nfs3_proc_access(struct inode *inode, int mode, int ruid)
if (mode & MAY_EXEC)
arg.access |= NFS3_ACCESS_EXECUTE;
}
flags = (ruid) ? RPC_CALL_REALUID : 0;
status = rpc_call(NFS_CLIENT(inode), NFS3PROC_ACCESS, &arg, &res, flags);
status = rpc_call_sync(NFS_CLIENT(inode), &msg, 0);
nfs_refresh_inode(inode, &fattr);
dprintk("NFS reply access\n");
......
......@@ -89,6 +89,16 @@
#ifdef __KERNEL__
/*
* NFSv3 Access mode cache
*/
struct nfs_access_cache {
unsigned long jiffies;
struct rpc_cred * cred;
int mask;
int err;
};
/*
* nfs fs inode data in memory
*/
......@@ -138,6 +148,8 @@ struct nfs_inode {
*/
unsigned long cache_mtime_jiffies;
struct nfs_access_cache cache_access;
/*
* This is the cookie verifier used for NFSv3 readdir
* operations
......
......@@ -300,7 +300,7 @@ struct nfs_rpc_ops {
struct iattr *);
int (*lookup) (struct inode *, struct qstr *,
struct nfs_fh *, struct nfs_fattr *);
int (*access) (struct inode *, int , int);
int (*access) (struct inode *, struct rpc_cred *, int);
int (*readlink)(struct inode *, struct page *);
int (*read) (struct inode *, struct rpc_cred *,
struct nfs_fattr *,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment