Commit 7782a1a9 authored by Ian Abbott's avatar Ian Abbott Committed by Mark Brown

spi: spidev: Convert buf pointers for 32-bit compat SPI_IOC_MESSAGE(n)

The SPI_IOC_MESSAGE(n) ioctl commands' argument points to an array of n
struct spi_ioc_transfer elements.  The spidev's compat_ioctl handler
just converts this pointer and passes it on to the unlocked_ioctl
handler to process it.

The tx_buf and rx_buf members of struct spi_ioc_transfer are of type
__u64 and hold pointer values.  A 32-bit userspace application running
in a 64-bit kernel might not have widened the 32-bit pointers correctly
for the kernel.  The application might have sign-extended the pointer to
when the kernel expects it to be zero-extended, or vice versa, leading
to an -EFAULT being returned by spidev_message() if the widened pointer
is invalid.

Handle the SPI_IOC_MESSAGE(n) ioctl commands specially in the
compat_ioctl handler, calling new function spidev_compat_ioctl_message()
to handle them.  This processes them in the same way as the
unlocked_ioctl handler except that it uses compat_ptr() to convert the
tx_buf and rx_buf members of each struct spi_ioc_transfer element.

To save code, factor out part of the unlocked_ioctl handler into a new
function spidev_get_ioc_message().  This checks the ioctl command code
is a valid SPI_IOC_MESSAGE(n), determines n and copies the array of n
struct spi_ioc_transfer elements from userspace into dynamically
allocated memory, returning either a pointer to the memory, an
ERR_PTR(-err) value, or NULL (for SPI_IOC_MESSAGE(0)).
Signed-off-by: default avatarIan Abbott <abbotti@mev.co.uk>
Signed-off-by: default avatarMark Brown <broonie@kernel.org>
parent 97bf6af1
...@@ -317,6 +317,37 @@ static int spidev_message(struct spidev_data *spidev, ...@@ -317,6 +317,37 @@ static int spidev_message(struct spidev_data *spidev,
return status; return status;
} }
static struct spi_ioc_transfer *
spidev_get_ioc_message(unsigned int cmd, struct spi_ioc_transfer __user *u_ioc,
unsigned *n_ioc)
{
struct spi_ioc_transfer *ioc;
u32 tmp;
/* Check type, command number and direction */
if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC
|| _IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0))
|| _IOC_DIR(cmd) != _IOC_WRITE)
return ERR_PTR(-ENOTTY);
tmp = _IOC_SIZE(cmd);
if ((tmp % sizeof(struct spi_ioc_transfer)) != 0)
return ERR_PTR(-EINVAL);
*n_ioc = tmp / sizeof(struct spi_ioc_transfer);
if (*n_ioc == 0)
return NULL;
/* copy into scratch area */
ioc = kmalloc(tmp, GFP_KERNEL);
if (!ioc)
return ERR_PTR(-ENOMEM);
if (__copy_from_user(ioc, u_ioc, tmp)) {
kfree(ioc);
return ERR_PTR(-EFAULT);
}
return ioc;
}
static long static long
spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{ {
...@@ -456,48 +487,90 @@ spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) ...@@ -456,48 +487,90 @@ spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
default: default:
/* segmented and/or full-duplex I/O request */ /* segmented and/or full-duplex I/O request */
if (_IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0)) /* Check message and copy into scratch area */
|| _IOC_DIR(cmd) != _IOC_WRITE) { ioc = spidev_get_ioc_message(cmd,
retval = -ENOTTY; (struct spi_ioc_transfer __user *)arg, &n_ioc);
if (IS_ERR(ioc)) {
retval = PTR_ERR(ioc);
break; break;
} }
if (!ioc)
break; /* n_ioc is also 0 */
tmp = _IOC_SIZE(cmd); /* translate to spi_message, execute */
if ((tmp % sizeof(struct spi_ioc_transfer)) != 0) { retval = spidev_message(spidev, ioc, n_ioc);
retval = -EINVAL; kfree(ioc);
break; break;
} }
n_ioc = tmp / sizeof(struct spi_ioc_transfer);
if (n_ioc == 0)
break;
/* copy into scratch area */ mutex_unlock(&spidev->buf_lock);
ioc = kmalloc(tmp, GFP_KERNEL); spi_dev_put(spi);
if (!ioc) { return retval;
retval = -ENOMEM; }
break;
#ifdef CONFIG_COMPAT
static long
spidev_compat_ioc_message(struct file *filp, unsigned int cmd,
unsigned long arg)
{
struct spi_ioc_transfer __user *u_ioc;
int retval = 0;
struct spidev_data *spidev;
struct spi_device *spi;
unsigned n_ioc, n;
struct spi_ioc_transfer *ioc;
u_ioc = (struct spi_ioc_transfer __user *) compat_ptr(arg);
if (!access_ok(VERIFY_READ, u_ioc, _IOC_SIZE(cmd)))
return -EFAULT;
/* guard against device removal before, or while,
* we issue this ioctl.
*/
spidev = filp->private_data;
spin_lock_irq(&spidev->spi_lock);
spi = spi_dev_get(spidev->spi);
spin_unlock_irq(&spidev->spi_lock);
if (spi == NULL)
return -ESHUTDOWN;
/* SPI_IOC_MESSAGE needs the buffer locked "normally" */
mutex_lock(&spidev->buf_lock);
/* Check message and copy into scratch area */
ioc = spidev_get_ioc_message(cmd, u_ioc, &n_ioc);
if (IS_ERR(ioc)) {
retval = PTR_ERR(ioc);
goto done;
} }
if (__copy_from_user(ioc, (void __user *)arg, tmp)) { if (!ioc)
kfree(ioc); goto done; /* n_ioc is also 0 */
retval = -EFAULT;
break; /* Convert buffer pointers */
for (n = 0; n < n_ioc; n++) {
ioc[n].rx_buf = (uintptr_t) compat_ptr(ioc[n].rx_buf);
ioc[n].tx_buf = (uintptr_t) compat_ptr(ioc[n].tx_buf);
} }
/* translate to spi_message, execute */ /* translate to spi_message, execute */
retval = spidev_message(spidev, ioc, n_ioc); retval = spidev_message(spidev, ioc, n_ioc);
kfree(ioc); kfree(ioc);
break;
}
done:
mutex_unlock(&spidev->buf_lock); mutex_unlock(&spidev->buf_lock);
spi_dev_put(spi); spi_dev_put(spi);
return retval; return retval;
} }
#ifdef CONFIG_COMPAT
static long static long
spidev_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) spidev_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{ {
if (_IOC_TYPE(cmd) == SPI_IOC_MAGIC
&& _IOC_NR(cmd) == _IOC_NR(SPI_IOC_MESSAGE(0))
&& _IOC_DIR(cmd) == _IOC_WRITE)
return spidev_compat_ioc_message(filp, cmd, arg);
return spidev_ioctl(filp, cmd, (unsigned long)compat_ptr(arg)); return spidev_ioctl(filp, cmd, (unsigned long)compat_ptr(arg));
} }
#else #else
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment