Skip to content

Commit

Permalink
[SCSI] Fix device removal NULL pointer dereference
Browse files Browse the repository at this point in the history
Use blk_queue_dead() to test whether the queue is dead instead
of !sdev. Since scsi_prep_fn() may be invoked concurrently with
__scsi_remove_device(), keep the queuedata (sdev) pointer in
__scsi_remove_device(). This patch fixes a kernel oops that
can be triggered by USB device removal. See also
http://www.spinics.net/lists/linux-scsi/msg56254.html.

Other changes included in this patch:
- Swap the blk_cleanup_queue() and kfree() calls in
  scsi_host_dev_release() to make that code easier to grasp.
- Remove the queue dead check from scsi_run_queue() since the
  queue state can change anyway at any point in that function
  where the queue lock is not held.
- Remove the queue dead check from the start of scsi_request_fn()
  since it is redundant with the scsi_device_online() check.

Reported-by: Jun'ichi Nomura <j-nomura@ce.jp.nec.com>
Signed-off-by: Bart Van Assche <bvanassche@acm.org>
Reviewed-by: Mike Christie <michaelc@cs.wisc.edu>
Reviewed-by: Tejun Heo <tj@kernel.org>
Cc: <stable@kernel.org>
Signed-off-by: James Bottomley <JBottomley@Parallels.com>
  • Loading branch information
Bart Van Assche authored and James Bottomley committed Jul 20, 2012
1 parent e81ca6f commit 67bd941
Show file tree
Hide file tree
Showing 4 changed files with 9 additions and 36 deletions.
7 changes: 4 additions & 3 deletions drivers/scsi/hosts.c
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ static void scsi_host_dev_release(struct device *dev)
struct Scsi_Host *shost = dev_to_shost(dev);
struct device *parent = dev->parent;
struct request_queue *q;
void *queuedata;

scsi_proc_hostdir_rm(shost->hostt);

Expand All @@ -299,9 +300,9 @@ static void scsi_host_dev_release(struct device *dev)
destroy_workqueue(shost->work_q);
q = shost->uspace_req_q;
if (q) {
kfree(q->queuedata);
q->queuedata = NULL;
scsi_free_queue(q);
queuedata = q->queuedata;
blk_cleanup_queue(q);
kfree(queuedata);
}

scsi_destroy_command_freelist(shost);
Expand Down
32 changes: 4 additions & 28 deletions drivers/scsi/scsi_lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -406,10 +406,6 @@ static void scsi_run_queue(struct request_queue *q)
LIST_HEAD(starved_list);
unsigned long flags;

/* if the device is dead, sdev will be NULL, so no queue to run */
if (!sdev)
return;

shost = sdev->host;
if (scsi_target(sdev)->single_lun)
scsi_single_lun_run(sdev);
Expand Down Expand Up @@ -1371,16 +1367,16 @@ static inline int scsi_host_queue_ready(struct request_queue *q,
* may be changed after request stacking drivers call the function,
* regardless of taking lock or not.
*
* When scsi can't dispatch I/Os anymore and needs to kill I/Os
* (e.g. !sdev), scsi needs to return 'not busy'.
* Otherwise, request stacking drivers may hold requests forever.
* When scsi can't dispatch I/Os anymore and needs to kill I/Os scsi
* needs to return 'not busy'. Otherwise, request stacking drivers
* may hold requests forever.
*/
static int scsi_lld_busy(struct request_queue *q)
{
struct scsi_device *sdev = q->queuedata;
struct Scsi_Host *shost;

if (!sdev)
if (blk_queue_dead(q))
return 0;

shost = sdev->host;
Expand Down Expand Up @@ -1491,12 +1487,6 @@ static void scsi_request_fn(struct request_queue *q)
struct scsi_cmnd *cmd;
struct request *req;

if (!sdev) {
while ((req = blk_peek_request(q)) != NULL)
scsi_kill_request(req, q);
return;
}

if(!get_device(&sdev->sdev_gendev))
/* We must be tearing the block queue down already */
return;
Expand Down Expand Up @@ -1698,20 +1688,6 @@ struct request_queue *scsi_alloc_queue(struct scsi_device *sdev)
return q;
}

void scsi_free_queue(struct request_queue *q)
{
unsigned long flags;

WARN_ON(q->queuedata);

/* cause scsi_request_fn() to kill all non-finished requests */
spin_lock_irqsave(q->queue_lock, flags);
q->request_fn(q);
spin_unlock_irqrestore(q->queue_lock, flags);

blk_cleanup_queue(q);
}

/*
* Function: scsi_block_requests()
*
Expand Down
1 change: 0 additions & 1 deletion drivers/scsi/scsi_priv.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ extern void scsi_next_command(struct scsi_cmnd *cmd);
extern void scsi_io_completion(struct scsi_cmnd *, unsigned int);
extern void scsi_run_host_queues(struct Scsi_Host *shost);
extern struct request_queue *scsi_alloc_queue(struct scsi_device *sdev);
extern void scsi_free_queue(struct request_queue *q);
extern int scsi_init_queue(void);
extern void scsi_exit_queue(void);
struct request_queue;
Expand Down
5 changes: 1 addition & 4 deletions drivers/scsi/scsi_sysfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -972,11 +972,8 @@ void __scsi_remove_device(struct scsi_device *sdev)
sdev->host->hostt->slave_destroy(sdev);
transport_destroy_device(dev);

/* cause the request function to reject all I/O requests */
sdev->request_queue->queuedata = NULL;

/* Freeing the queue signals to block that we're done */
scsi_free_queue(sdev->request_queue);
blk_cleanup_queue(sdev->request_queue);
put_device(dev);
}

Expand Down

0 comments on commit 67bd941

Please sign in to comment.