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:
@@ -2,6 +2,7 @@
|
||||
/*
|
||||
* Copyright (C) 2001 Jens Axboe <axboe@suse.de>
|
||||
*/
|
||||
#include <linux/compat.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/string.h>
|
||||
@@ -327,7 +328,14 @@ static int sg_io(struct request_queue *q, struct gendisk *bd_disk,
|
||||
struct iov_iter i;
|
||||
struct iovec *iov = NULL;
|
||||
|
||||
ret = import_iovec(rq_data_dir(rq),
|
||||
#ifdef CONFIG_COMPAT
|
||||
if (in_compat_syscall())
|
||||
ret = compat_import_iovec(rq_data_dir(rq),
|
||||
hdr->dxferp, hdr->iovec_count,
|
||||
0, &iov, &i);
|
||||
else
|
||||
#endif
|
||||
ret = import_iovec(rq_data_dir(rq),
|
||||
hdr->dxferp, hdr->iovec_count,
|
||||
0, &iov, &i);
|
||||
if (ret < 0)
|
||||
@@ -542,6 +550,122 @@ static inline int blk_send_start_stop(struct request_queue *q,
|
||||
return __blk_send_generic(q, bd_disk, GPCMD_START_STOP_UNIT, data);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
struct compat_sg_io_hdr {
|
||||
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 */
|
||||
};
|
||||
#endif
|
||||
|
||||
int put_sg_io_hdr(const struct sg_io_hdr *hdr, void __user *argp)
|
||||
{
|
||||
#ifdef CONFIG_COMPAT
|
||||
if (in_compat_syscall()) {
|
||||
struct compat_sg_io_hdr hdr32 = {
|
||||
.interface_id = hdr->interface_id,
|
||||
.dxfer_direction = hdr->dxfer_direction,
|
||||
.cmd_len = hdr->cmd_len,
|
||||
.mx_sb_len = hdr->mx_sb_len,
|
||||
.iovec_count = hdr->iovec_count,
|
||||
.dxfer_len = hdr->dxfer_len,
|
||||
.dxferp = (uintptr_t)hdr->dxferp,
|
||||
.cmdp = (uintptr_t)hdr->cmdp,
|
||||
.sbp = (uintptr_t)hdr->sbp,
|
||||
.timeout = hdr->timeout,
|
||||
.flags = hdr->flags,
|
||||
.pack_id = hdr->pack_id,
|
||||
.usr_ptr = (uintptr_t)hdr->usr_ptr,
|
||||
.status = hdr->status,
|
||||
.masked_status = hdr->masked_status,
|
||||
.msg_status = hdr->msg_status,
|
||||
.sb_len_wr = hdr->sb_len_wr,
|
||||
.host_status = hdr->host_status,
|
||||
.driver_status = hdr->driver_status,
|
||||
.resid = hdr->resid,
|
||||
.duration = hdr->duration,
|
||||
.info = hdr->info,
|
||||
};
|
||||
|
||||
if (copy_to_user(argp, &hdr32, sizeof(hdr32)))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (copy_to_user(argp, hdr, sizeof(*hdr)))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(put_sg_io_hdr);
|
||||
|
||||
int get_sg_io_hdr(struct sg_io_hdr *hdr, const void __user *argp)
|
||||
{
|
||||
#ifdef CONFIG_COMPAT
|
||||
struct compat_sg_io_hdr hdr32;
|
||||
|
||||
if (in_compat_syscall()) {
|
||||
if (copy_from_user(&hdr32, argp, sizeof(hdr32)))
|
||||
return -EFAULT;
|
||||
|
||||
*hdr = (struct sg_io_hdr) {
|
||||
.interface_id = hdr32.interface_id,
|
||||
.dxfer_direction = hdr32.dxfer_direction,
|
||||
.cmd_len = hdr32.cmd_len,
|
||||
.mx_sb_len = hdr32.mx_sb_len,
|
||||
.iovec_count = hdr32.iovec_count,
|
||||
.dxfer_len = hdr32.dxfer_len,
|
||||
.dxferp = compat_ptr(hdr32.dxferp),
|
||||
.cmdp = compat_ptr(hdr32.cmdp),
|
||||
.sbp = compat_ptr(hdr32.sbp),
|
||||
.timeout = hdr32.timeout,
|
||||
.flags = hdr32.flags,
|
||||
.pack_id = hdr32.pack_id,
|
||||
.usr_ptr = compat_ptr(hdr32.usr_ptr),
|
||||
.status = hdr32.status,
|
||||
.masked_status = hdr32.masked_status,
|
||||
.msg_status = hdr32.msg_status,
|
||||
.sb_len_wr = hdr32.sb_len_wr,
|
||||
.host_status = hdr32.host_status,
|
||||
.driver_status = hdr32.driver_status,
|
||||
.resid = hdr32.resid,
|
||||
.duration = hdr32.duration,
|
||||
.info = hdr32.info,
|
||||
};
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (copy_from_user(hdr, argp, sizeof(*hdr)))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(get_sg_io_hdr);
|
||||
|
||||
int scsi_cmd_ioctl(struct request_queue *q, struct gendisk *bd_disk, fmode_t mode,
|
||||
unsigned int cmd, void __user *arg)
|
||||
{
|
||||
@@ -581,14 +705,14 @@ int scsi_cmd_ioctl(struct request_queue *q, struct gendisk *bd_disk, fmode_t mod
|
||||
case SG_IO: {
|
||||
struct sg_io_hdr hdr;
|
||||
|
||||
err = -EFAULT;
|
||||
if (copy_from_user(&hdr, arg, sizeof(hdr)))
|
||||
err = get_sg_io_hdr(&hdr, arg);
|
||||
if (err)
|
||||
break;
|
||||
err = sg_io(q, bd_disk, &hdr, mode);
|
||||
if (err == -EFAULT)
|
||||
break;
|
||||
|
||||
if (copy_to_user(arg, &hdr, sizeof(hdr)))
|
||||
if (put_sg_io_hdr(&hdr, arg))
|
||||
err = -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user