Commit 846b4032 authored by Arnd Bergmann's avatar Arnd Bergmann Committed by Ben Hutchings

libata: fix HDIO_GET_32BIT ioctl

commit 287e6611 upstream.

As reported by Soohoon Lee, the HDIO_GET_32BIT ioctl does not
work correctly in compat mode with libata.

I have investigated the issue further and found multiple problems
that all appeared with the same commit that originally introduced
HDIO_GET_32BIT handling in libata back in linux-2.6.8 and presumably
also linux-2.4, as the code uses "copy_to_user(arg, &val, 1)" to copy
a 'long' variable containing either 0 or 1 to user space.

The problems with this are:

* On big-endian machines, this will always write a zero because it
  stores the wrong byte into user space.

* In compat mode, the upper three bytes of the variable are updated
  by the compat_hdio_ioctl() function, but they now contain
  uninitialized stack data.

* The hdparm tool calling this ioctl uses a 'static long' variable
  to store the result. This means at least the upper bytes are
  initialized to zero, but calling another ioctl like HDIO_GET_MULTCOUNT
  would fill them with data that remains stale when the low byte
  is overwritten. Fortunately libata doesn't implement any of the
  affected ioctl commands, so this would only happen when we query
  both an IDE and an ATA device in the same command such as
  "hdparm -N -c /dev/hda /dev/sda"

* The libata code for unknown reasons started using ATA_IOC_GET_IO32
  and ATA_IOC_SET_IO32 as aliases for HDIO_GET_32BIT and HDIO_SET_32BIT,
  while the ioctl commands that were added later use the normal
  HDIO_* names. This is harmless but rather confusing.

This addresses all four issues by changing the code to use put_user()
on an 'unsigned long' variable in HDIO_GET_32BIT, like the IDE subsystem
does, and by clarifying the names of the ioctl commands.
Signed-off-by: default avatarArnd Bergmann <arnd@arndb.de>
Reported-by: default avatarSoohoon Lee <Soohoon.Lee@f5.com>
Tested-by: default avatarSoohoon Lee <Soohoon.Lee@f5.com>
Signed-off-by: default avatarTejun Heo <tj@kernel.org>
Signed-off-by: default avatarBen Hutchings <ben@decadent.org.uk>
parent 0b22bde0
...@@ -672,19 +672,18 @@ static int ata_ioc32(struct ata_port *ap) ...@@ -672,19 +672,18 @@ static int ata_ioc32(struct ata_port *ap)
int ata_sas_scsi_ioctl(struct ata_port *ap, struct scsi_device *scsidev, int ata_sas_scsi_ioctl(struct ata_port *ap, struct scsi_device *scsidev,
int cmd, void __user *arg) int cmd, void __user *arg)
{ {
int val = -EINVAL, rc = -EINVAL; unsigned long val;
int rc = -EINVAL;
unsigned long flags; unsigned long flags;
switch (cmd) { switch (cmd) {
case ATA_IOC_GET_IO32: case HDIO_GET_32BIT:
spin_lock_irqsave(ap->lock, flags); spin_lock_irqsave(ap->lock, flags);
val = ata_ioc32(ap); val = ata_ioc32(ap);
spin_unlock_irqrestore(ap->lock, flags); spin_unlock_irqrestore(ap->lock, flags);
if (copy_to_user(arg, &val, 1)) return put_user(val, (unsigned long __user *)arg);
return -EFAULT;
return 0;
case ATA_IOC_SET_IO32: case HDIO_SET_32BIT:
val = (unsigned long) arg; val = (unsigned long) arg;
rc = 0; rc = 0;
spin_lock_irqsave(ap->lock, flags); spin_lock_irqsave(ap->lock, flags);
......
...@@ -464,8 +464,8 @@ enum ata_tf_protocols { ...@@ -464,8 +464,8 @@ enum ata_tf_protocols {
}; };
enum ata_ioctls { enum ata_ioctls {
ATA_IOC_GET_IO32 = 0x309, ATA_IOC_GET_IO32 = 0x309, /* HDIO_GET_32BIT */
ATA_IOC_SET_IO32 = 0x324, ATA_IOC_SET_IO32 = 0x324, /* HDIO_SET_32BIT */
}; };
/* core structures */ /* core structures */
......
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