scsi: Fix use-after-free
[pandora-kernel.git] / drivers / scsi / scsi_scan.c
index adfbf2c..5d207f1 100644 (file)
@@ -332,6 +332,7 @@ static void scsi_target_destroy(struct scsi_target *starget)
        struct Scsi_Host *shost = dev_to_shost(dev->parent);
        unsigned long flags;
 
+       starget->state = STARGET_DEL;
        transport_destroy_device(dev);
        spin_lock_irqsave(shost->host_lock, flags);
        if (shost->hostt->target_destroy)
@@ -396,9 +397,15 @@ static void scsi_target_reap_ref_release(struct kref *kref)
        struct scsi_target *starget
                = container_of(kref, struct scsi_target, reap_ref);
 
-       transport_remove_device(&starget->dev);
-       device_del(&starget->dev);
-       starget->state = STARGET_DEL;
+       /*
+        * if we get here and the target is still in the CREATED state that
+        * means it was allocated but never made visible (because a scan
+        * turned up no LUNs), so don't call device_del() on it.
+        */
+       if (starget->state != STARGET_CREATED) {
+               transport_remove_device(&starget->dev);
+               device_del(&starget->dev);
+       }
        scsi_target_destroy(starget);
 }
 
@@ -518,11 +525,13 @@ static struct scsi_target *scsi_alloc_target(struct device *parent,
  */
 void scsi_target_reap(struct scsi_target *starget)
 {
+       /*
+        * serious problem if this triggers: STARGET_DEL is only set in the if
+        * the reap_ref drops to zero, so we're trying to do another final put
+        * on an already released kref
+        */
        BUG_ON(starget->state == STARGET_DEL);
-       if (starget->state == STARGET_CREATED)
-               scsi_target_destroy(starget);
-       else
-               scsi_target_reap_ref_put(starget);
+       scsi_target_reap_ref_put(starget);
 }
 
 /**
@@ -1513,12 +1522,12 @@ static int scsi_report_lun_scan(struct scsi_target *starget, int bflags,
  out_err:
        kfree(lun_data);
  out:
-       scsi_device_put(sdev);
        if (scsi_device_created(sdev))
                /*
                 * the sdev we used didn't appear in the report luns scan
                 */
                __scsi_remove_device(sdev);
+       scsi_device_put(sdev);
        return ret;
 }