diff options
Diffstat (limited to 'fs/nfsd')
-rw-r--r-- | fs/nfsd/auth.c | 18 | ||||
-rw-r--r-- | fs/nfsd/export.c | 204 | ||||
-rw-r--r-- | fs/nfsd/nfsctl.c | 30 | ||||
-rw-r--r-- | fs/nfsd/nfsfh.c | 60 | ||||
-rw-r--r-- | fs/nfsd/nfssvc.c | 2 | ||||
-rw-r--r-- | fs/nfsd/vfs.c | 40 |
6 files changed, 273 insertions, 81 deletions
diff --git a/fs/nfsd/auth.c b/fs/nfsd/auth.c index c2346d458..a2f80bea7 100644 --- a/fs/nfsd/auth.c +++ b/fs/nfsd/auth.c @@ -41,7 +41,21 @@ nfsd_setuser(struct svc_rqst *rqstp, struct svc_export *exp) current->fsgid = cred->cr_gid; else current->fsgid = exp->ex_anon_gid; - for (i = 0; i < NGROUPS; i++) - current->groups[i] = cred->cr_groups[i]; + for (i = 0; i < NGROUPS; i++) { + gid_t group = cred->cr_groups[i]; + if (group == (gid_t) NOGROUP) + break; + current->groups[i] = group; + } + current->ngroups = i; + + if ((cred->cr_uid)) { + cap_lower(current->cap_effective, CAP_DAC_OVERRIDE); + cap_lower(current->cap_effective, CAP_DAC_READ_SEARCH); + } else { + cap_raise(current->cap_effective, CAP_DAC_OVERRIDE); + cap_raise(current->cap_effective, CAP_DAC_READ_SEARCH); + } + rqstp->rq_userset = 1; } diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c index e30953bb2..81bd84bcf 100644 --- a/fs/nfsd/export.c +++ b/fs/nfsd/export.c @@ -33,7 +33,10 @@ typedef struct svc_client svc_client; typedef struct svc_export svc_export; static svc_export * exp_find(svc_client *clp, kdev_t dev); -static svc_export * exp_parent(svc_client *clp, kdev_t dev); +static svc_export * exp_parent(svc_client *clp, kdev_t dev, + struct dentry *dentry); +static svc_export * exp_child(svc_client *clp, kdev_t dev, + struct dentry *dentry); static void exp_unexport_all(svc_client *clp); static void exp_do_unexport(svc_export *unexp); static svc_client * exp_getclientbyname(char *name); @@ -90,8 +93,16 @@ exp_get(svc_client *clp, kdev_t dev, ino_t ino) if (!clp) return NULL; - exp = exp_find(clp, dev); - return (exp && exp->ex_ino == ino)? exp : NULL; + + exp = clp->cl_export[EXPORT_HASH(dev)]; + if (exp) + do { + if (exp->ex_ino == ino && exp->ex_dev == dev) + goto out; + } while (NULL != (exp = exp->ex_next)); + exp = NULL; +out: + return exp; } /* @@ -126,22 +137,80 @@ nfsd_parentdev(kdev_t *devp) } /* - * Find the parent export entry for a given fs. This function is used - * only by the export syscall to keep the export tree consistent. + * Find the export entry for a given dentry. <gam3@acm.org> */ static svc_export * -exp_parent(svc_client *clp, kdev_t dev) +exp_parent(svc_client *clp, kdev_t dev, struct dentry *dentry) { - svc_export *exp; + svc_export *exp; kdev_t xdev = dev; + struct dentry *xdentry = dentry; + struct dentry *ndentry = NULL; + + if (clp == NULL || dentry == NULL) + return NULL; do { - exp = exp_find(clp, xdev); - if (exp) - return exp; - } while (nfsd_parentdev(&xdev)); + xdev = dev; + do { + exp = clp->cl_export[EXPORT_HASH(xdev)]; + if (exp) + do { + ndentry = exp->ex_dentry; + if (ndentry == xdentry) { +#ifdef NFSD_PARANOIA +if (dev == xdev) + dprintk("nfsd: exp_parent submount over mount.\n"); +else + dprintk("nfsd: exp_parent found.\n"); +#endif + goto out; + } + } while (NULL != (exp = exp->ex_next)); + } while (nfsd_parentdev(&xdev)); + if (xdentry == xdentry->d_parent) { + break; + } + } while ((xdentry = xdentry->d_parent)); + exp = NULL; +out: + return exp; +} - return NULL; +/* + * Find the child export entry for a given fs. This function is used + * only by the export syscall to keep the export tree consistent. + * <gam3@acm.org> + */ +static svc_export * +exp_child(svc_client *clp, kdev_t dev, struct dentry *dentry) +{ + svc_export *exp; + struct dentry *xdentry = dentry; + struct dentry *ndentry = NULL; + + if (clp == NULL || dentry == NULL) + return NULL; + + exp = clp->cl_export[EXPORT_HASH(dev)]; + if (exp) + do { + ndentry = exp->ex_dentry; + if (ndentry) + while ((ndentry = ndentry->d_parent)) { + if (ndentry == xdentry) { +#ifdef NFSD_PARANOIA +dprintk("nfsd: exp_child mount under submount.\n"); +#endif + goto out; + } + if (ndentry == ndentry->d_parent) + break; + } + } while (NULL != (exp = exp->ex_next)); + exp = NULL; +out: + return exp; } /* @@ -160,9 +229,10 @@ exp_export(struct nfsctl_export *nxp) ino_t ino; /* Consistency check */ + err = -EINVAL; if (!exp_verify_string(nxp->ex_path, NFS_MAXPATHLEN) || !exp_verify_string(nxp->ex_client, NFSCLNT_IDMAX)) - return -EINVAL; + goto out; dprintk("exp_export called for %s:%s (%x/%ld fl %x).\n", nxp->ex_client, nxp->ex_path, @@ -183,15 +253,11 @@ exp_export(struct nfsctl_export *nxp) * If there's already an export for this file, assume this * is just a flag update. */ - if ((exp = exp_find(clp, dev)) != NULL) { - /* Ensure there's only one export per FS. */ - err = -EPERM; - if (exp->ex_ino == ino) { - exp->ex_flags = nxp->ex_flags; - exp->ex_anon_uid = nxp->ex_anon_uid; - exp->ex_anon_gid = nxp->ex_anon_gid; - err = 0; - } + if ((exp = exp_get(clp, dev, ino)) != NULL) { + exp->ex_flags = nxp->ex_flags; + exp->ex_anon_uid = nxp->ex_anon_uid; + exp->ex_anon_gid = nxp->ex_anon_gid; + err = 0; goto out_unlock; } @@ -203,32 +269,32 @@ exp_export(struct nfsctl_export *nxp) err = -ENOENT; inode = dentry->d_inode; - if(!inode) + if (!inode) goto finish; err = -EINVAL; - if(inode->i_dev != dev || inode->i_ino != nxp->ex_ino) { - + if (inode->i_dev != dev || inode->i_ino != nxp->ex_ino) { printk(KERN_DEBUG "exp_export: i_dev = %x, dev = %x\n", inode->i_dev, dev); /* I'm just being paranoid... */ goto finish; } - /* We currently export only dirs. */ + /* We currently export only dirs and regular files. + * This is what umountd does. + */ err = -ENOTDIR; - if (!S_ISDIR(inode->i_mode)) + if (!S_ISDIR(inode->i_mode) && !S_ISREG(inode->i_mode)) goto finish; - /* If this is a sub-export, must be root of FS */ err = -EINVAL; - if ((parent = exp_parent(clp, dev)) != NULL) { - struct super_block *sb = inode->i_sb; - - if (inode != sb->s_root->d_inode) { -#ifdef NFSD_PARANOIA -printk("exp_export: sub-export %s not root of device %s\n", -nxp->ex_path, kdevname(sb->s_dev)); -#endif + if ((parent = exp_child(clp, dev, dentry)) != NULL) { + dprintk("exp_export: export not valid (Rule 3).\n"); + goto finish; + } + /* Is this is a sub-export, must be a proper subset of FS */ + if ((parent = exp_parent(clp, dev, dentry)) != NULL) { + if (dev == parent->ex_dev) { + dprintk("exp_export: sub-export not valid (Rule 2).\n"); goto finish; } } @@ -306,7 +372,7 @@ exp_do_unexport(svc_export *unexp) */ if (!exp_device_in_use(unexp->ex_dev)) { printk("exp_do_unexport: %s last use, flushing cache\n", -kdevname(unexp->ex_dev)); + kdevname(unexp->ex_dev)); nfsd_fh_flush(unexp->ex_dev); } @@ -362,18 +428,15 @@ exp_unexport(struct nfsctl_export *nxp) err = -EINVAL; clp = exp_getclientbyname(nxp->ex_client); if (clp) { -printk("exp_unexport: found client %s\n", nxp->ex_client); expp = clp->cl_export + EXPORT_HASH(nxp->ex_dev); while ((exp = *expp) != NULL) { if (exp->ex_dev == nxp->ex_dev) { - if (exp->ex_ino != nxp->ex_ino) { -printk("exp_unexport: ino mismatch, %ld not %ld\n", exp->ex_ino, nxp->ex_ino); + if (exp->ex_ino == nxp->ex_ino) { + *expp = exp->ex_next; + exp_do_unexport(exp); + err = 0; break; } - *expp = exp->ex_next; - exp_do_unexport(exp); - err = 0; - break; } expp = &(exp->ex_next); } @@ -390,28 +453,50 @@ out: * since its harder to fool a kernel module than a user space program. */ int -exp_rootfh(struct svc_client *clp, kdev_t dev, ino_t ino, struct knfs_fh *f) +exp_rootfh(struct svc_client *clp, kdev_t dev, ino_t ino, + char *path, struct knfs_fh *f) { struct svc_export *exp; - struct dentry *dentry; + struct dentry *dentry = NULL; struct inode *inode; struct svc_fh fh; + int err; - dprintk("nfsd: exp_rootfh(%s:%x/%ld)\n", clp->cl_ident, dev, ino); - - exp = exp_get(clp, dev, ino); - if (!exp) - return -EPERM; + err = -EPERM; + if (path) { + if (!(dentry = lookup_dentry(path, NULL, 0))) { + printk("nfsd: exp_rootfh path not found %s", path); + return -EPERM; + } + dev = dentry->d_inode->i_dev; + ino = dentry->d_inode->i_ino; + + dprintk("nfsd: exp_rootfh(%s [%p] %s:%x/%ld)\n", + path, dentry, clp->cl_ident, dev, ino); + exp = exp_parent(clp, dev, dentry); + } else { + dprintk("nfsd: exp_rootfh(%s:%x/%ld)\n", + clp->cl_ident, dev, ino); + if ((exp = exp_get(clp, dev, ino))) + if (!(dentry = dget(exp->ex_dentry))) { + printk("exp_rootfh: Aieee, NULL dentry\n"); + return -EPERM; + } + } + if (!exp) { + dprintk("nfsd: exp_rootfh export not found.\n"); + goto out; + } - dentry = exp->ex_dentry; inode = dentry->d_inode; - if(!inode) { + if (!inode) { printk("exp_rootfh: Aieee, NULL d_inode\n"); - return -EPERM; + goto out; } - if(inode->i_dev != dev || inode->i_ino != ino) { + if (inode->i_dev != dev || inode->i_ino != ino) { printk("exp_rootfh: Aieee, ino/dev mismatch\n"); - printk("exp_rootfh: arg[dev(%x):ino(%ld)] inode[dev(%x):ino(%ld)]\n", + printk("exp_rootfh: arg[dev(%x):ino(%ld)]" + " inode[dev(%x):ino(%ld)]\n", dev, ino, inode->i_dev, inode->i_ino); } @@ -419,11 +504,14 @@ exp_rootfh(struct svc_client *clp, kdev_t dev, ino_t ino, struct knfs_fh *f) * fh must be initialized before calling fh_compose */ fh_init(&fh); - fh_compose(&fh, exp, dget(dentry)); + fh_compose(&fh, exp, dentry); memcpy(f, &fh.fh_handle, sizeof(struct knfs_fh)); fh_put(&fh); - return 0; + +out: + dput(dentry); + return err; } /* diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index 43de72f54..5a8add24d 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -5,6 +5,7 @@ * * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de> */ +#define NFS_GETFH_NEW #include <linux/config.h> #include <linux/module.h> @@ -47,6 +48,7 @@ static int nfsctl_delclient(struct nfsctl_client *data); static int nfsctl_export(struct nfsctl_export *data); static int nfsctl_unexport(struct nfsctl_export *data); static int nfsctl_getfh(struct nfsctl_fhparm *, struct knfs_fh *); +static int nfsctl_getfd(struct nfsctl_fdparm *, struct knfs_fh *); /* static int nfsctl_ugidupdate(struct nfsctl_ugidmap *data); */ static int initialized = 0; @@ -108,6 +110,29 @@ nfsctl_ugidupdate(nfs_ugidmap *data) #endif static inline int +nfsctl_getfd(struct nfsctl_fdparm *data, struct knfs_fh *res) +{ + struct sockaddr_in *sin; + struct svc_client *clp; + int err = 0; + + if (data->gd_addr.sa_family != AF_INET) + return -EPROTONOSUPPORT; + if (data->gd_version < 2 || data->gd_version > NFSSVC_MAXVERS) + return -EINVAL; + sin = (struct sockaddr_in *)&data->gd_addr; + + exp_readlock(); + if (!(clp = exp_getclient(sin))) + err = -EPERM; + else + err = exp_rootfh(clp, 0, 0, data->gd_path, res); + exp_unlock(); + + return err; +} + +static inline int nfsctl_getfh(struct nfsctl_fhparm *data, struct knfs_fh *res) { struct sockaddr_in *sin; @@ -124,7 +149,7 @@ nfsctl_getfh(struct nfsctl_fhparm *data, struct knfs_fh *res) if (!(clp = exp_getclient(sin))) err = -EPERM; else - err = exp_rootfh(clp, to_kdev_t(data->gf_dev), data->gf_ino, res); + err = exp_rootfh(clp, to_kdev_t(data->gf_dev), data->gf_ino, NULL, res); exp_unlock(); return err; @@ -194,6 +219,9 @@ asmlinkage handle_sys_nfsservctl(int cmd, void *opaque_argp, void *opaque_resp) case NFSCTL_GETFH: err = nfsctl_getfh(&arg->ca_getfh, &res->cr_getfh); break; + case NFSCTL_GETFD: + err = nfsctl_getfd(&arg->ca_getfd, &res->cr_getfh); + break; default: err = -EINVAL; } diff --git a/fs/nfsd/nfsfh.c b/fs/nfsd/nfsfh.c index b19fd711c..8c3d91f64 100644 --- a/fs/nfsd/nfsfh.c +++ b/fs/nfsd/nfsfh.c @@ -493,6 +493,9 @@ static struct fh_entry *find_fhe(struct dentry *dentry, int cache, struct fh_entry *fhe; int i, found = (empty == NULL) ? 1 : 0; + if (!dentry) + goto out; + fhe = (cache == NFSD_FILE_CACHE) ? &filetable[0] : &dirstable[0]; for (i = 0; i < NFSD_MAXFH; i++, fhe++) { if (fhe->dentry == dentry) { @@ -504,6 +507,7 @@ static struct fh_entry *find_fhe(struct dentry *dentry, int cache, *empty = fhe; } } +out: return NULL; } @@ -756,8 +760,12 @@ static struct dentry *find_dentry_in_fhcache(struct knfs_fh *fh) fhe = find_fhe(fh->fh_dcookie, NFSD_FILE_CACHE, NULL); if (fhe) { - struct dentry *parent, *dentry = fhe->dentry; - struct inode *inode = dentry->d_inode; + struct dentry *parent, *dentry; + struct inode *inode; + + dentry = fhe->dentry; + inode = dentry->d_inode; + if (!inode) { #ifdef NFSD_PARANOIA printk("find_dentry_in_fhcache: %s/%s has no inode!\n", @@ -1019,7 +1027,7 @@ fh_verify(struct svc_rqst *rqstp, struct svc_fh *fhp, int type, int access) dprintk("nfsd: fh_verify(exp %x/%u cookie %p)\n", fh->fh_xdev, fh->fh_xino, fh->fh_dcookie); - if(fhp->fh_dverified) + if (fhp->fh_dverified) goto check_type; /* * Look up the export entry. @@ -1051,11 +1059,12 @@ fh_verify(struct svc_rqst *rqstp, struct svc_fh *fhp, int type, int access) dentry = find_fh_dentry(fh); if (!dentry) goto out; + /* * Note: it's possible the returned dentry won't be the one in the - * file handle. We can correct the file handle for our use, but - * unfortunately the client will keep sending the broken one. Let's - * hope the lookup will keep patching things up. + * file handle. We can correct the file handle for our use, but + * unfortunately the client will keep sending the broken one. Let's + * hope the lookup will keep patching things up. */ fhp->fh_dentry = dentry; fhp->fh_export = exp; @@ -1071,6 +1080,7 @@ fh_verify(struct svc_rqst *rqstp, struct svc_fh *fhp, int type, int access) check_type: dentry = fhp->fh_dentry; inode = dentry->d_inode; + exp = fhp->fh_export; if (type > 0 && (inode->i_mode & S_IFMT) != type) { error = (type == S_IFDIR)? nfserr_notdir : nfserr_isdir; goto out; @@ -1080,9 +1090,45 @@ check_type: goto out; } + /* + * Security: Check that the export is valid for dentry <gam3@acm.org> + */ + if (fh->fh_dev != fh->fh_xdev) { + printk("fh_verify: Security: export on other device" + " (%d, %d).\n", fh->fh_dev, fh->fh_xdev); + goto out; + } else if (exp->ex_dentry != dentry) { + struct dentry *tdentry = dentry; + int err2 = 0; + + error = nfserr_stale; + do { + tdentry = tdentry->d_parent; + if (exp->ex_dentry == tdentry) { + error = 0; + break; + } + if ((err2 = nfsd_permission(exp, tdentry, MAY_READ))) { + error = err2; +#ifdef NFSD_PARANOIA + goto out1; +#else + goto out; +#endif + } + } while ((tdentry != tdentry->d_parent)); + if (error) { + printk("fh_verify: Security: %s/%s bad export.\n", + dentry->d_parent->d_name.name, + dentry->d_name.name); + goto out; + } + } + /* Finally, check access permissions. */ - error = nfsd_permission(fhp->fh_export, dentry, access); + error = nfsd_permission(exp, dentry, access); #ifdef NFSD_PARANOIA +out1: if (error) printk("fh_verify: %s/%s permission failure, acc=%x, error=%d\n", dentry->d_parent->d_name.name, dentry->d_name.name, access, error); diff --git a/fs/nfsd/nfssvc.c b/fs/nfsd/nfssvc.c index d8da2f463..455d0f209 100644 --- a/fs/nfsd/nfssvc.c +++ b/fs/nfsd/nfssvc.c @@ -115,7 +115,7 @@ nfsd(struct svc_rqst *rqstp) * Find a socket with data available and call its * recvfrom routine. */ - while ((err = svc_recv(serv, rqstp)) == -EAGAIN) + while ((err = svc_recv(serv, rqstp, MAX_SCHEDULE_TIMEOUT)) == -EAGAIN) ; if (err < 0) break; diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index 8b398112d..72d67a13a 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c @@ -14,6 +14,7 @@ * Copyright (C) 1995, 1996, 1997 Olaf Kirch <okir@monad.swb.de> */ +#include <linux/config.h> #include <linux/version.h> #include <linux/sched.h> #include <linux/errno.h> @@ -39,6 +40,7 @@ #endif #define NFSDDBG_FACILITY NFSDDBG_FILEOP +#define NFSD_PARANOIA /* Open mode for nfsd_open */ #define OPEN_READ 0 @@ -168,13 +170,19 @@ nfsd_lookup(struct svc_rqst *rqstp, struct svc_fh *fhp, const char *name, if (IS_ERR(dchild)) goto out_nfserr; /* - * Make sure we haven't crossed a mount point ... + * check if we have crossed a mount point ... */ if (dchild->d_sb != dparent->d_sb) { -#ifdef NFSD_PARANOIA -printk("nfsd_lookup: %s/%s crossed mount point!\n", dparent->d_name.name, name); -#endif - goto out_dput; + struct dentry *tdentry; + tdentry = dchild->d_covers; + if (tdentry == dchild) + goto out_dput; + dput(dchild); + dchild = dget(tdentry); + if (dchild->d_sb != dparent->d_sb) { +printk("nfsd_lookup: %s/%s crossed mount point!\n", dparent->d_name.name, dchild->d_name.name); + goto out_dput; + } } /* @@ -416,8 +424,8 @@ found: * N.B. After this call fhp needs an fh_put */ int -nfsd_read(struct svc_rqst *rqstp, struct svc_fh *fhp, loff_t offset, char *buf, - unsigned long *count) +nfsd_read(struct svc_rqst *rqstp, struct svc_fh *fhp, loff_t offset, + char *buf, unsigned long *count) { struct raparms *ra; mm_segment_t oldfs; @@ -547,13 +555,10 @@ nfsd_write(struct svc_rqst *rqstp, struct svc_fh *fhp, loff_t offset, if (EX_WGATHER(exp) && (inode->i_writecount > 1 || (last_ino == inode->i_ino && last_dev == inode->i_dev))) { #if 0 - current->timeout = jiffies + 10 * HZ / 1000; - interruptible_sleep_on(&inode->i_wait); + interruptible_sleep_on_timeout(&inode->i_wait, 10 * HZ / 1000); #else dprintk("nfsd: write defer %d\n", current->pid); - current->need_resched = 1; - current->timeout = jiffies + HZ / 100; - schedule(); + schedule_timeout((HZ+99)/100); dprintk("nfsd: write resume %d\n", current->pid); #endif } @@ -1067,6 +1072,11 @@ nfsd_unlink(struct svc_rqst *rqstp, struct svc_fh *fhp, int type, if (IS_ERR(rdentry)) goto out_nfserr; + /* + * FIXME!! + * + * This should do a double-lock on both rdentry and the parent + */ err = fh_lock_parent(fhp, rdentry); if (err) goto out; @@ -1240,6 +1250,12 @@ nfsd_permission(struct svc_export *exp, struct dentry *dentry, int acc) inode->i_uid, inode->i_gid, current->fsuid, current->fsgid); */ +#ifndef CONFIG_NFSD_SUN + if (dentry->d_mounts != dentry) { + return nfserr_perm; + } +#endif + if (acc & (MAY_WRITE | MAY_SATTR | MAY_TRUNC)) { if (EX_RDONLY(exp) || IS_RDONLY(inode)) return nfserr_rofs; |