X-Git-Url: http://ftp.safe.ca/?p=safe%2Fjmp%2Flinux-2.6;a=blobdiff_plain;f=fs%2Fnfsd%2Fnfsfh.c;h=78d8ebf162caeb1cbf3b85f028b0375f4ee2c626;hp=8d2b49914843c91957b9c5b6cf1dad3b5ecb6b9e;hb=8e498751f2f36074ffa6fc7f0a9ec6e055b350e6;hpb=0ec757df9743025f14190d6034d8bd2bf37c2dd1 diff --git a/fs/nfsd/nfsfh.c b/fs/nfsd/nfsfh.c index 8d2b499..78d8ebf 100644 --- a/fs/nfsd/nfsfh.c +++ b/fs/nfsd/nfsfh.c @@ -22,13 +22,11 @@ #include #include #include +#include "auth.h" #define NFSDDBG_FACILITY NFSDDBG_FH -static int nfsd_nr_verified; -static int nfsd_nr_put; - /* * our acceptability function. * if NOSUBTREECHECK, accept anything @@ -46,11 +44,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 +56,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,165 +93,255 @@ 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)); +} + /* - * 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 - * an error return. - * - * This is only called at the start of an nfsproc call, so fhp points to - * a svc_fh which is all 0 except for the over-the-wire file handle. + * 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. */ -__be32 -fh_verify(struct svc_rqst *rqstp, struct svc_fh *fhp, int type, int access) +static __be32 nfsd_set_fh_dentry(struct svc_rqst *rqstp, struct svc_fh *fhp) { struct knfsd_fh *fh = &fhp->fh_handle; - struct svc_export *exp = NULL; - struct dentry *dentry; - __be32 error = 0; + struct fid *fid = NULL, sfid; + struct svc_export *exp; + struct dentry *dentry; + int fileid_type; + int data_left = fh->fh_size/4; + __be32 error; - dprintk("nfsd: fh_verify(%s)\n", SVCFH_fmt(fhp)); + error = nfserr_stale; + if (rqstp->rq_vers > 2) + error = nfserr_badhandle; + if (rqstp->rq_vers == 4 && fh->fh_size == 0) + return nfserr_nofilehandle; - 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); + 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) + 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; + } - if (IS_ERR(exp)) { - error = nfserrno(PTR_ERR(exp)); - 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; + } - /* 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))); + if (exp->ex_flags & NFSEXP_NOSUBTREECHECK) { + error = nfsd_setuser_and_check_port(rqstp, exp); + if (error) { + dput(dentry); goto out; } + } - /* Set user creds for this exportpoint */ - error = nfserrno(nfsd_setuser(rqstp, exp)); - if (error) - 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); + } - /* - * 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; - } + fhp->fh_dentry = dentry; + fhp->fh_export = exp; + return 0; +out: + exp_put(exp); + return error; +} - 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); - } +/** + * fh_verify - filehandle lookup and access checking + * @rqstp: pointer to current rpc request + * @fhp: filehandle to be verified + * @type: expected type of object pointed to by filehandle + * @access: type of access needed to object + * + * Look up a dentry from the on-the-wire filehandle, check the client's + * access to the export, and set the current task's credentials. + * + * Regardless of success or failure of fh_verify(), fh_put() should be + * called on @fhp when the caller is finished with the filehandle. + * + * fh_verify() may be called multiple times on a given filehandle, for + * example, when processing an NFSv4 compound. The first call will look + * up a dentry using the on-the-wire filehandle. Subsequent calls will + * skip the lookup and just perform the other checks and possibly change + * the current task's credentials. + * + * @type specifies the type of object expected using one of the S_IF* + * constants defined in include/linux/stat.h. The caller may use zero + * to indicate that it doesn't care, or a negative integer to indicate + * that it expects something not of the given type. + * + * @access is formed from the NFSD_MAY_* constants defined in + * include/linux/nfsd/nfsd.h. + */ +__be32 +fh_verify(struct svc_rqst *rqstp, struct svc_fh *fhp, int type, int access) +{ + struct svc_export *exp; + struct dentry *dentry; + __be32 error; + + dprintk("nfsd: fh_verify(%s)\n", SVCFH_fmt(fhp)); - fhp->fh_dentry = dentry; - fhp->fh_export = exp; - nfsd_nr_verified++; + if (!fhp->fh_dentry) { + error = nfsd_set_fh_dentry(rqstp, fhp); + if (error) + goto out; + 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(rqstp, exp, dentry, access); @@ -265,8 +353,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 +366,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; + } } /* @@ -306,6 +397,40 @@ static inline void _fh_update_old(struct dentry *dentry, fh->ofh_dirino = 0; } +static bool is_root_export(struct svc_export *exp) +{ + return exp->ex_path.dentry == exp->ex_path.dentry->d_sb->s_root; +} + +static struct super_block *exp_sb(struct svc_export *exp) +{ + return exp->ex_path.dentry->d_inode->i_sb; +} + +static bool fsid_type_ok_for_exp(u8 fsid_type, struct svc_export *exp) +{ + switch (fsid_type) { + case FSID_DEV: + if (!old_valid_dev(exp_sb(exp)->s_dev)) + return 0; + /* FALL THROUGH */ + case FSID_MAJOR_MINOR: + case FSID_ENCODE_DEV: + return exp_sb(exp)->s_type->fs_flags & FS_REQUIRES_DEV; + case FSID_NUM: + return exp->ex_flags & NFSEXP_FSID; + case FSID_UUID8: + case FSID_UUID16: + if (!is_root_export(exp)) + return 0; + /* fall through */ + case FSID_UUID4_INUM: + case FSID_UUID16_INUM: + return exp->ex_uuid != NULL; + } + return 1; +} + __be32 fh_compose(struct svc_fh *fhp, struct svc_export *exp, struct dentry *dentry, struct svc_fh *ref_fh) @@ -323,12 +448,11 @@ 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_sb(exp)->s_dev; 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)); @@ -356,54 +480,29 @@ fh_compose(struct svc_fh *fhp, struct svc_export *exp, struct dentry *dentry, goto retry; } - /* Need to check that this type works for this - * export point. As the fsid -> filesystem mapping - * was guided by user-space, there is no guarantee - * that the filesystem actually supports that fsid - * type. If it doesn't we loop around again without - * ref_fh set. + /* + * As the fsid -> filesystem mapping was guided by + * user-space, there is no guarantee that the filesystem + * actually supports that fsid type. If it doesn't we + * loop around again without ref_fh set. */ - switch(fsid_type) { - case FSID_DEV: - if (!old_valid_dev(ex_dev)) - goto retry; - /* FALL THROUGH */ - case FSID_MAJOR_MINOR: - case FSID_ENCODE_DEV: - if (!(exp->ex_dentry->d_inode->i_sb->s_type->fs_flags - & FS_REQUIRES_DEV)) - goto retry; - break; - case FSID_NUM: - if (! (exp->ex_flags & NFSEXP_FSID)) - goto retry; - break; - case FSID_UUID8: - case FSID_UUID16: - if (!root_export) - goto retry; - /* fall through */ - case FSID_UUID4_INUM: - case FSID_UUID16_INUM: - if (exp->ex_uuid == NULL) - goto retry; - break; - } + if (!fsid_type_ok_for_exp(fsid_type, exp)) + goto retry; + } else if (exp->ex_flags & NFSEXP_FSID) { + fsid_type = FSID_NUM; } else if (exp->ex_uuid) { if (fhp->fh_maxsize >= 64) { - if (root_export) + if (is_root_export(exp)) fsid_type = FSID_UUID16; else fsid_type = FSID_UUID16_INUM; } else { - if (root_export) + if (is_root_export(exp)) fsid_type = FSID_UUID8; else fsid_type = FSID_UUID4_INUM; } - } else if (exp->ex_flags & NFSEXP_FSID) - fsid_type = FSID_NUM; - else if (!old_valid_dev(ex_dev)) + } else if (!old_valid_dev(ex_dev)) /* for newer device numbers, we must use a newer fsid format */ fsid_type = FSID_ENCODE_DEV; else @@ -433,7 +532,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,24 +543,19 @@ 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; } - nfsd_nr_verified++; return 0; } @@ -473,7 +567,6 @@ __be32 fh_update(struct svc_fh *fhp) { struct dentry *dentry; - __u32 *datap; if (!fhp->fh_dentry) goto out_bad; @@ -484,15 +577,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; } @@ -524,7 +612,6 @@ fh_put(struct svc_fh *fhp) fhp->fh_pre_saved = 0; fhp->fh_post_saved = 0; #endif - nfsd_nr_put++; } if (exp) { cache_put(&exp->h, &svc_export_cache); @@ -560,13 +647,22 @@ enum fsid_source fsid_source(struct svc_fh *fhp) case FSID_DEV: case FSID_ENCODE_DEV: case FSID_MAJOR_MINOR: - return FSIDSOURCE_DEV; + if (exp_sb(fhp->fh_export)->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; }