[PATCH v1] qed: Protect iSCSI connection hash operations
DORMANTno replies
From: Binbin Deng <hidden>
Date: 2026-10-20 06:48:11
Also in:
lkml
Subsystem:
networking drivers, qlogic ql4xxx ethernet driver, the rest · Maintainers:
Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
The qed iSCSI connection hash is accessed without a common lock.
Operations first look up a qed_hash_iscsi_con wrapper and then keep
dereferencing its embedded connection object while firmware commands are
posted. Connection release removes the wrapper from the hash, releases
the firmware connection, and frees the wrapper. If two endpoints hash to
the same bucket, a release for one handle can race with lookup and object
use for another handle.
The race is reachable from qedi connection setup and teardown work: one
endpoint can call release_conn() while another endpoint concurrently calls
offload_conn(), update_conn(), clear_sq(), destroy_conn(), or change_mac().
The loop in qed_iscsi_get_hash() can also be traversing a bucket while a
node in that bucket is unlinked and freed.
This can cause a KASAN use-after-free, hash-list corruption, a stale
connection pointer being passed to firmware, or a panic. The storage stop
path also needs to see a stable empty/not-empty result before clearing the
started state.
Protect the hash and connection lifetime with a file-local mutex. Hold it
across each complete connection operation, including firmware submission,
hash insertion or removal, and wrapper free. The mutex also serializes
connection acquisition with storage stop.
Found during a static-analysis-assisted audit: candidate sites were
surfaced by a Coccinelle rule, and the reader/writer paths were
confirmed by LLM-assisted (GLM-5.3) code inspection. Due to a lack
of hardware infrastructure, the issue was not observed at runtime.
It is identified by code inspection only.
Fixes: fc831825f99e ("qed: Add support for hardware offloaded iSCSI.")
Assisted-by: GLM-5.3 coccinelle
Signed-off-by: Binbin Deng <redacted>
---
drivers/net/ethernet/qlogic/qed/qed_iscsi.c | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/drivers/net/ethernet/qlogic/qed/qed_iscsi.c b/drivers/net/ethernet/qlogic/qed/qed_iscsi.c
index 34f1217227bc..f7b97883e4b9 100644
--- a/drivers/net/ethernet/qlogic/qed/qed_iscsi.c
+++ b/drivers/net/ethernet/qlogic/qed/qed_iscsi.c@@ -14,6 +14,7 @@ #include <linux/kernel.h> #include <linux/log2.h> #include <linux/module.h> +#include <linux/mutex.h> #include <linux/pci.h> #include <linux/slab.h> #include <linux/stddef.h>
@@ -118,6 +119,8 @@ struct qed_iscsi_conn { u8 abortive_dsconnect; }; +static DEFINE_MUTEX(qed_iscsi_conn_lock); + static int qed_iscsi_async_event(struct qed_hwfn *p_hwfn, u8 fw_event_code, __le16 echo, union event_ring_data *data, u8 fw_return_code)
@@ -1080,6 +1083,7 @@ static int qed_iscsi_stop(struct qed_dev *cdev) { int rc; + guard(mutex)(&qed_iscsi_conn_lock); if (!(cdev->flags & QED_FLAG_STORAGE_STARTED)) { DP_NOTICE(cdev, "iscsi already stopped\n"); return 0;
@@ -1158,6 +1162,7 @@ static int qed_iscsi_acquire_conn(struct qed_dev *cdev, struct qed_hash_iscsi_con *hash_con; int rc; + guard(mutex)(&qed_iscsi_conn_lock); /* Allocate a hashed connection */ hash_con = kzalloc_obj(*hash_con, GFP_ATOMIC); if (!hash_con)
@@ -1188,6 +1193,7 @@ static int qed_iscsi_release_conn(struct qed_dev *cdev, u32 handle) { struct qed_hash_iscsi_con *hash_con; + guard(mutex)(&qed_iscsi_conn_lock); hash_con = qed_iscsi_get_hash(cdev, handle); if (!hash_con) { DP_NOTICE(cdev, "Failed to find connection for handle %d\n",
@@ -1209,6 +1215,7 @@ static int qed_iscsi_offload_conn(struct qed_dev *cdev, struct qed_hash_iscsi_con *hash_con; struct qed_iscsi_conn *con; + guard(mutex)(&qed_iscsi_conn_lock); hash_con = qed_iscsi_get_hash(cdev, handle); if (!hash_con) { DP_NOTICE(cdev, "Failed to find connection for handle %d\n",
@@ -1284,6 +1291,7 @@ static int qed_iscsi_update_conn(struct qed_dev *cdev, struct qed_hash_iscsi_con *hash_con; struct qed_iscsi_conn *con; + guard(mutex)(&qed_iscsi_conn_lock); hash_con = qed_iscsi_get_hash(cdev, handle); if (!hash_con) { DP_NOTICE(cdev, "Failed to find connection for handle %d\n",
@@ -1308,6 +1316,7 @@ static int qed_iscsi_clear_conn_sq(struct qed_dev *cdev, u32 handle) { struct qed_hash_iscsi_con *hash_con; + guard(mutex)(&qed_iscsi_conn_lock); hash_con = qed_iscsi_get_hash(cdev, handle); if (!hash_con) { DP_NOTICE(cdev, "Failed to find connection for handle %d\n",
@@ -1324,6 +1333,7 @@ static int qed_iscsi_destroy_conn(struct qed_dev *cdev, { struct qed_hash_iscsi_con *hash_con; + guard(mutex)(&qed_iscsi_conn_lock); hash_con = qed_iscsi_get_hash(cdev, handle); if (!hash_con) { DP_NOTICE(cdev, "Failed to find connection for handle %d\n",
@@ -1354,6 +1364,7 @@ static int qed_iscsi_change_mac(struct qed_dev *cdev, { struct qed_hash_iscsi_con *hash_con; + guard(mutex)(&qed_iscsi_conn_lock); hash_con = qed_iscsi_get_hash(cdev, handle); if (!hash_con) { DP_NOTICE(cdev, "Failed to find connection for handle %d\n",
--
2.43.0