summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiklos Szeredi <mszeredi@suse.cz>2010-12-07 20:16:56 +0100
committerMiklos Szeredi <mszeredi@suse.cz>2010-12-07 20:16:56 +0100
commit1baa26b2be92fe9917e2f7ef46d423b5dfa4da71 (patch)
tree1b4f24c149a01e74c17cb0c290952dd09778ca1d
parent02c048b919455aaa38628563cdcc2e691c8a9f53 (diff)
downloadkernel-common-1baa26b2be92fe9917e2f7ef46d423b5dfa4da71.tar.gz
kernel-common-1baa26b2be92fe9917e2f7ef46d423b5dfa4da71.tar.bz2
kernel-common-1baa26b2be92fe9917e2f7ef46d423b5dfa4da71.zip
fuse: fix ioctl ABI
In kernel ABI version 7.16 and later FUSE_IOCTL_RETRY reply from a unrestricted IOCTL request shall return with an array of 'struct fuse_ioctl_iovec' instead of 'struct iovec'. This fixes the ABI ambiguity of 32bit vs. 64bit. Reported-by: "ccmail111" <ccmail111@yahoo.com> Signed-off-by: Miklos Szeredi <mszeredi@suse.cz> CC: Tejun Heo <tj@kernel.org>
-rw-r--r--fs/fuse/file.c53
-rw-r--r--include/linux/fuse.h10
2 files changed, 58 insertions, 5 deletions
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index ca3b6bbb3790..95da1bc1c826 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -1634,9 +1634,9 @@ static int fuse_ioctl_copy_user(struct page **pages, struct iovec *iov,
* and 64bit. Fortunately we can determine which structure the server
* used from the size of the reply.
*/
-static int fuse_copy_ioctl_iovec(struct iovec *dst, void *src,
- size_t transferred, unsigned count,
- bool is_compat)
+static int fuse_copy_ioctl_iovec_old(struct iovec *dst, void *src,
+ size_t transferred, unsigned count,
+ bool is_compat)
{
#ifdef CONFIG_COMPAT
if (count * sizeof(struct compat_iovec) == transferred) {
@@ -1680,6 +1680,42 @@ static int fuse_verify_ioctl_iov(struct iovec *iov, size_t count)
return 0;
}
+static int fuse_copy_ioctl_iovec(struct fuse_conn *fc, struct iovec *dst,
+ void *src, size_t transferred, unsigned count,
+ bool is_compat)
+{
+ unsigned i;
+ struct fuse_ioctl_iovec *fiov = src;
+
+ if (fc->minor < 16) {
+ return fuse_copy_ioctl_iovec_old(dst, src, transferred,
+ count, is_compat);
+ }
+
+ if (count * sizeof(struct fuse_ioctl_iovec) != transferred)
+ return -EIO;
+
+ for (i = 0; i < count; i++) {
+ /* Did the server supply an inappropriate value? */
+ if (fiov[i].base != (unsigned long) fiov[i].base ||
+ fiov[i].len != (unsigned long) fiov[i].len)
+ return -EIO;
+
+ dst[i].iov_base = (void __user *) (unsigned long) fiov[i].base;
+ dst[i].iov_len = (size_t) fiov[i].len;
+
+#ifdef CONFIG_COMPAT
+ if (is_compat &&
+ (ptr_to_compat(dst[i].iov_base) != fiov[i].base ||
+ (compat_size_t) dst[i].iov_len != fiov[i].len))
+ return -EIO;
+#endif
+ }
+
+ return 0;
+}
+
+
/*
* For ioctls, there is no generic way to determine how much memory
* needs to be read and/or written. Furthermore, ioctls are allowed
@@ -1746,8 +1782,15 @@ long fuse_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg,
size_t in_size, out_size, transferred;
int err;
+#if BITS_PER_LONG == 32
+ inarg.flags |= FUSE_IOCTL_32BIT;
+#else
+ if (flags & FUSE_IOCTL_COMPAT)
+ inarg.flags |= FUSE_IOCTL_32BIT;
+#endif
+
/* assume all the iovs returned by client always fits in a page */
- BUILD_BUG_ON(sizeof(struct iovec) * FUSE_IOCTL_MAX_IOV > PAGE_SIZE);
+ BUILD_BUG_ON(sizeof(struct fuse_ioctl_iovec) * FUSE_IOCTL_MAX_IOV > PAGE_SIZE);
err = -ENOMEM;
pages = kzalloc(sizeof(pages[0]) * FUSE_MAX_PAGES_PER_REQ, GFP_KERNEL);
@@ -1862,7 +1905,7 @@ long fuse_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg,
goto out;
vaddr = kmap_atomic(pages[0], KM_USER0);
- err = fuse_copy_ioctl_iovec(iov_page, vaddr,
+ err = fuse_copy_ioctl_iovec(fc, iov_page, vaddr,
transferred, in_iovs + out_iovs,
(flags & FUSE_IOCTL_COMPAT) != 0);
kunmap_atomic(vaddr, KM_USER0);
diff --git a/include/linux/fuse.h b/include/linux/fuse.h
index cf11881f4938..d464de53db43 100644
--- a/include/linux/fuse.h
+++ b/include/linux/fuse.h
@@ -44,6 +44,9 @@
*
* 7.16
* - add BATCH_FORGET request
+ * - FUSE_IOCTL_UNRESTRICTED shall now return with array of 'struct
+ * fuse_ioctl_iovec' instead of ambiguous 'struct iovec'
+ * - add FUSE_IOCTL_32BIT flag
*/
#ifndef _LINUX_FUSE_H
@@ -203,12 +206,14 @@ struct fuse_file_lock {
* FUSE_IOCTL_COMPAT: 32bit compat ioctl on 64bit machine
* FUSE_IOCTL_UNRESTRICTED: not restricted to well-formed ioctls, retry allowed
* FUSE_IOCTL_RETRY: retry with new iovecs
+ * FUSE_IOCTL_32BIT: 32bit ioctl
*
* FUSE_IOCTL_MAX_IOV: maximum of in_iovecs + out_iovecs
*/
#define FUSE_IOCTL_COMPAT (1 << 0)
#define FUSE_IOCTL_UNRESTRICTED (1 << 1)
#define FUSE_IOCTL_RETRY (1 << 2)
+#define FUSE_IOCTL_32BIT (1 << 3)
#define FUSE_IOCTL_MAX_IOV 256
@@ -524,6 +529,11 @@ struct fuse_ioctl_in {
__u32 out_size;
};
+struct fuse_ioctl_iovec {
+ __u64 base;
+ __u64 len;
+};
+
struct fuse_ioctl_out {
__s32 result;
__u32 flags;