compat_ioctl: reimplement SG_IO handling
There are two code locations that implement the SG_IO ioctl: the old sg.c driver, and the generic scsi_ioctl helper that is in turn used by multiple drivers. To eradicate the old compat_ioctl conversion handler for the SG_IO command, I implement a readable pair of put_sg_io_hdr() /get_sg_io_hdr() helper functions that can be used for both compat and native mode, and then I call this from both drivers. For the iovec handling, there is already a compat_import_iovec() function that can simply be called in place of import_iovec(). To avoid having to pass the compat/native state through multiple indirections, I mark the SG_IO command itself as compatible in fs/compat_ioctl.c and use in_compat_syscall() to figure out where we are called from. As a side-effect of this, the sg.c driver now also accepts the 32-bit sg_io_hdr format in compat mode using the read/write interface, not just ioctl. This should improve compatiblity with old 32-bit binaries, but it would break if any application intentionally passes the 64-bit data structure in compat mode here. Steffen Maier helped debug an issue in an earlier version of this patch. Cc: Steffen Maier <maier@linux.ibm.com> Cc: linux-scsi@vger.kernel.org Cc: Doug Gilbert <dgilbert@interlog.com> Cc: "James E.J. Bottomley" <jejb@linux.ibm.com> Cc: "Martin K. Petersen" <martin.petersen@oracle.com> Signed-off-by: Arnd Bergmann <arnd@arndb.de>
This commit is contained in:
@@ -64,151 +64,6 @@ static int do_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
}
|
||||
|
||||
#ifdef CONFIG_BLOCK
|
||||
typedef struct sg_io_hdr32 {
|
||||
compat_int_t interface_id; /* [i] 'S' for SCSI generic (required) */
|
||||
compat_int_t dxfer_direction; /* [i] data transfer direction */
|
||||
unsigned char cmd_len; /* [i] SCSI command length ( <= 16 bytes) */
|
||||
unsigned char mx_sb_len; /* [i] max length to write to sbp */
|
||||
unsigned short iovec_count; /* [i] 0 implies no scatter gather */
|
||||
compat_uint_t dxfer_len; /* [i] byte count of data transfer */
|
||||
compat_uint_t dxferp; /* [i], [*io] points to data transfer memory
|
||||
or scatter gather list */
|
||||
compat_uptr_t cmdp; /* [i], [*i] points to command to perform */
|
||||
compat_uptr_t sbp; /* [i], [*o] points to sense_buffer memory */
|
||||
compat_uint_t timeout; /* [i] MAX_UINT->no timeout (unit: millisec) */
|
||||
compat_uint_t flags; /* [i] 0 -> default, see SG_FLAG... */
|
||||
compat_int_t pack_id; /* [i->o] unused internally (normally) */
|
||||
compat_uptr_t usr_ptr; /* [i->o] unused internally */
|
||||
unsigned char status; /* [o] scsi status */
|
||||
unsigned char masked_status; /* [o] shifted, masked scsi status */
|
||||
unsigned char msg_status; /* [o] messaging level data (optional) */
|
||||
unsigned char sb_len_wr; /* [o] byte count actually written to sbp */
|
||||
unsigned short host_status; /* [o] errors from host adapter */
|
||||
unsigned short driver_status; /* [o] errors from software driver */
|
||||
compat_int_t resid; /* [o] dxfer_len - actual_transferred */
|
||||
compat_uint_t duration; /* [o] time taken by cmd (unit: millisec) */
|
||||
compat_uint_t info; /* [o] auxiliary information */
|
||||
} sg_io_hdr32_t; /* 64 bytes long (on sparc32) */
|
||||
|
||||
typedef struct sg_iovec32 {
|
||||
compat_uint_t iov_base;
|
||||
compat_uint_t iov_len;
|
||||
} sg_iovec32_t;
|
||||
|
||||
static int sg_build_iovec(sg_io_hdr_t __user *sgio, void __user *dxferp, u16 iovec_count)
|
||||
{
|
||||
sg_iovec_t __user *iov = (sg_iovec_t __user *) (sgio + 1);
|
||||
sg_iovec32_t __user *iov32 = dxferp;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < iovec_count; i++) {
|
||||
u32 base, len;
|
||||
|
||||
if (get_user(base, &iov32[i].iov_base) ||
|
||||
get_user(len, &iov32[i].iov_len) ||
|
||||
put_user(compat_ptr(base), &iov[i].iov_base) ||
|
||||
put_user(len, &iov[i].iov_len))
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (put_user(iov, &sgio->dxferp))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sg_ioctl_trans(struct file *file, unsigned int cmd,
|
||||
sg_io_hdr32_t __user *sgio32)
|
||||
{
|
||||
sg_io_hdr_t __user *sgio;
|
||||
u16 iovec_count;
|
||||
u32 data;
|
||||
void __user *dxferp;
|
||||
int err;
|
||||
int interface_id;
|
||||
|
||||
if (get_user(interface_id, &sgio32->interface_id))
|
||||
return -EFAULT;
|
||||
if (interface_id != 'S')
|
||||
return do_ioctl(file, cmd, (unsigned long)sgio32);
|
||||
|
||||
if (get_user(iovec_count, &sgio32->iovec_count))
|
||||
return -EFAULT;
|
||||
|
||||
{
|
||||
void __user *top = compat_alloc_user_space(0);
|
||||
void __user *new = compat_alloc_user_space(sizeof(sg_io_hdr_t) +
|
||||
(iovec_count * sizeof(sg_iovec_t)));
|
||||
if (new > top)
|
||||
return -EINVAL;
|
||||
|
||||
sgio = new;
|
||||
}
|
||||
|
||||
/* Ok, now construct. */
|
||||
if (copy_in_user(&sgio->interface_id, &sgio32->interface_id,
|
||||
(2 * sizeof(int)) +
|
||||
(2 * sizeof(unsigned char)) +
|
||||
(1 * sizeof(unsigned short)) +
|
||||
(1 * sizeof(unsigned int))))
|
||||
return -EFAULT;
|
||||
|
||||
if (get_user(data, &sgio32->dxferp))
|
||||
return -EFAULT;
|
||||
dxferp = compat_ptr(data);
|
||||
if (iovec_count) {
|
||||
if (sg_build_iovec(sgio, dxferp, iovec_count))
|
||||
return -EFAULT;
|
||||
} else {
|
||||
if (put_user(dxferp, &sgio->dxferp))
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
{
|
||||
unsigned char __user *cmdp;
|
||||
unsigned char __user *sbp;
|
||||
|
||||
if (get_user(data, &sgio32->cmdp))
|
||||
return -EFAULT;
|
||||
cmdp = compat_ptr(data);
|
||||
|
||||
if (get_user(data, &sgio32->sbp))
|
||||
return -EFAULT;
|
||||
sbp = compat_ptr(data);
|
||||
|
||||
if (put_user(cmdp, &sgio->cmdp) ||
|
||||
put_user(sbp, &sgio->sbp))
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (copy_in_user(&sgio->timeout, &sgio32->timeout,
|
||||
3 * sizeof(int)))
|
||||
return -EFAULT;
|
||||
|
||||
if (get_user(data, &sgio32->usr_ptr))
|
||||
return -EFAULT;
|
||||
if (put_user(compat_ptr(data), &sgio->usr_ptr))
|
||||
return -EFAULT;
|
||||
|
||||
err = do_ioctl(file, cmd, (unsigned long) sgio);
|
||||
|
||||
if (err >= 0) {
|
||||
void __user *datap;
|
||||
|
||||
if (copy_in_user(&sgio32->pack_id, &sgio->pack_id,
|
||||
sizeof(int)) ||
|
||||
get_user(datap, &sgio->usr_ptr) ||
|
||||
put_user((u32)(unsigned long)datap,
|
||||
&sgio32->usr_ptr) ||
|
||||
copy_in_user(&sgio32->status, &sgio->status,
|
||||
(4 * sizeof(unsigned char)) +
|
||||
(2 * sizeof(unsigned short)) +
|
||||
(3 * sizeof(int))))
|
||||
err = -EFAULT;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
struct compat_sg_req_info { /* used by SG_GET_REQUEST_TABLE ioctl() */
|
||||
char req_state;
|
||||
char orphan;
|
||||
@@ -358,6 +213,7 @@ COMPATIBLE_IOCTL(SCSI_IOCTL_GET_PCI)
|
||||
#endif
|
||||
#ifdef CONFIG_BLOCK
|
||||
/* SG stuff */
|
||||
COMPATIBLE_IOCTL(SG_IO)
|
||||
COMPATIBLE_IOCTL(SG_SET_TIMEOUT)
|
||||
COMPATIBLE_IOCTL(SG_GET_TIMEOUT)
|
||||
COMPATIBLE_IOCTL(SG_EMULATED_HOST)
|
||||
@@ -435,8 +291,6 @@ static long do_ioctl_trans(unsigned int cmd,
|
||||
case PPPIOCSACTIVE32:
|
||||
return ppp_sock_fprog_ioctl_trans(file, cmd, argp);
|
||||
#ifdef CONFIG_BLOCK
|
||||
case SG_IO:
|
||||
return sg_ioctl_trans(file, cmd, argp);
|
||||
case SG_GET_REQUEST_TABLE:
|
||||
return sg_grt_trans(file, cmd, argp);
|
||||
#endif
|
||||
|
Reference in New Issue
Block a user