Skip to contentSkip to conversation

scsi: ufs: core: Fix task management completion timeout race

PATCH Superseded Adrian Hunter 2 participants 8 messages Oct 13, 2021 – Oct 15, 2021 SCSI

v1 of 2 Superseded Latest is v2 →

lore
Share & tools
lore
#0 lore raw

cover scsi: ufs: core: Fix task management completion timeout race

Hi

Please consider this for v5.15 fixes.

Adrian Hunter (1):
scsi: ufs: core: Fix task management completion timeout race

 drivers/scsi/ufs/ufshcd.c | 1 -
 1 file changed, 1 deletion(-)

Regards
Adrian

#1 lore raw

PATCH 1/1 scsi: ufs: core: Fix task management completion timeout race

__ufshcd_issue_tm_cmd() clears req->end_io_data after timing out, which races with the completion function ufshcd_tmc_handler() which expects req->end_io_data to have a value.

Note __ufshcd_issue_tm_cmd() and ufshcd_tmc_handler() are already synchronized using hba->tmf_rqs and hba->outstanding_tasks under the host_lock spinlock.

It is also not necessary (nor typical) to clear req->end_io_data because the block layer does it before allocating out requests e.g. via blk_get_request().

So fix by not clearing it.

Fixes f5ef336fd2e4c3 ("scsi: ufs: core: Fix task management completion")
---
 drivers/scsi/ufs/ufshcd.c | 5 -----
 1 file changed, 5 deletions(-)
