nfsd: permit unauthenticated stat of export root
[safe/jmp/linux-2.6] / fs / nfsd / nfsfh.c
index d5fe392..cd25d91 100644 (file)
@@ -22,6 +22,7 @@
 #include <linux/sunrpc/svc.h>
 #include <linux/sunrpc/svcauth_gss.h>
 #include <linux/nfsd/nfsd.h>
+#include "auth.h"
 
 #define NFSDDBG_FACILITY               NFSDDBG_FH
 
@@ -46,11 +47,11 @@ static int nfsd_acceptable(void *expv, struct dentry *dentry)
                return 1;
 
        tdentry = dget(dentry);
-       while (tdentry != exp->ex_dentry && ! IS_ROOT(tdentry)) {
+       while (tdentry != exp->ex_path.dentry && !IS_ROOT(tdentry)) {
                /* 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;
@@ -58,9 +59,9 @@ static int nfsd_acceptable(void *expv, struct dentry *dentry)
                dput(tdentry);
                tdentry = parent;
        }
-       if (tdentry != exp->ex_dentry)
+       if (tdentry != exp->ex_path.dentry)
                dprintk("nfsd_acceptable failed at %p %s\n", tdentry, tdentry->d_name.name);
-       rv = (tdentry == exp->ex_dentry);
+       rv = (tdentry == exp->ex_path.dentry);
        dput(tdentry);
        return rv;
 }
@@ -95,6 +96,163 @@ nfsd_mode_check(struct svc_rqst *rqstp, umode_t mode, int type)
        return 0;
 }
 
+static __be32 nfsd_setuser_and_check_port(struct svc_rqst *rqstp,
+                                         struct svc_export *exp)
+{
+       /* Check if the request originated from a secure port. */
+       if (!rqstp->rq_secure && EX_SECURE(exp)) {
+               RPC_IFDEBUG(char buf[RPC_MAX_ADDRBUFLEN]);
+               dprintk(KERN_WARNING
+                      "nfsd: request from insecure port %s!\n",
+                      svc_print_addr(rqstp, buf, sizeof(buf)));
+               return nfserr_perm;
+       }
+
+       /* Set user creds for this exportpoint */
+       return nfserrno(nfsd_setuser(rqstp, exp));
+}
+
+/*
+ * 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.
+                */
+               current->cap_effective =
+                       cap_raise_nfsd_set(current->cap_effective,
+                                          current->cap_permitted);
+       } 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.
  *
