Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 22032991 authored by Bart Van Assche's avatar Bart Van Assche Committed by Roland Dreier
Browse files

IB/srp: Fix a race condition



Avoid a crash caused by the scmnd->scsi_done(scmnd) call in
srp_process_rsp() being invoked with scsi_done == NULL.  This can
happen if a reply is received during or after a command abort.

Reported-by: default avatarJoseph Glanville <joseph.glanville@orionvm.com.au>
Reference: http://marc.info/?l=linux-rdma&m=134314367801595


Cc: <stable@vger.kernel.org>
Acked-by: default avatarDavid Dillow <dillowda@ornl.gov>
Signed-off-by: default avatarBart Van Assche <bvanassche@acm.org>
Signed-off-by: default avatarRoland Dreier <roland@purestorage.com>
parent 0d7614f0
Loading
Loading
Loading
Loading
+63 −24
Original line number Diff line number Diff line
@@ -586,24 +586,62 @@ static void srp_unmap_data(struct scsi_cmnd *scmnd,
			scmnd->sc_data_direction);
}

static void srp_remove_req(struct srp_target_port *target,
			   struct srp_request *req, s32 req_lim_delta)
/**
 * srp_claim_req - Take ownership of the scmnd associated with a request.
 * @target: SRP target port.
 * @req: SRP request.
 * @scmnd: If NULL, take ownership of @req->scmnd. If not NULL, only take
 *         ownership of @req->scmnd if it equals @scmnd.
 *
 * Return value:
 * Either NULL or a pointer to the SCSI command the caller became owner of.
 */
static struct scsi_cmnd *srp_claim_req(struct srp_target_port *target,
				       struct srp_request *req,
				       struct scsi_cmnd *scmnd)
{
	unsigned long flags;

	srp_unmap_data(req->scmnd, target, req);
	spin_lock_irqsave(&target->lock, flags);
	target->req_lim += req_lim_delta;
	if (!scmnd) {
		scmnd = req->scmnd;
		req->scmnd = NULL;
	} else if (req->scmnd == scmnd) {
		req->scmnd = NULL;
	} else {
		scmnd = NULL;
	}
	spin_unlock_irqrestore(&target->lock, flags);

	return scmnd;
}

/**
 * srp_free_req() - Unmap data and add request to the free request list.
 */
static void srp_free_req(struct srp_target_port *target,
			 struct srp_request *req, struct scsi_cmnd *scmnd,
			 s32 req_lim_delta)
{
	unsigned long flags;

	srp_unmap_data(scmnd, target, req);

	spin_lock_irqsave(&target->lock, flags);
	target->req_lim += req_lim_delta;
	list_add_tail(&req->list, &target->free_reqs);
	spin_unlock_irqrestore(&target->lock, flags);
}

static void srp_reset_req(struct srp_target_port *target, struct srp_request *req)
{
	req->scmnd->result = DID_RESET << 16;
	req->scmnd->scsi_done(req->scmnd);
	srp_remove_req(target, req, 0);
	struct scsi_cmnd *scmnd = srp_claim_req(target, req, NULL);

	if (scmnd) {
		scmnd->result = DID_RESET << 16;
		scmnd->scsi_done(scmnd);
		srp_free_req(target, req, scmnd, 0);
	}
}

static int srp_reconnect_target(struct srp_target_port *target)
@@ -1073,11 +1111,18 @@ static void srp_process_rsp(struct srp_target_port *target, struct srp_rsp *rsp)
		complete(&target->tsk_mgmt_done);
	} else {
		req = &target->req_ring[rsp->tag];
		scmnd = req->scmnd;
		if (!scmnd)
		scmnd = srp_claim_req(target, req, NULL);
		if (!scmnd) {
			shost_printk(KERN_ERR, target->scsi_host,
				     "Null scmnd for RSP w/tag %016llx\n",
				     (unsigned long long) rsp->tag);

			spin_lock_irqsave(&target->lock, flags);
			target->req_lim += be32_to_cpu(rsp->req_lim_delta);
			spin_unlock_irqrestore(&target->lock, flags);

			return;
		}
		scmnd->result = rsp->status;

		if (rsp->flags & SRP_RSP_FLAG_SNSVALID) {
@@ -1092,7 +1137,9 @@ static void srp_process_rsp(struct srp_target_port *target, struct srp_rsp *rsp)
		else if (rsp->flags & (SRP_RSP_FLAG_DIOVER | SRP_RSP_FLAG_DIUNDER))
			scsi_set_resid(scmnd, be32_to_cpu(rsp->data_in_res_cnt));

		srp_remove_req(target, req, be32_to_cpu(rsp->req_lim_delta));
		srp_free_req(target, req, scmnd,
			     be32_to_cpu(rsp->req_lim_delta));

		scmnd->host_scribble = NULL;
		scmnd->scsi_done(scmnd);
	}
@@ -1631,25 +1678,17 @@ static int srp_abort(struct scsi_cmnd *scmnd)
{
	struct srp_target_port *target = host_to_target(scmnd->device->host);
	struct srp_request *req = (struct srp_request *) scmnd->host_scribble;
	int ret = SUCCESS;

	shost_printk(KERN_ERR, target->scsi_host, "SRP abort called\n");

	if (!req || target->qp_in_error)
	if (!req || target->qp_in_error || !srp_claim_req(target, req, scmnd))
		return FAILED;
	if (srp_send_tsk_mgmt(target, req->index, scmnd->device->lun,
			      SRP_TSK_ABORT_TASK))
		return FAILED;

	if (req->scmnd) {
		if (!target->tsk_mgmt_status) {
			srp_remove_req(target, req, 0);
	srp_send_tsk_mgmt(target, req->index, scmnd->device->lun,
			  SRP_TSK_ABORT_TASK);
	srp_free_req(target, req, scmnd, 0);
	scmnd->result = DID_ABORT << 16;
		} else
			ret = FAILED;
	}

	return ret;
	return SUCCESS;
}

static int srp_reset_device(struct scsi_cmnd *scmnd)