[PATCH v3 02/17] nfsd: correctly handle CREATE of mounted-on files

NeilBrown <[email protected]>
Newsgroups gmane.linux.nfs
Message-ID <[email protected]>
From: NeilBrown <neil-+NVA1uvv1dVBDLzU/[email protected]>

Linux allows a file (non-directory) to be mounted on a file.  nfsd
mostly supports this if the crossmnt option is in effect.  However if
CREATE is used on an existing mounted-on file, the filehandle for the
underlying file is returns.  The client will then continue to use that
filehandle.

So
  cat /mnt/file
will show the contents of the mounted file as expected, but if
the dcache is flushed with "drop_caches" or similar, then
  >> /mnt/file
  cat /mnt/file
will show the mounted-on file.

For exclusive or checked creates this is not a problem as the creation
will fail no matter which file is seen. For unchecked creates we need to
see if the name is in the dcache, and if it is mounted.  If so, we
simply provide that filehandle, possibly truncating.

Signed-off-by: NeilBrown <neil-+NVA1uvv1dVBDLzU/[email protected]>
---
 fs/nfsd/nfs3proc.c | 28 ++++++++++++++++++++++++++++
 fs/nfsd/nfs4proc.c | 30 ++++++++++++++++++++++++++++++
 fs/nfsd/nfsproc.c  | 24 +++++++++++++++++++++++-
 3 files changed, 81 insertions(+), 1 deletion(-)

diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index bbaef884f893..20eaf56fa9e7 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -303,6 +303,34 @@ nfsd3_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
 	parent = fhp->fh_dentry;
 	inode = d_inode(parent);
 
+	if (argp->createmode == NFS3_CREATE_UNCHECKED) {
+		/*
+		 * If name is already in dcache we need to check for mountpoints
+		 */
+		child = try_lookup_noperm(&QSTR_LEN(argp->name,
+						    argp->len),
+					  parent);
+		if (child && !IS_ERR(child) && d_is_reg(child) &&
+		    unlikely(nfsd_mountpoint(child, fhp->fh_export))) {
+			struct svc_export *exp = exp_get(fhp->fh_export);
+			if (nfsd_cross_mnt(rqstp, &child, &exp) == 0) {
+				status = check_nfsd_access(exp, rqstp, false);
+				if (status == nfs_ok)
+					status = fh_compose(resfhp, exp,
+							    child, fhp);
+				if (status == nfs_ok)
+					status = nfsd_create_setattr(
+						rqstp, fhp, resfhp, &attrs);
+				dput(child);
+				exp_put(exp);
+				return status;
+			}
+			exp_put(exp);
+		}
+		if (!IS_ERR(child))
+			dput(child);
+	}
+
 	host_err = fh_want_write(fhp);
 	if (host_err)
 		return nfserrno(host_err);
diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c
index ca9460e97e2b..9a8c1e37cc0f 100644
--- a/fs/nfsd/nfs4proc.c
+++ b/fs/nfsd/nfs4proc.c
@@ -270,6 +270,36 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
 	parent = fhp->fh_dentry;
 	inode = d_inode(parent);
 
+	if (open->op_createmode == NFS4_CREATE_UNCHECKED) {
+		/*
+		 * If name is already in dcache we need to check for mountpoints
+		 */
+		child = try_lookup_noperm(&QSTR_LEN(open->op_fname,
+						    open->op_fnamelen),
+					  parent);
+		if (child && !IS_ERR(child) && d_is_reg(child) &&
+		    unlikely(nfsd_mountpoint(child, fhp->fh_export))) {
+			struct svc_export *exp = exp_get(fhp->fh_export);
+			if (nfsd_cross_mnt(rqstp, &child, &exp) == 0) {
+				status = check_nfsd_access(exp, rqstp, false);
+				if (status == nfs_ok)
+					status = fh_compose(resfhp, exp,
+							    child, fhp);
+				if (status == nfs_ok)
+					status = fh_fill_both_attrs(fhp);
+				open->op_truncate =
+					(iap->ia_valid & ATTR_SIZE) &&
+					!iap->ia_size;
+				dput(child);
+				exp_put(exp);
+				return status;
+			}
+			exp_put(exp);
+		}
+		if (!IS_ERR(child))
+			dput(child);
+	}
+
 	host_err = fh_want_write(fhp);
 	if (host_err)
 		return nfserrno(host_err);
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c
index f60043632575..549eed8f2c19 100644
--- a/fs/nfsd/nfsproc.c
+++ b/fs/nfsd/nfsproc.c
@@ -302,11 +302,34 @@ nfsd_proc_create(struct svc_rqst *rqstp)
 	if (resp->status != nfs_ok)
 		goto done; /* must fh_put dirfhp even on error */
 
+	fh_init(newfhp, NFS_FHSIZE);
+
 	/* Check for NFSD_MAY_WRITE in nfsd_create if necessary */
 
 	resp->status = nfserr_exist;
 	if (name_is_dot_dotdot(argp->name, argp->len))
 		goto done;
+
+	/*
+	 * If name is already in dcache we need to check for mountpoints
+	 */
+	dchild = try_lookup_noperm(&QSTR_LEN(argp->name, argp->len),
+				   dirfhp->fh_export);
+	if (dchild && !IS_ERR(dchild) && d_is_reg(child) &&
+	    unlikely(nfsd_mountpoint(dchild, fhp->fh_export))) {
+		struct svc_export *exp = fhp->fh_export;
+		if (nfsd_cross_mnt(rqstp, &dchild, &exp) == 0 &&
+		    d_isreg(dchild)) {
+			resp->status = check_nfsd_access(exp, rqstp, false);
+			if (resp->status == nfs_ok)
+				resp->status = fh_compose(newfhp, dirfhp->fh_export,
+							  dchild, dirfhp);
+			goto done;
+		}
+	}
+	if (!IS_ERR(dchild))
+		dput(dchild);
+
 	hosterr = fh_want_write(dirfhp);
 	if (hosterr) {
 		resp->status = nfserrno(hosterr);
@@ -319,7 +342,6 @@ nfsd_proc_create(struct svc_rqst *rqstp)
 		resp->status = nfserrno(PTR_ERR(dchild));
 		goto out_write;
 	}
-	fh_init(newfhp, NFS_FHSIZE);
 	resp->status = fh_compose(newfhp, dirfhp->fh_export, dchild, dirfhp);
 	if (!resp->status && d_really_is_negative(dchild))
 		resp->status = nfserr_noent;
-- 
2.50.0.107.gf914562f5916.dirty
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.