@@ -107,155 +265,66 @@ nfsd_mode_check(struct svc_rqst *rqstp, umode_t mode, int type)
 __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) {
-               __u32 *datap=NULL;
-               __u32 tfh[3];           /* filehandle fragment for oldstyle filehandles */
-               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;
-                       datap = fh->fh_auth;
-                       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, datap);
-                       datap += len;
-               } else {
-                       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;
-               }
-
-               /* Check if the request originated from a secure port. */
-               error = nfserr_perm;
-               if (!rqstp->rq_secure && EX_SECURE(exp)) {
-                       char buf[RPC_MAX_ADDRBUFLEN];
-                       printk(KERN_WARNING
-                              "nfsd: request from insecure port %s!\n",
-                              svc_print_addr(rqstp, buf, sizeof(buf)));
-                       goto out;
-               }
-
-               /* Set user creds for this exportpoint */
-               error = nfserrno(nfsd_setuser(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) {
-                       tfh[0] = fh->ofh_ino;
-                       tfh[1] = fh->ofh_generation;
-                       tfh[2] = fh->ofh_dirino;
-                       datap = tfh;
-                       data_left = 3;
-                       if (fh->ofh_dirino == 0)
-                               fileid_type = 1;
-                       else
-                               fileid_type = 2;
-               } else
-                       fileid_type = fh->fh_fileid_type;
-
-               if (fileid_type == 0)
-                       dentry = dget(exp->ex_dentry);
-               else {
-                       dentry = exportfs_decode_fh(exp->ex_mnt, datap,
-                                       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
-                * (e.g. nfsproc_create calls fh_verify, then nfsd_create does as well)
+               /*
+                * just rechecking permissions
+                * (e.g. nfsproc_create calls fh_verify, then nfsd_create
+                * does as well)
                 */
                dprintk("nfsd: fh_verify - just checking\n");
                dentry = fhp->fh_dentry;
                exp = fhp->fh_export;
-               /* Set user creds for this exportpoint; necessary even
+               /*
+                * Set user creds for this exportpoint; necessary even
                 * in the "just checking" case because this may be a
                 * filehandle that was created by fh_compose, and that
                 * is about to be used in another nfsv4 compound
-                * operation */
-               error = nfserrno(nfsd_setuser(rqstp, exp));
+                * operation.
+                */
+               error = nfsd_setuser_and_check_port(rqstp, exp);
                if (error)
                        goto out;
        }
-       cache_get(&exp->h);
-
 
        error = nfsd_mode_check(rqstp, dentry->d_inode->i_mode, type);
        if (error)
                goto out;
 
-       /* Check security flavor */
+       /*
+        * 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(exp, dentry, access);
+       error = nfsd_permission(rqstp, exp, dentry, access);
 
        if (error) {
                dprintk("fh_verify: %s/%s permission failure, "
@@ -265,8 +334,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;
@@ -280,16 +347,21 @@ out:
  * an inode.  In this case a call to fh_update should be made
  * before the fh goes out on the wire ...
  */
-static inline int _fh_update(struct dentry *dentry, struct svc_export *exp,
-                            __u32 *datap, int *maxsize)
+static void _fh_update(struct svc_fh *fhp, struct svc_export *exp,
+               struct dentry *dentry)
 {
-       if (dentry == exp->ex_dentry) {
-               *maxsize = 0;
-               return 0;
-       }
+       if (dentry != exp->ex_path.dentry) {
+               struct fid *fid = (struct fid *)
+                       (fhp->fh_handle.fh_auth + fhp->fh_handle.fh_size/4 - 1);
+               int maxsize = (fhp->fh_maxsize - fhp->fh_handle.fh_size)/4;
+               int subtreecheck = !(exp->ex_flags & NFSEXP_NOSUBTREECHECK);
 
-       return exportfs_encode_fh(dentry, datap, maxsize,
-                         !(exp->ex_flags & NFSEXP_NOSUBTREECHECK));
+               fhp->fh_handle.fh_fileid_type =
+                       exportfs_encode_fh(dentry, fid, &maxsize, subtreecheck);
+               fhp->fh_handle.fh_size += maxsize * 4;
+       } else {
+               fhp->fh_handle.fh_fileid_type = FILEID_ROOT;
+       }
 }
 
 /*
@@ -323,12 +395,12 @@ fh_compose(struct svc_fh *fhp, struct svc_export *exp, struct dentry *dentry,
        struct inode * inode = dentry->d_inode;
        struct dentry *parent = dentry->d_parent;
        __u32 *datap;
-       dev_t ex_dev = exp->ex_dentry->d_inode->i_sb->s_dev;
-       int root_export = (exp->ex_dentry == exp->ex_dentry->d_sb->s_root);
+       dev_t ex_dev = exp->ex_path.dentry->d_inode->i_sb->s_dev;
+       int root_export = (exp->ex_path.dentry == exp->ex_path.dentry->d_sb->s_root);
 
        dprintk("nfsd: fh_compose(exp %02x:%02x/%ld %s/%s, ino=%ld)\n",
                MAJOR(ex_dev), MINOR(ex_dev),
-               (long) exp->ex_dentry->d_inode->i_ino,
+               (long) exp->ex_path.dentry->d_inode->i_ino,
                parent->d_name.name, dentry->d_name.name,
                (inode ? inode->i_ino : 0));
 
@@ -370,7 +442,7 @@ fh_compose(struct svc_fh *fhp, struct svc_export *exp, struct dentry *dentry,
                        /* FALL THROUGH */
                case FSID_MAJOR_MINOR:
                case FSID_ENCODE_DEV:
-                       if (!(exp->ex_dentry->d_inode->i_sb->s_type->fs_flags
+                       if (!(exp->ex_path.dentry->d_inode->i_sb->s_type->fs_flags
                              & FS_REQUIRES_DEV))
                                goto retry;
                        break;
@@ -433,7 +505,7 @@ fh_compose(struct svc_fh *fhp, struct svc_export *exp, struct dentry *dentry,
                fhp->fh_handle.ofh_dev =  old_encode_dev(ex_dev);
                fhp->fh_handle.ofh_xdev = fhp->fh_handle.ofh_dev;
                fhp->fh_handle.ofh_xino =
-                       ino_t_to_u32(exp->ex_dentry->d_inode->i_ino);
+                       ino_t_to_u32(exp->ex_path.dentry->d_inode->i_ino);
                fhp->fh_handle.ofh_dirino = ino_t_to_u32(parent_ino(dentry));
                if (inode)
                        _fh_update_old(dentry, exp, &fhp->fh_handle);
@@ -444,19 +516,15 @@ fh_compose(struct svc_fh *fhp, struct svc_export *exp, struct dentry *dentry,
                datap = fhp->fh_handle.fh_auth+0;
                fhp->fh_handle.fh_fsid_type = fsid_type;
                mk_fsid(fsid_type, datap, ex_dev,
-                       exp->ex_dentry->d_inode->i_ino,
+                       exp->ex_path.dentry->d_inode->i_ino,
                        exp->ex_fsid, exp->ex_uuid);
 
                len = key_len(fsid_type);
                datap += len/4;
                fhp->fh_handle.fh_size = 4 + len;
 
-               if (inode) {
-                       int size = (fhp->fh_maxsize-len-4)/4;
-                       fhp->fh_handle.fh_fileid_type =
-                               _fh_update(dentry, exp, datap, &size);
-                       fhp->fh_handle.fh_size += size*4;
-               }
+               if (inode)
+                       _fh_update(fhp, exp, dentry);
                if (fhp->fh_handle.fh_fileid_type == 255)
                        return nfserr_opnotsupp;
        }
@@ -473,7 +541,6 @@ __be32
 fh_update(struct svc_fh *fhp)
 {
        struct dentry *dentry;
-       __u32 *datap;
 
        if (!fhp->fh_dentry)
                goto out_bad;
@@ -484,15 +551,10 @@ fh_update(struct svc_fh *fhp)
        if (fhp->fh_handle.fh_version != 1) {
                _fh_update_old(dentry, fhp->fh_export, &fhp->fh_handle);
        } else {
-               int size;
-               if (fhp->fh_handle.fh_fileid_type != 0)
+               if (fhp->fh_handle.fh_fileid_type != FILEID_ROOT)
                        goto out;
-               datap = fhp->fh_handle.fh_auth+
-                       fhp->fh_handle.fh_size/4 -1;
-               size = (fhp->fh_maxsize - fhp->fh_handle.fh_size)/4;
-               fhp->fh_handle.fh_fileid_type =
-                       _fh_update(dentry, fhp->fh_export, datap, &size);
-               fhp->fh_handle.fh_size += size*4;
+
+               _fh_update(fhp, fhp->fh_export, dentry);
                if (fhp->fh_handle.fh_fileid_type == 255)
                        return nfserr_opnotsupp;
        }
@@ -560,13 +622,23 @@ enum fsid_source fsid_source(struct svc_fh *fhp)
        case FSID_DEV:
        case FSID_ENCODE_DEV:
        case FSID_MAJOR_MINOR:
-               return FSIDSOURCE_DEV;
+               if (fhp->fh_export->ex_path.dentry->d_inode->i_sb->s_type->fs_flags
+                   & FS_REQUIRES_DEV)
+                       return FSIDSOURCE_DEV;
+               break;
        case FSID_NUM:
-               return FSIDSOURCE_FSID;
-       default:
                if (fhp->fh_export->ex_flags & NFSEXP_FSID)
                        return FSIDSOURCE_FSID;
-               else
-                       return FSIDSOURCE_UUID;
+               break;
+       default:
+               break;
        }
+       /* either a UUID type filehandle, or the filehandle doesn't
+        * match the export.
+        */
+       if (fhp->fh_export->ex_flags & NFSEXP_FSID)
+               return FSIDSOURCE_FSID;
+       if (fhp->fh_export->ex_uuid)
+               return FSIDSOURCE_UUID;
+       return FSIDSOURCE_DEV;
 }