Vulnerability: An issue was discovered in the Linux kernel before 4.20. There is a race condition in smp_task_timedout() and smp_task_done() in drivers/scsi/libsas/sas_expander.c, leading to a use-after-free.
Vulnerability ID: CVE-2018-20836
CWE: CWE-362
Severity score: 10
Project: https://github.com/torvalds/linux
Published: 2019-05-07
File: drivers/scsi/libsas/sas_expander.c
Commit message: scsi: libsas: fix a race condition when smp task timeout

When the lldd is processing the complete sas task in interrupt and set the
task stat as SAS_TASK_STATE_DONE, the smp timeout timer is able to be
triggered at the same time. And smp_task_timedout() will complete the task
wheter the SAS_TASK_STATE_DONE is set or not. Then the sas task may freed
before lldd end the interrupt process. Thus a use-after-free will happen.

Fix this by calling the complete() only when SAS_TASK_STATE_DONE is not
set. And remove the check of the return value of the del_timer(). Once the
LLDD sets DONE, it must call task->done(), which will call
smp_task_done()->complete() and the task will be completed and freed
correctly.

Reported-by: chenxiang <chenxiang66@hisilicon.com>
Signed-off-by: Jason Yan <yanaijie@huawei.com>
CC: John Garry <john.garry@huawei.com>
CC: Johannes Thumshirn <jthumshirn@suse.de>
CC: Ewan Milne <emilne@redhat.com>
CC: Christoph Hellwig <hch@lst.de>
CC: Tomas Henzl <thenzl@redhat.com>
CC: Dan Williams <dan.j.williams@intel.com>
CC: Hannes Reinecke <hare@suse.com>
Reviewed-by: Hannes Reinecke <hare@suse.com>
Reviewed-by: John Garry <john.garry@huawei.com>
Reviewed-by: Johannes Thumshirn <jthumshirn@suse.de>
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
Commit author: Jason Yan

Vulnerable version (before fix):
 static void smp_task_timedout(struct timer_list *t) 	unsigned long flags;  	spin_lock_irqsave(&task->task_state_lock, flags); 	if (!(task->task_state_flags & SAS_TASK_STATE_DONE)) 		task->task_state_flags |= SAS_TASK_STATE_ABORTED; 	spin_unlock_irqrestore(&task->task_state_lock, flags);  	complete(&task->slow_task->completion); }  static void smp_task_done(struct sas_task *task) { 	if (!del_timer(&task->slow_task->timer)) 		return; 	complete(&task->slow_task->completion); } 

Fixed version (after fix):
 static void smp_task_timedout(struct timer_list *t) 	unsigned long flags;  	spin_lock_irqsave(&task->task_state_lock, flags); 	if (!(task->task_state_flags & SAS_TASK_STATE_DONE)) { 		task->task_state_flags |= SAS_TASK_STATE_ABORTED; 		complete(&task->slow_task->completion); 	} 	spin_unlock_irqrestore(&task->task_state_lock, flags); }  static void smp_task_done(struct sas_task *task) { 	del_timer(&task->slow_task->timer); 	complete(&task->slow_task->completion); } 

Patch:
patch: @@ -48,17 +48,16 @@ static void smp_task_timedout(struct timer_list *t)
 	unsigned long flags;
 
 	spin_lock_irqsave(&task->task_state_lock, flags);
-	if (!(task->task_state_flags & SAS_TASK_STATE_DONE))
+	if (!(task->task_state_flags & SAS_TASK_STATE_DONE)) {
 		task->task_state_flags |= SAS_TASK_STATE_ABORTED;
+		complete(&task->slow_task->completion);
+	}
 	spin_unlock_irqrestore(&task->task_state_lock, flags);
-
-	complete(&task->slow_task->completion);
 }
 
 static void smp_task_done(struct sas_task *task)
 {
-	if (!del_timer(&task->slow_task->timer))
-		return;
+	del_timer(&task->slow_task->timer);
 	complete(&task->slow_task->completion);
 }
path: drivers/scsi/libsas/sas_expander.c