summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTrond Myklebust <Trond.Myklebust@netapp.com>2008-05-21 01:34:39 +0200
committerTrond Myklebust <Trond.Myklebust@netapp.com>2008-07-09 18:08:40 +0200
commit2116271a347d1181b5497602c2bfada1de8fd53b (patch)
tree537498aa91bbe1fbbfc7f2c1e00910ca7fbc2261
parentNFS: Fix a preemption count leak in nfs_update_request (diff)
downloadlinux-2116271a347d1181b5497602c2bfada1de8fd53b.tar.xz
linux-2116271a347d1181b5497602c2bfada1de8fd53b.zip
NFS: Add correct bounds checking to NFSv2 locks
NFSv2 file locking currently fails the Connectathon tests, because the calls to the VFS locking code do not return an EINVAL error if the struct file_lock overflows the 32-bit boundaries. The problem is due to the fact that we occasionally call helpers from fs/locks.c in order to avoid RPC calls to the server when we know that a local process holds the lock. These helpers are, of course, always 64-bit enabled, so EINVAL is not returned in cases when it would if the call had gone to the NLM code. For consistency, we therefore add support for a bounds-checking helper. Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
-rw-r--r--fs/nfs/file.c20
-rw-r--r--fs/nfs/proc.c24
-rw-r--r--include/linux/nfs_xdr.h1
3 files changed, 40 insertions, 5 deletions
diff --git a/fs/nfs/file.c b/fs/nfs/file.c
index d84a3d8f32af..7c73f06692b6 100644
--- a/fs/nfs/file.c
+++ b/fs/nfs/file.c
@@ -593,6 +593,7 @@ out:
static int nfs_lock(struct file *filp, int cmd, struct file_lock *fl)
{
struct inode * inode = filp->f_mapping->host;
+ int ret = -ENOLCK;
dprintk("NFS: nfs_lock(f=%s/%ld, t=%x, fl=%x, r=%Ld:%Ld)\n",
inode->i_sb->s_id, inode->i_ino,
@@ -602,13 +603,22 @@ static int nfs_lock(struct file *filp, int cmd, struct file_lock *fl)
/* No mandatory locks over NFS */
if (__mandatory_lock(inode) && fl->fl_type != F_UNLCK)
- return -ENOLCK;
+ goto out_err;
+
+ if (NFS_PROTO(inode)->lock_check_bounds != NULL) {
+ ret = NFS_PROTO(inode)->lock_check_bounds(fl);
+ if (ret < 0)
+ goto out_err;
+ }
if (IS_GETLK(cmd))
- return do_getlk(filp, cmd, fl);
- if (fl->fl_type == F_UNLCK)
- return do_unlk(filp, cmd, fl);
- return do_setlk(filp, cmd, fl);
+ ret = do_getlk(filp, cmd, fl);
+ else if (fl->fl_type == F_UNLCK)
+ ret = do_unlk(filp, cmd, fl);
+ else
+ ret = do_setlk(filp, cmd, fl);
+out_err:
+ return ret;
}
/*
diff --git a/fs/nfs/proc.c b/fs/nfs/proc.c
index 03599bfe81cf..5c35b02857f3 100644
--- a/fs/nfs/proc.c
+++ b/fs/nfs/proc.c
@@ -598,6 +598,29 @@ nfs_proc_lock(struct file *filp, int cmd, struct file_lock *fl)
return nlmclnt_proc(NFS_SERVER(inode)->nlm_host, cmd, fl);
}
+/* Helper functions for NFS lock bounds checking */
+#define NFS_LOCK32_OFFSET_MAX ((__s32)0x7fffffffUL)
+static int nfs_lock_check_bounds(const struct file_lock *fl)
+{
+ __s32 start, end;
+
+ start = (__s32)fl->fl_start;
+ if ((loff_t)start != fl->fl_start)
+ goto out_einval;
+
+ if (fl->fl_end != OFFSET_MAX) {
+ end = (__s32)fl->fl_end;
+ if ((loff_t)end != fl->fl_end)
+ goto out_einval;
+ } else
+ end = NFS_LOCK32_OFFSET_MAX;
+
+ if (start < 0 || start > end)
+ goto out_einval;
+ return 0;
+out_einval:
+ return -EINVAL;
+}
const struct nfs_rpc_ops nfs_v2_clientops = {
.version = 2, /* protocol version */
@@ -633,4 +656,5 @@ const struct nfs_rpc_ops nfs_v2_clientops = {
.file_open = nfs_open,
.file_release = nfs_release,
.lock = nfs_proc_lock,
+ .lock_check_bounds = nfs_lock_check_bounds,
};
diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h
index 24263bb8e0be..8d780de371f0 100644
--- a/include/linux/nfs_xdr.h
+++ b/include/linux/nfs_xdr.h
@@ -832,6 +832,7 @@ struct nfs_rpc_ops {
int (*file_open) (struct inode *, struct file *);
int (*file_release) (struct inode *, struct file *);
int (*lock)(struct file *, int, struct file_lock *);
+ int (*lock_check_bounds)(const struct file_lock *);
void (*clear_acl_cache)(struct inode *);
};