drivers/scsi/ufs/ufshcd.c +0 -5
index 95be7ecdfe10..f34b3994d1aa 100644
@@ -6550,11 +6550,6 @@ static int __ufshcd_issue_tm_cmd(struct ufs_hba *hba,
 	err = wait_for_completion_io_timeout(&wait,
 			msecs_to_jiffies(TM_CMD_TIMEOUT));
 	if (!err) {
-		/*
-		 * Make sure that ufshcd_compl_tm() does not trigger a
-		 * use-after-free.
-		 */
-		req->end_io_data = NULL;
 		ufshcd_add_tm_upiu_trace(hba, task_tag, UFS_TM_ERR);
 		dev_err(hba->dev, "%s: task management cmd 0x%.2x timed-out\n",
 				__func__, tm_function);
Signature · 1 line
2.25.1
#2 lore raw
Quoted 26 lines · Adrian Hunter

__ufshcd_issue_tm_cmd() clears req->end_io_data after timing out, which races with the completion function ufshcd_tmc_handler() which expects req->end_io_data to have a value.

Note __ufshcd_issue_tm_cmd() and ufshcd_tmc_handler() are already synchronized using hba->tmf_rqs and hba->outstanding_tasks under the host_lock spinlock.

It is also not necessary (nor typical) to clear req->end_io_data because the block layer does it before allocating out requests e.g. via blk_get_request().

So fix by not clearing it.

Fixes f5ef336fd2e4c3 ("scsi: ufs: core: Fix task management completion")
---
  drivers/scsi/ufs/ufshcd.c | 5 -----
  1 file changed, 5 deletions(-)
drivers/scsi/ufs/ufshcd.c +0 -0
index 95be7ecdfe10..f34b3994d1aa 100644
@@ -6550,11 +6550,6 @@ static int __ufshcd_issue_tm_cmd(struct ufs_hba *hba,
  	err = wait_for_completion_io_timeout(&wait,

drivers/scsi/ufs/ufshcd.c static int __ufshcd_issue_tm_cmd(struct ufs_hba *hba, · line 6555

Quoted 10 lines
drivers/scsi/ufs/ufshcd.c +0 -5
  			msecs_to_jiffies(TM_CMD_TIMEOUT));
  	if (!err) {
-		/*
-		 * Make sure that ufshcd_compl_tm() does not trigger a
-		 * use-after-free.
-		 */
-		req->end_io_data = NULL;
  		ufshcd_add_tm_upiu_trace(hba, task_tag, UFS_TM_ERR);
  		dev_err(hba->dev, "%s: task management cmd 0x%.2x timed-out\n",
  				__func__, tm_function);

With this patch applied ufshcd_tmc_handler() can trigger a use-after-free of the stack memory used for the 'wait' completion. Wouldn't it be better to keep the code that clears req->end_io_data and to change complete(c) into if(c) complete(c) in ufshcd_tmc_handler()?

Thanks,

Bart.

#3 lore raw
Quoted 31 lines · Bart Van Assche
Quoted 30 lines · Adrian Hunter

__ufshcd_issue_tm_cmd() clears req->end_io_data after timing out, which races with the completion function ufshcd_tmc_handler() which expects req->end_io_data to have a value.

Note __ufshcd_issue_tm_cmd() and ufshcd_tmc_handler() are already synchronized using hba->tmf_rqs and hba->outstanding_tasks under the host_lock spinlock.

It is also not necessary (nor typical) to clear req->end_io_data because the block layer does it before allocating out requests e.g. via blk_get_request().

So fix by not clearing it.

Fixes f5ef336fd2e4c3 ("scsi: ufs: core: Fix task management completion")

---
  drivers/scsi/ufs/ufshcd.c | 5 -----

  1 file changed, 5 deletions(-)
drivers/scsi/ufs/ufshcd.c +0 -2
index 95be7ecdfe10..f34b3994d1aa 100644
@@ -6550,11 +6550,6 @@ static int __ufshcd_issue_tm_cmd(struct ufs_hba *hba,
       err = wait_for_completion_io_timeout(&wait,
               msecs_to_jiffies(TM_CMD_TIMEOUT));
       if (!err) {
-        /*
-         * Make sure that ufshcd_compl_tm() does not trigger a
Quoted 8 lines

drivers/scsi/ufs/ufshcd.c static int __ufshcd_issue_tm_cmd(struct ufs_hba *hba, · line 6555

Quoted 6 lines
drivers/scsi/ufs/ufshcd.c +0 -3
-         * use-after-free.
-         */
-        req->end_io_data = NULL;
           ufshcd_add_tm_upiu_trace(hba, task_tag, UFS_TM_ERR);
           dev_err(hba->dev, "%s: task management cmd 0x%.2x timed-out\n",
                   __func__, tm_function);

With this patch applied ufshcd_tmc_handler() can trigger a use-after-free of the stack memory used for the 'wait' completion.

AFAICT that would only happen because blk_mq_tagset_busy_iter() is not synchronized with respect to blk_put_request(). But ufshcd_tmc_handler() does not use blk_mq_tagset_busy_iter() anymore, so that can't happen.

Wouldn't it be better to keep the code that clears req->end_io_data and to change complete(c) into if(c) complete(c) in ufshcd_tmc_handler()?

If that were needed, it would imply the synchronization was broken i.e. why are we referencing a request that has already been through blk_put_request()?

#4 lore raw
Quoted 8 lines · Adrian Hunter
Quoted 3 lines · Bart Van Assche

Wouldn't it be better to keep the code that clears req->end_io_data and to change complete(c) into if(c) complete(c) in ufshcd_tmc_handler()?

If that were needed, it would imply the synchronization was broken i.e. why are we referencing a request that has already been through blk_put_request()?

The scenario I'm worried about is as follows:
* __ufshcd_issue_tm_cmd() issues a task management function.
* No completion is received before TM_CMD_TIMEOUT has expired (100 ms).
* ufshcd_clear_tm_cmd() fails.
* The TMF completes, ufshcd_tmc_handler() is called and that function calls complete(req->end_io_data).

Can this happen?

I agree that this scenario involves completion of a request that has already been through blk_put_request().

Thanks,

Bart.

#5 lore raw
Quoted 10 lines · Bart Van Assche
Quoted 8 lines · Adrian Hunter
Quoted 3 lines · Bart Van Assche

Wouldn't it be better to keep the code that clears req->end_io_data and to change complete(c) into if(c) complete(c) in ufshcd_tmc_handler()?

If that were needed, it would imply the synchronization was broken i.e. why are we referencing a request that has already been through blk_put_request()?

Quoted 7 lines

The scenario I'm worried about is as follows:
* __ufshcd_issue_tm_cmd() issues a task management function.
* No completion is received before TM_CMD_TIMEOUT has expired (100 ms).
* ufshcd_clear_tm_cmd() fails.
* The TMF completes, ufshcd_tmc_handler() is called and that function calls complete(req->end_io_data).

Can this happen?

No because the tag's bit is cleared from outstanding_tasks before blk_put_request() and access to outstanding_tasks is protected by host_lock in both __ufshcd_issue_tm_cmd() and ufshcd_clear_tm_cmd().

Quoted 6 lines

I agree that this scenario involves completion of a request that has already been through blk_put_request().

Thanks,

Bart.

#6 lore raw
Quoted 17 lines · Adrian Hunter
Quoted 16 lines · Bart Van Assche
Quoted 8 lines · Adrian Hunter
Quoted 3 lines · Bart Van Assche

Wouldn't it be better to keep the code that clears req->end_io_data and to change complete(c) into if(c) complete(c) in ufshcd_tmc_handler()?

If that were needed, it would imply the synchronization was broken i.e. why are we referencing a request that has already been through blk_put_request()?

The scenario I'm worried about is as follows:
* __ufshcd_issue_tm_cmd() issues a task management function.
* No completion is received before TM_CMD_TIMEOUT has expired (100 ms).
* ufshcd_clear_tm_cmd() fails.
* The TMF completes, ufshcd_tmc_handler() is called and that function calls complete(req->end_io_data).

Quoted 5 lines
Quoted 1 line

Can this happen?

No because the tag's bit is cleared from outstanding_tasks before blk_put_request() and access to outstanding_tasks is protected by host_lock in both __ufshcd_issue_tm_cmd() and ufshcd_clear_tm_cmd().

Although I just noticed a different issue.

In ufshcd_tmc_handler() the task doorbell register needs to be read in conjunction with outstanding_tasks i.e. under the spinlock

I will send a V2.

Quoted 8 lines
Quoted 7 lines

I agree that this scenario involves completion of a request that has already been through blk_put_request().

Thanks,

Bart.

Reviewed-by on v1 1/1
#7 lore raw
In reply to Adrian Hunter
Quoted 26 lines · Adrian Hunter

__ufshcd_issue_tm_cmd() clears req->end_io_data after timing out, which races with the completion function ufshcd_tmc_handler() which expects req->end_io_data to have a value.

Note __ufshcd_issue_tm_cmd() and ufshcd_tmc_handler() are already synchronized using hba->tmf_rqs and hba->outstanding_tasks under the host_lock spinlock.

It is also not necessary (nor typical) to clear req->end_io_data because the block layer does it before allocating out requests e.g. via blk_get_request().

So fix by not clearing it.

Fixes f5ef336fd2e4c3 ("scsi: ufs: core: Fix task management completion")
---
  drivers/scsi/ufs/ufshcd.c | 5 -----
  1 file changed, 5 deletions(-)
drivers/scsi/ufs/ufshcd.c +0 -0
index 95be7ecdfe10..f34b3994d1aa 100644
@@ -6550,11 +6550,6 @@ static int __ufshcd_issue_tm_cmd(struct ufs_hba *hba,
  	err = wait_for_completion_io_timeout(&wait,

drivers/scsi/ufs/ufshcd.c static int __ufshcd_issue_tm_cmd(struct ufs_hba *hba, · line 6555

Quoted 10 lines
drivers/scsi/ufs/ufshcd.c +0 -5
  			msecs_to_jiffies(TM_CMD_TIMEOUT));
  	if (!err) {
-		/*
-		 * Make sure that ufshcd_compl_tm() does not trigger a
-		 * use-after-free.
-		 */
-		req->end_io_data = NULL;
  		ufshcd_add_tm_upiu_trace(hba, task_tag, UFS_TM_ERR);
  		dev_err(hba->dev, "%s: task management cmd 0x%.2x timed-out\n",
  				__func__, tm_function);