scsi: qla2xxx: Fix double scsi_done for abort path

Current code assumes abort will remove the original command from the active
list where scsi_done will not be called. Instead, the eh_abort thread will
do the scsi_done. That is not the case.  Instead, we have a double
scsi_done calls triggering use after free.

Abort will tell FW to release the command from FW possesion. The original
command will return to ULP with error in its normal fashion via scsi_done.
eh_abort path would wait for the original command completion before
returning.  eh_abort path will not perform the scsi_done call.

Fixes: 219d27d714 ("scsi: qla2xxx: Fix race conditions in the code for aborting SCSI commands")
Cc: stable@vger.kernel.org # 5.2
Link: https://lore.kernel.org/r/20191105150657.8092-6-hmadhani@marvell.com
Reviewed-by: Ewan D. Milne <emilne@redhat.com>
Signed-off-by: Quinn Tran <qutran@marvell.com>
Signed-off-by: Arun Easi <aeasi@marvell.com>
Signed-off-by: Himanshu Madhani <hmadhani@marvell.com>
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
This commit is contained in:
Quinn Tran 2019-11-05 07:06:54 -08:00 committed by Martin K. Petersen
parent dd322b7f3e
commit f45bca8c50
4 changed files with 74 additions and 61 deletions

View File

@ -591,13 +591,16 @@ typedef struct srb {
*/
uint8_t cmd_type;
uint8_t pad[3];
atomic_t ref_count;
struct kref cmd_kref; /* need to migrate ref_count over to this */
void *priv;
wait_queue_head_t nvme_ls_waitq;
struct fc_port *fcport;
struct scsi_qla_host *vha;
unsigned int start_timer:1;
unsigned int abort:1;
unsigned int aborted:1;
unsigned int completed:1;
uint32_t handle;
uint16_t flags;
uint16_t type;

View File

@ -2487,6 +2487,11 @@ qla2x00_status_entry(scsi_qla_host_t *vha, struct rsp_que *rsp, void *pkt)
return;
}
if (sp->abort)
sp->aborted = 1;
else
sp->completed = 1;
if (sp->cmd_type != TYPE_SRB) {
req->outstanding_cmds[handle] = NULL;
ql_dbg(ql_dbg_io, vha, 0x3015,

View File

@ -224,8 +224,8 @@ static void qla_nvme_abort_work(struct work_struct *work)
if (ha->flags.host_shutting_down) {
ql_log(ql_log_info, sp->fcport->vha, 0xffff,
"%s Calling done on sp: %p, type: 0x%x, sp->ref_count: 0x%x\n",
__func__, sp, sp->type, atomic_read(&sp->ref_count));
"%s Calling done on sp: %p, type: 0x%x\n",
__func__, sp, sp->type);
sp->done(sp, 0);
goto out;
}

View File

@ -698,11 +698,6 @@ void qla2x00_sp_compl(srb_t *sp, int res)
struct scsi_cmnd *cmd = GET_CMD_SP(sp);
struct completion *comp = sp->comp;
if (WARN_ON_ONCE(atomic_read(&sp->ref_count) == 0))
return;
atomic_dec(&sp->ref_count);
sp->free(sp);
cmd->result = res;
CMD_SP(cmd) = NULL;
@ -794,11 +789,6 @@ void qla2xxx_qpair_sp_compl(srb_t *sp, int res)
struct scsi_cmnd *cmd = GET_CMD_SP(sp);
struct completion *comp = sp->comp;
if (WARN_ON_ONCE(atomic_read(&sp->ref_count) == 0))
return;
atomic_dec(&sp->ref_count);
sp->free(sp);
cmd->result = res;
CMD_SP(cmd) = NULL;
@ -903,7 +893,7 @@ qla2xxx_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *cmd)
sp->u.scmd.cmd = cmd;
sp->type = SRB_SCSI_CMD;
atomic_set(&sp->ref_count, 1);
CMD_SP(cmd) = (void *)sp;
sp->free = qla2x00_sp_free_dma;
sp->done = qla2x00_sp_compl;
@ -985,11 +975,9 @@ qla2xxx_mqueuecommand(struct Scsi_Host *host, struct scsi_cmnd *cmd,
sp->u.scmd.cmd = cmd;
sp->type = SRB_SCSI_CMD;
atomic_set(&sp->ref_count, 1);
CMD_SP(cmd) = (void *)sp;
sp->free = qla2xxx_qpair_sp_free_dma;
sp->done = qla2xxx_qpair_sp_compl;
sp->qpair = qpair;
rval = ha->isp_ops->start_scsi_mq(sp);
if (rval != QLA_SUCCESS) {
@ -1187,16 +1175,6 @@ qla2x00_wait_for_chip_reset(scsi_qla_host_t *vha)
return return_status;
}
static int
sp_get(struct srb *sp)
{
if (!refcount_inc_not_zero((refcount_t *)&sp->ref_count))
/* kref get fail */
return ENXIO;
else
return 0;
}
#define ISP_REG_DISCONNECT 0xffffffffU
/**************************************************************************
* qla2x00_isp_reg_stat
@ -1252,6 +1230,9 @@ qla2xxx_eh_abort(struct scsi_cmnd *cmd)
uint64_t lun;
int rval;
struct qla_hw_data *ha = vha->hw;
uint32_t ratov_j;
struct qla_qpair *qpair;
unsigned long flags;
if (qla2x00_isp_reg_stat(ha)) {
ql_log(ql_log_info, vha, 0x8042,
@ -1264,13 +1245,26 @@ qla2xxx_eh_abort(struct scsi_cmnd *cmd)
return ret;
sp = scsi_cmd_priv(cmd);
qpair = sp->qpair;
if (sp->fcport && sp->fcport->deleted)
if ((sp->fcport && sp->fcport->deleted) || !qpair)
return SUCCESS;
/* Return if the command has already finished. */
if (sp_get(sp))
spin_lock_irqsave(qpair->qp_lock_ptr, flags);
if (sp->completed) {
spin_unlock_irqrestore(qpair->qp_lock_ptr, flags);
return SUCCESS;
}
if (sp->abort || sp->aborted) {
spin_unlock_irqrestore(qpair->qp_lock_ptr, flags);
return FAILED;
}
sp->abort = 1;
sp->comp = &comp;
spin_unlock_irqrestore(qpair->qp_lock_ptr, flags);
id = cmd->device->id;
lun = cmd->device->lun;
@ -1279,47 +1273,37 @@ qla2xxx_eh_abort(struct scsi_cmnd *cmd)
"Aborting from RISC nexus=%ld:%d:%llu sp=%p cmd=%p handle=%x\n",
vha->host_no, id, lun, sp, cmd, sp->handle);
/*
* Abort will release the original Command/sp from FW. Let the
* original command call scsi_done. In return, he will wakeup
* this sleeping thread.
*/
rval = ha->isp_ops->abort_command(sp);
ql_dbg(ql_dbg_taskm, vha, 0x8003,
"Abort command mbx cmd=%p, rval=%x.\n", cmd, rval);
/* Wait for the command completion. */
ratov_j = ha->r_a_tov/10 * 4 * 1000;
ratov_j = msecs_to_jiffies(ratov_j);
switch (rval) {
case QLA_SUCCESS:
/*
* The command has been aborted. That means that the firmware
* won't report a completion.
*/
sp->done(sp, DID_ABORT << 16);
ret = SUCCESS;
break;
case QLA_FUNCTION_PARAMETER_ERROR: {
/* Wait for the command completion. */
uint32_t ratov = ha->r_a_tov/10;
uint32_t ratov_j = msecs_to_jiffies(4 * ratov * 1000);
WARN_ON_ONCE(sp->comp);
sp->comp = &comp;
if (!wait_for_completion_timeout(&comp, ratov_j)) {
ql_dbg(ql_dbg_taskm, vha, 0xffff,
"%s: Abort wait timer (4 * R_A_TOV[%d]) expired\n",
__func__, ha->r_a_tov);
__func__, ha->r_a_tov/10);
ret = FAILED;
} else {
ret = SUCCESS;
}
break;
}
default:
/*
* Either abort failed or abort and completion raced. Let
* the SCSI core retry the abort in the former case.
*/
ret = FAILED;
break;
}
sp->comp = NULL;
atomic_dec(&sp->ref_count);
ql_log(ql_log_info, vha, 0x801c,
"Abort command issued nexus=%ld:%d:%llu -- %x.\n",
vha->host_no, id, lun, ret);
@ -1711,32 +1695,53 @@ static void qla2x00_abort_srb(struct qla_qpair *qp, srb_t *sp, const int res,
scsi_qla_host_t *vha = qp->vha;
struct qla_hw_data *ha = vha->hw;
int rval;
bool ret_cmd;
uint32_t ratov_j;
if (sp_get(sp))
if (qla2x00_chip_is_down(vha)) {
sp->done(sp, res);
return;
}
if (sp->type == SRB_NVME_CMD || sp->type == SRB_NVME_LS ||
(sp->type == SRB_SCSI_CMD && !ha->flags.eeh_busy &&
!test_bit(ABORT_ISP_ACTIVE, &vha->dpc_flags) &&
!qla2x00_isp_reg_stat(ha))) {
sp->comp = &comp;
spin_unlock_irqrestore(qp->qp_lock_ptr, *flags);
rval = ha->isp_ops->abort_command(sp);
if (sp->comp) {
sp->done(sp, res);
return;
}
sp->comp = &comp;
sp->abort = 1;
spin_unlock_irqrestore(qp->qp_lock_ptr, *flags);
rval = ha->isp_ops->abort_command(sp);
/* Wait for command completion. */
ret_cmd = false;
ratov_j = ha->r_a_tov/10 * 4 * 1000;
ratov_j = msecs_to_jiffies(ratov_j);
switch (rval) {
case QLA_SUCCESS:
sp->done(sp, res);
if (wait_for_completion_timeout(&comp, ratov_j)) {
ql_dbg(ql_dbg_taskm, vha, 0xffff,
"%s: Abort wait timer (4 * R_A_TOV[%d]) expired\n",
__func__, ha->r_a_tov/10);
ret_cmd = true;
}
/* else FW return SP to driver */
break;
case QLA_FUNCTION_PARAMETER_ERROR:
wait_for_completion(&comp);
default:
ret_cmd = true;
break;
}
spin_lock_irqsave(qp->qp_lock_ptr, *flags);
sp->comp = NULL;
if (ret_cmd && (!sp->completed || !sp->aborted))
sp->done(sp, res);
} else {
sp->done(sp, res);
}
atomic_dec(&sp->ref_count);
}
static void
@ -1758,7 +1763,6 @@ __qla2x00_abort_all_cmds(struct qla_qpair *qp, int res)
for (cnt = 1; cnt < req->num_outstanding_cmds; cnt++) {
sp = req->outstanding_cmds[cnt];
if (sp) {
req->outstanding_cmds[cnt] = NULL;
switch (sp->cmd_type) {
case TYPE_SRB:
qla2x00_abort_srb(qp, sp, res, &flags);
@ -1780,6 +1784,7 @@ __qla2x00_abort_all_cmds(struct qla_qpair *qp, int res)
default:
break;
}
req->outstanding_cmds[cnt] = NULL;
}
}
spin_unlock_irqrestore(qp->qp_lock_ptr, flags);