summaryrefslogtreecommitdiffstats
path: root/fs/nfsd
diff options
context:
space:
mode:
authorRalf Baechle <ralf@linux-mips.org>1999-01-04 16:03:48 +0000
committerRalf Baechle <ralf@linux-mips.org>1999-01-04 16:03:48 +0000
commit78c388aed2b7184182c08428db1de6c872d815f5 (patch)
tree4b2003b1b4ceb241a17faa995da8dd1004bb8e45 /fs/nfsd
parenteb7a5bf93aaa4be1d7c6181100ab7639e74d67f7 (diff)
Merge with Linux 2.1.131 and more MIPS goodies.
(Did I mention that CVS is buggy ...)
Diffstat (limited to 'fs/nfsd')
-rw-r--r--fs/nfsd/auth.c18
-rw-r--r--fs/nfsd/export.c204
-rw-r--r--fs/nfsd/nfsctl.c30
-rw-r--r--fs/nfsd/nfsfh.c60
-rw-r--r--fs/nfsd/nfssvc.c2
-rw-r--r--fs/nfsd/vfs.c40
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;