]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
scsi: xen: scsiback: Free unsubmitted command instead of double-putting it
authorMichael Bommarito <michael.bommarito@gmail.com>
Thu, 11 Jun 2026 12:30:45 +0000 (08:30 -0400)
committerMartin K. Petersen <martin.petersen@oracle.com>
Tue, 16 Jun 2026 01:52:39 +0000 (21:52 -0400)
scsiback_get_pend_req() obtains a command tag and returns a vscsibk_pend
whose embedded se_cmd has only been memset to 0, so its cmd_kref is 0;
the se_cmd is initialised (kref_init() via target_init_cmd()) only
later, in scsiback_cmd_exec(), on the successful VSCSIIF_ACT_SCSI_CDB
path. The two error paths in scsiback_do_cmd_fn() taken before the
command is submitted -- a failed scsiback_gnttab_data_map() and an
unknown ring_req.act -- call
transport_generic_free_cmd(&pending_req->se_cmd, 0), which kref_put()s a
refcount of 0. That underflows it ("refcount_t: underflow;
use-after-free") and, as the release function is not run, leaks the
command tag.

Impact: a pvSCSI guest can leak every command tag of a LUN's session,
stopping the LUN, by submitting requests with a bad grant reference or
an unknown request type; under panic_on_warn the refcount underflow
panics the host.

Add a helper that just returns the tag with target_free_tag() and sends
the error response. It frees the tag while the v2p reference still pins
the session, and snapshots the response fields beforehand because
freeing the tag can let another ring reuse the pending_req slot.

Fixes: 2dbcdf33dbf6 ("xen-scsiback: Convert to percpu_ida tag allocation")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
Reviewed-by: Juergen Gross <jgross@suse.com>
Link: https://patch.msgid.link/20260611123046.2323342-2-michael.bommarito@gmail.com
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
drivers/xen/xen-scsiback.c

index e33f95c91b096b4d855cf37feb3505444e32ca24..f324732eba7f85c5b57ebbe76f2cc5fc21cc8e4c 100644 (file)
@@ -611,6 +611,25 @@ static void scsiback_disconnect(struct vscsibk_info *info)
        xenbus_unmap_ring_vfree(info->dev, info->ring.sring);
 }
 
+/*
+ * Send the error response for a request that did not reach the target core
+ * and return its tag.  Free the tag before the response drops the v2p
+ * reference that keeps the session alive, and snapshot what the response
+ * needs since returning the tag can let the slot be reused.
+ */
+static void scsiback_resp_and_free(struct vscsibk_pend *pending_req,
+                                  int32_t result)
+{
+       struct vscsibk_info *info = pending_req->info;
+       struct v2p_entry *v2p = pending_req->v2p;
+       struct se_session *se_sess = v2p->tpg->tpg_nexus->tvn_se_sess;
+       u16 rqid = pending_req->rqid;
+
+       target_free_tag(se_sess, &pending_req->se_cmd);
+       scsiback_send_response(info, NULL, result, 0, rqid);
+       kref_put(&v2p->kref, scsiback_free_translation_entry);
+}
+
 static void scsiback_device_action(struct vscsibk_pend *pending_req,
        enum tcm_tmreq_table act, int tag)
 {
@@ -792,9 +811,8 @@ static int scsiback_do_cmd_fn(struct vscsibk_info *info,
                case VSCSIIF_ACT_SCSI_CDB:
                        if (scsiback_gnttab_data_map(&ring_req, pending_req)) {
                                scsiback_fast_flush_area(pending_req);
-                               scsiback_do_resp_with_sense(NULL,
-                                               DID_ERROR << 16, 0, pending_req);
-                               transport_generic_free_cmd(&pending_req->se_cmd, 0);
+                               scsiback_resp_and_free(pending_req,
+                                                      DID_ERROR << 16);
                        } else {
                                scsiback_cmd_exec(pending_req);
                        }
@@ -808,9 +826,7 @@ static int scsiback_do_cmd_fn(struct vscsibk_info *info,
                        break;
                default:
                        pr_err_ratelimited("invalid request\n");
-                       scsiback_do_resp_with_sense(NULL, DID_ERROR << 16, 0,
-                                                   pending_req);
-                       transport_generic_free_cmd(&pending_req->se_cmd, 0);
+                       scsiback_resp_and_free(pending_req, DID_ERROR << 16);
                        break;
                }