nfsd4: check for negative dentry before use in nfsv4 readdir
After2f9092e102
"Fix i_mutex vs. readdir handling in nfsd" (and14f7dd63
"Copy XFS readdir hack into nfsd code"), an entry may be removed between the first mutex_unlock and the second mutex_lock. In this case, lookup_one_len() will return a negative dentry. Check for this case to avoid a NULL dereference. Signed-off-by: J. Bruce Fields <bfields@citi.umich.edu> Reviewed-by: J. R. Okajima <hooanon05@yahoo.co.jp> Cc: stable@kernel.org
This commit is contained in:
@@ -2214,6 +2214,15 @@ nfsd4_encode_dirent_fattr(struct nfsd4_readdir *cd,
|
|||||||
dentry = lookup_one_len(name, cd->rd_fhp->fh_dentry, namlen);
|
dentry = lookup_one_len(name, cd->rd_fhp->fh_dentry, namlen);
|
||||||
if (IS_ERR(dentry))
|
if (IS_ERR(dentry))
|
||||||
return nfserrno(PTR_ERR(dentry));
|
return nfserrno(PTR_ERR(dentry));
|
||||||
|
if (!dentry->d_inode) {
|
||||||
|
/*
|
||||||
|
* nfsd_buffered_readdir drops the i_mutex between
|
||||||
|
* readdir and calling this callback, leaving a window
|
||||||
|
* where this directory entry could have gone away.
|
||||||
|
*/
|
||||||
|
dput(dentry);
|
||||||
|
return nfserr_noent;
|
||||||
|
}
|
||||||
|
|
||||||
exp_get(exp);
|
exp_get(exp);
|
||||||
/*
|
/*
|
||||||
@@ -2276,6 +2285,7 @@ nfsd4_encode_dirent(void *ccdv, const char *name, int namlen,
|
|||||||
struct nfsd4_readdir *cd = container_of(ccd, struct nfsd4_readdir, common);
|
struct nfsd4_readdir *cd = container_of(ccd, struct nfsd4_readdir, common);
|
||||||
int buflen;
|
int buflen;
|
||||||
__be32 *p = cd->buffer;
|
__be32 *p = cd->buffer;
|
||||||
|
__be32 *cookiep;
|
||||||
__be32 nfserr = nfserr_toosmall;
|
__be32 nfserr = nfserr_toosmall;
|
||||||
|
|
||||||
/* In nfsv4, "." and ".." never make it onto the wire.. */
|
/* In nfsv4, "." and ".." never make it onto the wire.. */
|
||||||
@@ -2292,7 +2302,7 @@ nfsd4_encode_dirent(void *ccdv, const char *name, int namlen,
|
|||||||
goto fail;
|
goto fail;
|
||||||
|
|
||||||
*p++ = xdr_one; /* mark entry present */
|
*p++ = xdr_one; /* mark entry present */
|
||||||
cd->offset = p; /* remember pointer */
|
cookiep = p;
|
||||||
p = xdr_encode_hyper(p, NFS_OFFSET_MAX); /* offset of next entry */
|
p = xdr_encode_hyper(p, NFS_OFFSET_MAX); /* offset of next entry */
|
||||||
p = xdr_encode_array(p, name, namlen); /* name length & name */
|
p = xdr_encode_array(p, name, namlen); /* name length & name */
|
||||||
|
|
||||||
@@ -2306,6 +2316,8 @@ nfsd4_encode_dirent(void *ccdv, const char *name, int namlen,
|
|||||||
goto fail;
|
goto fail;
|
||||||
case nfserr_dropit:
|
case nfserr_dropit:
|
||||||
goto fail;
|
goto fail;
|
||||||
|
case nfserr_noent:
|
||||||
|
goto skip_entry;
|
||||||
default:
|
default:
|
||||||
/*
|
/*
|
||||||
* If the client requested the RDATTR_ERROR attribute,
|
* If the client requested the RDATTR_ERROR attribute,
|
||||||
@@ -2324,6 +2336,8 @@ nfsd4_encode_dirent(void *ccdv, const char *name, int namlen,
|
|||||||
}
|
}
|
||||||
cd->buflen -= (p - cd->buffer);
|
cd->buflen -= (p - cd->buffer);
|
||||||
cd->buffer = p;
|
cd->buffer = p;
|
||||||
|
cd->offset = cookiep;
|
||||||
|
skip_entry:
|
||||||
cd->common.err = nfs_ok;
|
cd->common.err = nfs_ok;
|
||||||
return 0;
|
return 0;
|
||||||
fail:
|
fail:
|
||||||
|
Reference in New Issue
Block a user