CRED: Inaugurate COW credentials
[safe/jmp/linux-2.6] / fs / nfsd / nfsfh.c
index 1eb771d..f0da7d9 100644 (file)
@@ -51,7 +51,7 @@ static int nfsd_acceptable(void *expv, struct dentry *dentry)
                /* make sure parents give x permission to user */
                int err;
                parent = dget_parent(tdentry);
-               err = permission(parent->d_inode, MAY_EXEC, NULL);
+               err = inode_permission(parent->d_inode, MAY_EXEC);
                if (err < 0) {
                        dput(parent);
                        break;
@@ -113,6 +113,152 @@ static __be32 nfsd_setuser_and_check_port(struct svc_rqst *rqstp,
 }
 
 /*
+ * Use the given filehandle to look up the corresponding export and
+ * dentry.  On success, the results are used to set fh_export and
+ * fh_dentry.
+ */
+static __be32 nfsd_set_fh_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp)
+{
+       struct knfsd_fh *fh = &fhp->fh_handle;
+       struct fid *fid = NULL, sfid;
+       struct svc_export *exp;
+       struct dentry *dentry;
+       int fileid_type;
+       int data_left = fh->fh_size/4;
+       __be32 error;
+
+       error = nfserr_stale;
+       if (rqstp->rq_vers > 2)
+               error = nfserr_badhandle;
+       if (rqstp->rq_vers == 4 && fh->fh_size == 0)
+               return nfserr_nofilehandle;
+
+       if (fh->fh_version == 1) {
+               int len;
+
+               if (--data_left < 0)
+                       return error;
+               if (fh->fh_auth_type != 0)
+                       return error;
+               len = key_len(fh->fh_fsid_type) / 4;
+               if (len == 0)
+                       return error;
+               if  (fh->fh_fsid_type == FSID_MAJOR_MINOR) {
+                       /* deprecated, convert to type 3 */
+                       len = key_len(FSID_ENCODE_DEV)/4;
+                       fh->fh_fsid_type = FSID_ENCODE_DEV;
+                       fh->fh_fsid[0] = new_encode_dev(MKDEV(ntohl(fh->fh_fsid[0]), ntohl(fh->fh_fsid[1])));
+                       fh->fh_fsid[1] = fh->fh_fsid[2];
+               }
+               data_left -= len;
+               if (data_left < 0)
+                       return error;
+               exp = rqst_exp_find(rqstp, fh->fh_fsid_type, fh->fh_auth);
+               fid = (struct fid *)(fh->fh_auth + len);
+       } else {
+               __u32 tfh[2];
+               dev_t xdev;
+               ino_t xino;
+
+               if (fh->fh_size != NFS_FHSIZE)
+                       return error;
+               /* assume old filehandle format */
+               xdev = old_decode_dev(fh->ofh_xdev);
+               xino = u32_to_ino_t(fh->ofh_xino);
+               mk_fsid(FSID_DEV, tfh, xdev, xino, 0, NULL);
+               exp = rqst_exp_find(rqstp, FSID_DEV, tfh);
+       }
+
+       error = nfserr_stale;
+       if (PTR_ERR(exp) == -ENOENT)
+               return error;
+
+       if (IS_ERR(exp))
+               return nfserrno(PTR_ERR(exp));
+
+       if (exp->ex_flags & NFSEXP_NOSUBTREECHECK) {
+               /* Elevate privileges so that the lack of 'r' or 'x'
+                * permission on some parent directory will
+                * not stop exportfs_decode_fh from being able
+                * to reconnect a directory into the dentry cache.
+                * The same problem can affect "SUBTREECHECK" exports,
+                * but as nfsd_acceptable depends on correct
+                * access control settings being in effect, we cannot
+                * fix that case easily.
+                */
+               struct cred *new = prepare_creds();
+               if (!new)
+                       return nfserrno(-ENOMEM);
+               new->cap_effective =
+                       cap_raise_nfsd_set(new->cap_effective,
+                                          new->cap_permitted);
+               put_cred(override_creds(new));
+               put_cred(new);
+       } else {
+               error = nfsd_setuser_and_check_port(rqstp, exp);
+               if (error)
+                       goto out;
+       }
+
+       /*
+        * Look up the dentry using the NFS file handle.
+        */
+       error = nfserr_stale;
+       if (rqstp->rq_vers > 2)
+               error = nfserr_badhandle;
+
+       if (fh->fh_version != 1) {
+               sfid.i32.ino = fh->ofh_ino;
+               sfid.i32.gen = fh->ofh_generation;
+               sfid.i32.parent_ino = fh->ofh_dirino;
+               fid = &sfid;
+               data_left = 3;
+               if (fh->ofh_dirino == 0)
+                       fileid_type = FILEID_INO32_GEN;
+               else
+                       fileid_type = FILEID_INO32_GEN_PARENT;
+       } else
+               fileid_type = fh->fh_fileid_type;
+
+       if (fileid_type == FILEID_ROOT)
+               dentry = dget(exp->ex_path.dentry);
+       else {
+               dentry = exportfs_decode_fh(exp->ex_path.mnt, fid,
+                               data_left, fileid_type,
+                               nfsd_acceptable, exp);
+       }
+       if (dentry == NULL)
+               goto out;
+       if (IS_ERR(dentry)) {
+               if (PTR_ERR(dentry) != -EINVAL)
+                       error = nfserrno(PTR_ERR(dentry));
+               goto out;
+       }
+
+       if (exp->ex_flags & NFSEXP_NOSUBTREECHECK) {
+               error = nfsd_setuser_and_check_port(rqstp, exp);
+               if (error) {
+                       dput(dentry);
+                       goto out;
+               }
+       }
+
+       if (S_ISDIR(dentry->d_inode->i_mode) &&
+                       (dentry->d_flags & DCACHE_DISCONNECTED)) {
+               printk("nfsd: find_fh_dentry returned a DISCONNECTED directory: %s/%s\n",
+                               dentry->d_parent->d_name.name, dentry->d_name.name);
+       }
+
+       fhp->fh_dentry = dentry;
+       fhp->fh_export = exp;
+       nfsd_nr_verified++;
+       return 0;
+out:
+       exp_put(exp);
+       return error;
+}
+
+/*
  * Perform sanity checks on the dentry in a client's file handle.
  *
  * Note that the file handle dentry may need to be freed even after
@@ -124,114 +270,18 @@ static __be32 nfsd_setuser_and_check_port(struct svc_rqst *rqstp,
 __be32
 fh_verify(struct svc_rqst *rqstp, struct svc_fh *fhp, int type, int access)
 {
-       struct knfsd_fh *fh = &fhp->fh_handle;
-       struct svc_export *exp = NULL;
+       struct svc_export *exp;
        struct dentry   *dentry;
-       __be32          error = 0;
+       __be32          error;
 
        dprintk("nfsd: fh_verify(%s)\n", SVCFH_fmt(fhp));
 
        if (!fhp->fh_dentry) {
-               struct fid *fid = NULL, sfid;
-               int fileid_type;
-               int data_left = fh->fh_size/4;
-
-               error = nfserr_stale;
-               if (rqstp->rq_vers > 2)
-                       error = nfserr_badhandle;
-               if (rqstp->rq_vers == 4 && fh->fh_size == 0)
-                       return nfserr_nofilehandle;
-
-               if (fh->fh_version == 1) {
-                       int len;
-                       if (--data_left<0) goto out;
-                       switch (fh->fh_auth_type) {
-                       case 0: break;
-                       default: goto out;
-                       }
-                       len = key_len(fh->fh_fsid_type) / 4;
-                       if (len == 0) goto out;
-                       if  (fh->fh_fsid_type == FSID_MAJOR_MINOR) {
-                               /* deprecated, convert to type 3 */
-                               len = key_len(FSID_ENCODE_DEV)/4;
-                               fh->fh_fsid_type = FSID_ENCODE_DEV;
-                               fh->fh_fsid[0] = new_encode_dev(MKDEV(ntohl(fh->fh_fsid[0]), ntohl(fh->fh_fsid[1])));
-                               fh->fh_fsid[1] = fh->fh_fsid[2];
-                       }
-                       if ((data_left -= len)<0) goto out;
-                       exp = rqst_exp_find(rqstp, fh->fh_fsid_type,
-                                           fh->fh_auth);
-                       fid = (struct fid *)(fh->fh_auth + len);
-               } else {
-                       __u32 tfh[2];
-                       dev_t xdev;
-                       ino_t xino;
-                       if (fh->fh_size != NFS_FHSIZE)
-                               goto out;
-                       /* assume old filehandle format */
-                       xdev = old_decode_dev(fh->ofh_xdev);
-                       xino = u32_to_ino_t(fh->ofh_xino);
-                       mk_fsid(FSID_DEV, tfh, xdev, xino, 0, NULL);
-                       exp = rqst_exp_find(rqstp, FSID_DEV, tfh);
-               }
-
-               error = nfserr_stale;
-               if (PTR_ERR(exp) == -ENOENT)
-                       goto out;
-
-               if (IS_ERR(exp)) {
-                       error = nfserrno(PTR_ERR(exp));
-                       goto out;
-               }
-
-               error = nfsd_setuser_and_check_port(rqstp, exp);
+               error = nfsd_set_fh_dentry(rqstp, fhp);
                if (error)
                        goto out;
