Subject: [BUG] scsi: core: KCSAN: data-race in scsi_block_when_processing_errors / scsi_host_set_state
Dear Maintainers,
We are writing to report a KCSAN-detected data race vulnerability within the SCSI core subsystem (`drivers/scsi/hosts.c` and `include/scsi/scsi_host.h`). This bug was found by our custom fuzzing tool, RacePilot. The race occurs during the host state transition while an error recovery process is active, specifically between the active modification of `shost->shost_state` within `scsi_host_set_state` and the lockless checking loop inside `scsi_block_when_processing_errors` through `scsi_host_in_recovery()`. We observed this bug on the Linux kernel version 6.18.0-08691-g2061f18ad76e-dirty.
Call Trace & Context
==================================================================
BUG: KCSAN: data-race in scsi_block_when_processing_errors / scsi_host_set_state
write to 0xffff888009ff4280 of 4 bytes by task 307 on cpu 1:
scsi_host_set_state+0x92/0x180 drivers/scsi/hosts.c:148
scsi_restart_operations drivers/scsi/scsi_error.c:2162 [inline]
scsi_error_handler+0x269/0x840 drivers/scsi/scsi_error.c:2372
...
read to 0xffff888009ff4280 of 4 bytes by task 22653 on cpu 0:
scsi_host_in_recovery include/scsi/scsi_host.h:754 [inline]
scsi_block_when_processing_errors+0x41/0x240 drivers/scsi/scsi_error.c:388
sr_open+0x2e/0x60 drivers/scsi/sr.c:609
cdrom_open+0xbc/0xec0 drivers/cdrom/cdrom.c:1154
sr_block_open+0x9b/0x120 drivers/scsi/sr.c:512
blkdev_get_whole+0x55/0x1f0 block/bdev.c:758
...
__x64_sys_openat+0xc2/0x130 fs/open.c:1447
value changed: 0x00000005 -> 0x00000002
Reported by Kernel Concurrency Sanitizer on:
CPU: 0 UID: 0 PID: 22653 Comm: syz.1.672 Not tainted 6.18.0-08691-g2061f18ad76e-dirty #42 PREEMPT(voluntary)
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014
==================================================================
Execution Flow & Code Context
When the SCSI error handler resolves outstanding issues and restarts operations, it invokes `scsi_restart_operations()`, which transitions the SCSI host state back to `SHOST_RUNNING` by calling `scsi_host_set_state()`. This alters the state enum variable locklessly but assigns the target state indiscriminately via standard assignment:
```c
// drivers/scsi/hosts.c
int scsi_host_set_state(struct Scsi_Host *shost, enum scsi_host_state state)
{
...
shost->shost_state = state; // <-- Plain concurrent 4-byte write
return 0;
...
}
```
At the exact same time, a completely separate thread opening the SCSI/CD-ROM device issues `wait_event(sdev->host->host_wait, !scsi_host_in_recovery(sdev->host));` inside `scsi_block_when_processing_errors()`. The `wait_event` loop repeatedly checks `scsi_host_in_recovery()`, which evaluates the `shost_state`:
```c
// include/scsi/scsi_host.h
static inline int scsi_host_in_recovery(struct Scsi_Host *shost)
{
return shost->shost_state == SHOST_RECOVERY || // <-- Plain concurrent 4-byte read
shost->shost_state == SHOST_CANCEL_RECOVERY ||
shost->shost_state == SHOST_DEL_RECOVERY ||
shost->tmf_in_progress;
}
```
Root Cause Analysis
A KCSAN data race unfolds due to the unprotected modification of the enum type `shost->shost_state` competing against the lockless predicate loop inherent to `wait_event`. The condition function `scsi_host_in_recovery` repetitively evaluates `shost_state`. Since this value can asynchronously shift (from `SHOST_RECOVERY` to `SHOST_RUNNING` as shown in the KCSAN trace when observing the change `0x00000005 -> 0x00000002`), evaluating it as a plain variable lacks fundamental compiler memory consistency boundaries. Without adequate compiler barriers, this specific mutation can suffer load tearing or generate severe KCSAN spam due to read-caching compiler optimizations globally.
Unfortunately, we were unable to generate a reproducer for this bug.
Potential Impact
This data race largely forces dynamic analysis tools like KCSAN to repetitively issue warnings when traversing the SCSI block paths, obstructing true analysis. In certain highly optimized architectures, standard assignments and reads over variable data can encounter load-tearing, wherein the predicate evaluation observes an illegal or incomplete transitional state structure causing `wait_event` to behave inconsistently.
Proposed Fix
We propose implementing `WRITE_ONCE` and `READ_ONCE` within the transition path and wait queue evaluator specifically for `shost_state` updates to respect proper concurrency protocols.
```diff
--- a/drivers/scsi/hosts.c
+++ b/drivers/scsi/hosts.c
@@ -145,7 +145,7 @@ int scsi_host_set_state(struct Scsi_Host *shost, enum scsi_host_state state)
}
break;
}
- shost->shost_state = state;
+ WRITE_ONCE(shost->shost_state, state);
return 0;
illegal:
--- a/include/scsi/scsi_host.h
+++ b/include/scsi/scsi_host.h
@@ -751,9 +751,11 @@ static inline struct Scsi_Host *dev_to_shost(struct device *dev)
static inline int scsi_host_in_recovery(struct Scsi_Host *shost)
{
- return shost->shost_state == SHOST_RECOVERY ||
- shost->shost_state == SHOST_CANCEL_RECOVERY ||
- shost->shost_state == SHOST_DEL_RECOVERY ||
+ enum scsi_host_state state = READ_ONCE(shost->shost_state);
+
+ return state == SHOST_RECOVERY ||
+ state == SHOST_CANCEL_RECOVERY ||
+ state == SHOST_DEL_RECOVERY ||
shost->tmf_in_progress;
}
```
We would be highly honored if this could be of any help.
Best regards,
RacePilot Team
On 3/11/26 1:06 AM, Jianzhou Zhao wrote:
> Proposed Fix
> We propose implementing `WRITE_ONCE` and `READ_ONCE` within the
> transition path and wait queue evaluator specifically for `shost_state`
> updates to respect proper concurrency protocols.
Is the proposed fix complete? If I annotate the "shost_state" member
variable with __guarded_by(&host_lock) and if I enable lock context
analysis in all SCSI core and SCSI driver Makefiles, something like
this is probably needed to suppress all compiler warnings reported by
Clang:
diff --git a/drivers/scsi/hosts.c b/drivers/scsi/hosts.c
index e047747d4ecf..3d9d183b4e84 100644
--- a/drivers/scsi/hosts.c
+++ b/drivers/scsi/hosts.c
@@ -278,7 +278,8 @@ int scsi_add_host_with_dma(struct Scsi_Host *shost,
struct device *dev,
if (error)
goto out_disable_runtime_pm;
- scsi_host_set_state(shost, SHOST_RUNNING);
+ scoped_guard(spinlock_irq, shost->host_lock)
+ scsi_host_set_state(shost, SHOST_RUNNING);
get_device(shost->shost_gendev.parent);
device_enable_async_suspend(&shost->shost_dev);
@@ -364,7 +365,7 @@ static void scsi_host_dev_release(struct device *dev)
if (shost->work_q)
destroy_workqueue(shost->work_q);
- if (shost->shost_state == SHOST_CREATED) {
+ if (context_unsafe(shost->shost_state == SHOST_CREATED)) {
/*
* Free the shost_dev device name and remove the proc host dir
* here if scsi_host_{alloc,put}() have been called but neither
@@ -380,7 +381,7 @@ static void scsi_host_dev_release(struct device *dev)
ida_free(&host_index_ida, shost->host_no);
- if (shost->shost_state != SHOST_CREATED)
+ if (context_unsafe(shost->shost_state != SHOST_CREATED))
put_device(parent);
kfree(shost);
}
@@ -414,7 +415,7 @@ struct Scsi_Host *scsi_host_alloc(const struct
scsi_host_template *sht, int priv
shost->host_lock = &shost->default_lock;
spin_lock_init(shost->host_lock);
- shost->shost_state = SHOST_CREATED;
+ context_unsafe(shost->shost_state = SHOST_CREATED);
INIT_LIST_HEAD(&shost->__devices);
INIT_LIST_HEAD(&shost->__targets);
INIT_LIST_HEAD(&shost->eh_abort_list);
@@ -600,7 +601,7 @@ EXPORT_SYMBOL(scsi_host_lookup);
**/
struct Scsi_Host *scsi_host_get(struct Scsi_Host *shost)
{
- if ((shost->shost_state == SHOST_DEL) ||
+ if (context_unsafe(shost->shost_state == SHOST_DEL) ||
!get_device(&shost->shost_gendev))
return NULL;
return shost;
diff --git a/drivers/scsi/megaraid/megaraid_sas_base.c
b/drivers/scsi/megaraid/megaraid_sas_base.c
index ccefe5841a17..a234b18bc01e 100644
--- a/drivers/scsi/megaraid/megaraid_sas_base.c
+++ b/drivers/scsi/megaraid/megaraid_sas_base.c
@@ -3075,7 +3075,7 @@ static int megasas_reset_bus_host(struct scsi_cmnd
*scmd)
scmd_printk(KERN_INFO, scmd,
"SCSI host state: %d SCSI host busy: %d FW outstanding: %d\n",
- scmd->device->host->shost_state,
+ context_unsafe(scmd->device->host->shost_state),
scsi_host_busy(scmd->device->host),
atomic_read(&instance->fw_outstanding));
/*
diff --git a/drivers/scsi/mpt3sas/mpt3sas_scsih.c
b/drivers/scsi/mpt3sas/mpt3sas_scsih.c
index 6ff788557294..0737a07afdf4 100644
--- a/drivers/scsi/mpt3sas/mpt3sas_scsih.c
+++ b/drivers/scsi/mpt3sas/mpt3sas_scsih.c
@@ -5460,7 +5460,7 @@ static enum scsi_qc_status scsih_qcmd(struct
Scsi_Host *shost,
* Avoid error handling escallation when device is disconnected
*/
if (handle == MPT3SAS_INVALID_DEVICE_HANDLE ||
sas_device_priv_data->block) {
- if (scmd->device->host->shost_state == SHOST_RECOVERY &&
+ if (context_unsafe(scmd->device->host->shost_state == SHOST_RECOVERY) &&
scmd->cmnd[0] == TEST_UNIT_READY) {
scsi_build_sense(scmd, 0, UNIT_ATTENTION, 0x29, 0x07);
scsi_done(scmd);
diff --git a/drivers/scsi/qla4xxx/ql4_os.c b/drivers/scsi/qla4xxx/ql4_os.c
index d598ab4126f8..3bd55887a655 100644
--- a/drivers/scsi/qla4xxx/ql4_os.c
+++ b/drivers/scsi/qla4xxx/ql4_os.c
@@ -9413,9 +9413,7 @@ static int qla4xxx_eh_target_reset(struct
scsi_cmnd *cmd)
**/
static int qla4xxx_is_eh_active(struct Scsi_Host *shost)
{
- if (shost->shost_state == SHOST_RECOVERY)
- return 1;
- return 0;
+ return context_unsafe(shost->shost_state == SHOST_RECOVERY);
}
/**
diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
index 6e8c7a42603e..5d35007bf9f1 100644
--- a/drivers/scsi/scsi_lib.c
+++ b/drivers/scsi/scsi_lib.c
@@ -1638,7 +1638,7 @@ static enum scsi_qc_status
scsi_dispatch_cmd(struct scsi_cmnd *cmd)
goto done;
}
- if (unlikely(host->shost_state == SHOST_DEL)) {
+ if (unlikely(context_unsafe(host->shost_state == SHOST_DEL))) {
cmd->result = (DID_NO_CONNECT << 16);
goto done;
diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c
index dfc3559e7e04..8e939e21ea18 100644
--- a/drivers/scsi/scsi_sysfs.c
+++ b/drivers/scsi/scsi_sysfs.c
@@ -214,8 +214,9 @@ store_shost_state(struct device *dev, struct
device_attribute *attr,
if (!state)
return -EINVAL;
- if (scsi_host_set_state(shost, state))
- return -EINVAL;
+ scoped_guard(spinlock_irq, shost->host_lock)
+ if (scsi_host_set_state(shost, state))
+ return -EINVAL;
return count;
}
@@ -223,7 +224,7 @@ static ssize_t
show_shost_state(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct Scsi_Host *shost = class_to_shost(dev);
- const char *name = scsi_host_state_name(shost->shost_state);
+ const char *name =
scsi_host_state_name(context_unsafe(shost->shost_state));
if (!name)
return -EINVAL;
diff --git a/include/scsi/scsi_host.h b/include/scsi/scsi_host.h
index 7e2011830ba4..a16d115c389b 100644
--- a/include/scsi/scsi_host.h
+++ b/include/scsi/scsi_host.h
@@ -727,7 +727,7 @@ struct Scsi_Host {
unsigned int irq;
- enum scsi_host_state shost_state;
+ enum scsi_host_state shost_state __guarded_by(&host_lock);
/* ldm bits */
struct device shost_gendev, shost_dev;
@@ -787,9 +787,11 @@ static inline struct Scsi_Host *dev_to_shost(struct
device *dev)
static inline int scsi_host_in_recovery(struct Scsi_Host *shost)
{
- return shost->shost_state == SHOST_RECOVERY ||
- shost->shost_state == SHOST_CANCEL_RECOVERY ||
- shost->shost_state == SHOST_DEL_RECOVERY ||
+ enum scsi_host_state state =
context_unsafe(READ_ONCE(shost->shost_state));
+
+ return state == SHOST_RECOVERY ||
+ state == SHOST_CANCEL_RECOVERY ||
+ state == SHOST_DEL_RECOVERY ||
shost->tmf_in_progress;
}
@@ -835,8 +837,9 @@ static inline struct device *scsi_get_device(struct
Scsi_Host *shost)
**/
static inline int scsi_host_scan_allowed(struct Scsi_Host *shost)
{
- return shost->shost_state == SHOST_RUNNING ||
- shost->shost_state == SHOST_RECOVERY;
+ enum scsi_host_state state =
context_unsafe(READ_ONCE(shost->shost_state));
+
+ return state == SHOST_RUNNING || state == SHOST_RECOVERY;
}
extern void scsi_unblock_requests(struct Scsi_Host *);
@@ -940,6 +943,7 @@ static inline unsigned char
scsi_host_get_guard(struct Scsi_Host *shost)
return shost->prot_guard_type;
}
-extern int scsi_host_set_state(struct Scsi_Host *, enum scsi_host_state);
+int scsi_host_set_state(struct Scsi_Host *shost, enum scsi_host_state
state)
+ __must_hold(&shost->host_lock);
#endif /* _SCSI_SCSI_HOST_H */
© 2016 - 2026 Red Hat, Inc.