fuse: fix ioctl when server is 32bit
If a 32bit CUSE server is run on 64bit this results in EIO being returned to the caller. The reason is that FUSE_IOCTL_RETRY reply was defined to use 'struct iovec', which is different on 32bit and 64bit archs. Work around this by looking at the size of the reply to determine which struct was used. This is only needed if CONFIG_COMPAT is defined. A more permanent fix for the interface will be to use the same struct on both 32bit and 64bit. Reported-by: "ccmail111" <ccmail111@yahoo.com> Signed-off-by: Miklos Szeredi <mszeredi@suse.cz> CC: Tejun Heo <tj@kernel.org> CC: <stable@kernel.org> [2.6.31+]
This commit is contained in:
parent
e8a7e48bb2
commit
d9d318d39d
|
@ -13,6 +13,7 @@
|
|||
#include <linux/kernel.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/compat.h>
|
||||
|
||||
static const struct file_operations fuse_direct_io_file_operations;
|
||||
|
||||
|
@ -1627,6 +1628,44 @@ static int fuse_ioctl_copy_user(struct page **pages, struct iovec *iov,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* CUSE servers compiled on 32bit broke on 64bit kernels because the
|
||||
* ABI was defined to be 'struct iovec' which is different on 32bit
|
||||
* 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)
|
||||
{
|
||||
#ifdef CONFIG_COMPAT
|
||||
if (count * sizeof(struct compat_iovec) == transferred) {
|
||||
struct compat_iovec *ciov = src;
|
||||
unsigned i;
|
||||
|
||||
/*
|
||||
* With this interface a 32bit server cannot support
|
||||
* non-compat (i.e. ones coming from 64bit apps) ioctl
|
||||
* requests
|
||||
*/
|
||||
if (!is_compat)
|
||||
return -EINVAL;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
dst[i].iov_base = compat_ptr(ciov[i].iov_base);
|
||||
dst[i].iov_len = ciov[i].iov_len;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (count * sizeof(struct iovec) != transferred)
|
||||
return -EIO;
|
||||
|
||||
memcpy(dst, src, transferred);
|
||||
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
|
||||
|
@ -1808,14 +1847,13 @@ long fuse_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg,
|
|||
in_iovs + out_iovs > FUSE_IOCTL_MAX_IOV)
|
||||
goto out;
|
||||
|
||||
err = -EIO;
|
||||
if ((in_iovs + out_iovs) * sizeof(struct iovec) != transferred)
|
||||
goto out;
|
||||
|
||||
/* okay, copy in iovs and retry */
|
||||
vaddr = kmap_atomic(pages[0], KM_USER0);
|
||||
memcpy(page_address(iov_page), vaddr, transferred);
|
||||
err = fuse_copy_ioctl_iovec(page_address(iov_page), vaddr,
|
||||
transferred, in_iovs + out_iovs,
|
||||
(flags & FUSE_IOCTL_COMPAT) != 0);
|
||||
kunmap_atomic(vaddr, KM_USER0);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
in_iov = page_address(iov_page);
|
||||
out_iov = in_iov + in_iovs;
|
||||
|
|
Loading…
Reference in New Issue