-
-               /*
-                * Look up the dentry using the NFS file handle.
-                */
-               error = nfserr_stale;
-               if (rqstp->rq_vers > 2)
-                       error = nfserr_badhandle;
-
-               if (fh->fh_version != 1) {
-                       sfid.i32.ino = fh->ofh_ino;
-                       sfid.i32.gen = fh->ofh_generation;
-                       sfid.i32.parent_ino = fh->ofh_dirino;
-                       fid = &sfid;
-                       data_left = 3;
-                       if (fh->ofh_dirino == 0)
-                               fileid_type = FILEID_INO32_GEN;
-                       else
-                               fileid_type = FILEID_INO32_GEN_PARENT;
-               } else
-                       fileid_type = fh->fh_fileid_type;
-
-               if (fileid_type == FILEID_ROOT)
-                       dentry = dget(exp->ex_path.dentry);
-               else {
-                       dentry = exportfs_decode_fh(exp->ex_path.mnt, fid,
-                                       data_left, fileid_type,
-                                       nfsd_acceptable, exp);
-               }
-               if (dentry == NULL)
-                       goto out;
-               if (IS_ERR(dentry)) {
-                       if (PTR_ERR(dentry) != -EINVAL)
-                               error = nfserrno(PTR_ERR(dentry));
-                       goto out;
-               }
-
-               if (S_ISDIR(dentry->d_inode->i_mode) &&
-                   (dentry->d_flags & DCACHE_DISCONNECTED)) {
-                       printk("nfsd: find_fh_dentry returned a DISCONNECTED directory: %s/%s\n",
-                              dentry->d_parent->d_name.name, dentry->d_name.name);
-               }
-
-               fhp->fh_dentry = dentry;
-               fhp->fh_export = exp;
-               nfsd_nr_verified++;
+               dentry = fhp->fh_dentry;
+               exp = fhp->fh_export;
        } else {
                /*
                 * just rechecking permissions
@@ -252,24 +302,32 @@ fh_verify(struct svc_rqst *rqstp, struct svc_fh *fhp, int type, int access)
                if (error)
                        goto out;
        }
-       cache_get(&exp->h);
-
 
        error = nfsd_mode_check(rqstp, dentry->d_inode->i_mode, type);
        if (error)
                goto out;
 
-       if (!(access & MAY_LOCK)) {
-               /*
-                * pseudoflavor restrictions are not enforced on NLM,
-                * which clients virtually always use auth_sys for,
-                * even while using RPCSEC_GSS for NFS.
-                */
-               error = check_nfsd_access(exp, rqstp);
-               if (error)
-                       goto out;
-       }
+       /*
+        * pseudoflavor restrictions are not enforced on NLM,
+        * which clients virtually always use auth_sys for,
+        * even while using RPCSEC_GSS for NFS.
+        */
+       if (access & NFSD_MAY_LOCK)
+               goto skip_pseudoflavor_check;
+       /*
+        * Clients may expect to be able to use auth_sys during mount,
+        * even if they use gss for everything else; see section 2.3.2
+        * of rfc 2623.
+        */
+       if (access & NFSD_MAY_BYPASS_GSS_ON_ROOT
+                       && exp->ex_path.dentry == dentry)
+               goto skip_pseudoflavor_check;
+
+       error = check_nfsd_access(exp, rqstp);
+       if (error)
+               goto out;
 
+skip_pseudoflavor_check:
        /* Finally, check access permissions. */
        error = nfsd_permission(rqstp, exp, dentry, access);
 
@@ -281,8 +339,6 @@ fh_verify(struct svc_rqst *rqstp, struct svc_fh *fhp, int type, int access)
                        access, ntohl(error));
        }
 out:
-       if (exp && !IS_ERR(exp))
-               exp_put(exp);
        if (error == nfserr_stale)
                nfsdstats.fh_stale++;
        return error;