CVE-2026-46112
Description
In the Linux kernel, the following vulnerability has been resolved:
RDMA/hns: Fix unlocked call to hns_roce_qp_remove()
Sashiko points out that hns_roce_qp_remove() requires the caller to hold locks. The error flow in hns_roce_create_qp_common() doesn't hold those locks for the error unwind so it risks corrupting memory.
Grab the same locks the other two callers use.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A missing lock acquisition in hns_roce_create_qp_common() allows memory corruption via unlocked hns_roce_qp_remove() call.
Vulnerability
In the Linux kernel's RDMA/hns driver, the error flow in hns_roce_create_qp_common() calls hns_roce_qp_remove() without holding the required locks. The function documentation (as noted by Sashiko) mandates that the caller must hold locks before invoking hns_roce_qp_remove(). This code path is reachable only when a queue pair creation fails after certain initialisation steps, and the missing lock acquisition can corrupt internal data structures. The vulnerability affects Linux kernel versions containing the RDMA/hns driver before the fix introduced by commit fb4ae739811d.
Exploitation
An attacker would need to induce a failure in Queue Pair creation on an RDMA hns device, for example by exhausting kernel memory or triggering an error return in one of the earlier allocation or initialisation calls within hns_roce_create_qp_common(). No special privileges beyond the ability to issue RDMA operations are required. The attacker must be able to trigger the error path; user interaction is not required beyond the triggering action itself. The race window timing is not a factor since the locks are simply not acquired [1].
Impact
Successful exploitation leads to memory corruption in the RDMA subsystem's internal data structures, specifically those managed by hns_roce_qp_remove(). This can result in a denial-of-service (system crash or hang) or potentially privilege escalation depending on the precise memory layout and corruption state. The impact is local to the kernel's memory space; confidentiality and integrity may also be affected if the corruption enables arbitrary write [1].
Mitigation
The fix was applied to the Linux kernel in commit fb4ae739811d467409bd07d0e36cfd4140f3d26a. The patch ensures that the same locks held by other callers of hns_roce_qp_remove() are also acquired in the error path of hns_roce_create_qp_common(). Users should update to a kernel version containing this commit. No workaround is available other than applying the patch [1].
AI Insight generated on May 28, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
1Patches
100c99acbc8b6cRDMA/hns: Fix unlocked call to hns_roce_qp_remove()
1 file changed · +7 −1
drivers/infiniband/hw/hns/hns_roce_qp.c+7 −1 modifieddiff --git a/drivers/infiniband/hw/hns/hns_roce_qp.c b/drivers/infiniband/hw/hns/hns_roce_qp.c index f94ba98871f0d0..bf04ee84a94392 100644 --- a/drivers/infiniband/hw/hns/hns_roce_qp.c +++ b/drivers/infiniband/hw/hns/hns_roce_qp.c @@ -1171,6 +1171,7 @@ static int hns_roce_create_qp_common(struct hns_roce_dev *hr_dev, struct hns_roce_ib_create_qp_resp resp = {}; struct ib_device *ibdev = &hr_dev->ib_dev; struct hns_roce_ib_create_qp ucmd = {}; + unsigned long flags; int ret; mutex_init(&hr_qp->mutex); @@ -1257,7 +1258,13 @@ static int hns_roce_create_qp_common(struct hns_roce_dev *hr_dev, return 0; err_flow_ctrl: + spin_lock_irqsave(&hr_dev->qp_list_lock, flags); + hns_roce_lock_cqs(init_attr->send_cq ? to_hr_cq(init_attr->send_cq) : NULL, + init_attr->recv_cq ? to_hr_cq(init_attr->recv_cq) : NULL); hns_roce_qp_remove(hr_dev, hr_qp); + hns_roce_unlock_cqs(init_attr->send_cq ? to_hr_cq(init_attr->send_cq) : NULL, + init_attr->recv_cq ? to_hr_cq(init_attr->recv_cq) : NULL); + spin_unlock_irqrestore(&hr_dev->qp_list_lock, flags); err_store: free_qpc(hr_dev, hr_qp); err_qpc: -- cgit 1.3-korg
fb4ae739811dRDMA/hns: Fix unlocked call to hns_roce_qp_remove()
1 file changed · +7 −1
drivers/infiniband/hw/hns/hns_roce_qp.c+7 −1 modifieddiff --git a/drivers/infiniband/hw/hns/hns_roce_qp.c b/drivers/infiniband/hw/hns/hns_roce_qp.c index 26784b296ffa6b..64516f898f8068 100644 --- a/drivers/infiniband/hw/hns/hns_roce_qp.c +++ b/drivers/infiniband/hw/hns/hns_roce_qp.c @@ -1082,6 +1082,7 @@ static int hns_roce_create_qp_common(struct hns_roce_dev *hr_dev, struct hns_roce_ib_create_qp_resp resp = {}; struct ib_device *ibdev = &hr_dev->ib_dev; struct hns_roce_ib_create_qp ucmd = {}; + unsigned long flags; int ret; mutex_init(&hr_qp->mutex); @@ -1165,7 +1166,13 @@ static int hns_roce_create_qp_common(struct hns_roce_dev *hr_dev, return 0; err_flow_ctrl: + spin_lock_irqsave(&hr_dev->qp_list_lock, flags); + hns_roce_lock_cqs(init_attr->send_cq ? to_hr_cq(init_attr->send_cq) : NULL, + init_attr->recv_cq ? to_hr_cq(init_attr->recv_cq) : NULL); hns_roce_qp_remove(hr_dev, hr_qp); + hns_roce_unlock_cqs(init_attr->send_cq ? to_hr_cq(init_attr->send_cq) : NULL, + init_attr->recv_cq ? to_hr_cq(init_attr->recv_cq) : NULL); + spin_unlock_irqrestore(&hr_dev->qp_list_lock, flags); err_store: free_qpc(hr_dev, hr_qp); err_qpc: -- cgit 1.3-korg
fcf6a832c0d5RDMA/hns: Fix unlocked call to hns_roce_qp_remove()
1 file changed · +7 −1
drivers/infiniband/hw/hns/hns_roce_qp.c+7 −1 modifieddiff --git a/drivers/infiniband/hw/hns/hns_roce_qp.c b/drivers/infiniband/hw/hns/hns_roce_qp.c index 66d4c693694e97..01ea72876fd138 100644 --- a/drivers/infiniband/hw/hns/hns_roce_qp.c +++ b/drivers/infiniband/hw/hns/hns_roce_qp.c @@ -1150,6 +1150,7 @@ static int hns_roce_create_qp_common(struct hns_roce_dev *hr_dev, struct hns_roce_ib_create_qp_resp resp = {}; struct ib_device *ibdev = &hr_dev->ib_dev; struct hns_roce_ib_create_qp ucmd = {}; + unsigned long flags; int ret; mutex_init(&hr_qp->mutex); @@ -1236,7 +1237,13 @@ static int hns_roce_create_qp_common(struct hns_roce_dev *hr_dev, return 0; err_flow_ctrl: + spin_lock_irqsave(&hr_dev->qp_list_lock, flags); + hns_roce_lock_cqs(init_attr->send_cq ? to_hr_cq(init_attr->send_cq) : NULL, + init_attr->recv_cq ? to_hr_cq(init_attr->recv_cq) : NULL); hns_roce_qp_remove(hr_dev, hr_qp); + hns_roce_unlock_cqs(init_attr->send_cq ? to_hr_cq(init_attr->send_cq) : NULL, + init_attr->recv_cq ? to_hr_cq(init_attr->recv_cq) : NULL); + spin_unlock_irqrestore(&hr_dev->qp_list_lock, flags); err_store: free_qpc(hr_dev, hr_qp); err_qpc: -- cgit 1.3-korg
1912f7879850RDMA/hns: Fix unlocked call to hns_roce_qp_remove()
1 file changed · +7 −1
drivers/infiniband/hw/hns/hns_roce_qp.c+7 −1 modifieddiff --git a/drivers/infiniband/hw/hns/hns_roce_qp.c b/drivers/infiniband/hw/hns/hns_roce_qp.c index bdd879ac12dda2..5c475ac19e3cae 100644 --- a/drivers/infiniband/hw/hns/hns_roce_qp.c +++ b/drivers/infiniband/hw/hns/hns_roce_qp.c @@ -1150,6 +1150,7 @@ static int hns_roce_create_qp_common(struct hns_roce_dev *hr_dev, struct hns_roce_ib_create_qp_resp resp = {}; struct ib_device *ibdev = &hr_dev->ib_dev; struct hns_roce_ib_create_qp ucmd = {}; + unsigned long flags; int ret; mutex_init(&hr_qp->mutex); @@ -1236,7 +1237,13 @@ static int hns_roce_create_qp_common(struct hns_roce_dev *hr_dev, return 0; err_flow_ctrl: + spin_lock_irqsave(&hr_dev->qp_list_lock, flags); + hns_roce_lock_cqs(init_attr->send_cq ? to_hr_cq(init_attr->send_cq) : NULL, + init_attr->recv_cq ? to_hr_cq(init_attr->recv_cq) : NULL); hns_roce_qp_remove(hr_dev, hr_qp); + hns_roce_unlock_cqs(init_attr->send_cq ? to_hr_cq(init_attr->send_cq) : NULL, + init_attr->recv_cq ? to_hr_cq(init_attr->recv_cq) : NULL); + spin_unlock_irqrestore(&hr_dev->qp_list_lock, flags); err_store: free_qpc(hr_dev, hr_qp); err_qpc: -- cgit 1.3-korg
615d9d260c32RDMA/hns: Fix unlocked call to hns_roce_qp_remove()
1 file changed · +7 −1
drivers/infiniband/hw/hns/hns_roce_qp.c+7 −1 modifieddiff --git a/drivers/infiniband/hw/hns/hns_roce_qp.c b/drivers/infiniband/hw/hns/hns_roce_qp.c index 5f7ea6c1664460..44300f7db5b16e 100644 --- a/drivers/infiniband/hw/hns/hns_roce_qp.c +++ b/drivers/infiniband/hw/hns/hns_roce_qp.c @@ -1178,6 +1178,7 @@ static int hns_roce_create_qp_common(struct hns_roce_dev *hr_dev, struct hns_roce_ib_create_qp_resp resp = {}; struct ib_device *ibdev = &hr_dev->ib_dev; struct hns_roce_ib_create_qp ucmd = {}; + unsigned long flags; int ret; mutex_init(&hr_qp->mutex); @@ -1264,7 +1265,13 @@ static int hns_roce_create_qp_common(struct hns_roce_dev *hr_dev, return 0; err_flow_ctrl: + spin_lock_irqsave(&hr_dev->qp_list_lock, flags); + hns_roce_lock_cqs(init_attr->send_cq ? to_hr_cq(init_attr->send_cq) : NULL, + init_attr->recv_cq ? to_hr_cq(init_attr->recv_cq) : NULL); hns_roce_qp_remove(hr_dev, hr_qp); + hns_roce_unlock_cqs(init_attr->send_cq ? to_hr_cq(init_attr->send_cq) : NULL, + init_attr->recv_cq ? to_hr_cq(init_attr->recv_cq) : NULL); + spin_unlock_irqrestore(&hr_dev->qp_list_lock, flags); err_store: free_qpc(hr_dev, hr_qp); err_qpc: -- cgit 1.3-korg
0c99acbc8b6cRDMA/hns: Fix unlocked call to hns_roce_qp_remove()
1 file changed · +7 −1
drivers/infiniband/hw/hns/hns_roce_qp.c+7 −1 modifieddiff --git a/drivers/infiniband/hw/hns/hns_roce_qp.c b/drivers/infiniband/hw/hns/hns_roce_qp.c index f94ba98871f0d0..bf04ee84a94392 100644 --- a/drivers/infiniband/hw/hns/hns_roce_qp.c +++ b/drivers/infiniband/hw/hns/hns_roce_qp.c @@ -1171,6 +1171,7 @@ static int hns_roce_create_qp_common(struct hns_roce_dev *hr_dev, struct hns_roce_ib_create_qp_resp resp = {}; struct ib_device *ibdev = &hr_dev->ib_dev; struct hns_roce_ib_create_qp ucmd = {}; + unsigned long flags; int ret; mutex_init(&hr_qp->mutex); @@ -1257,7 +1258,13 @@ static int hns_roce_create_qp_common(struct hns_roce_dev *hr_dev, return 0; err_flow_ctrl: + spin_lock_irqsave(&hr_dev->qp_list_lock, flags); + hns_roce_lock_cqs(init_attr->send_cq ? to_hr_cq(init_attr->send_cq) : NULL, + init_attr->recv_cq ? to_hr_cq(init_attr->recv_cq) : NULL); hns_roce_qp_remove(hr_dev, hr_qp); + hns_roce_unlock_cqs(init_attr->send_cq ? to_hr_cq(init_attr->send_cq) : NULL, + init_attr->recv_cq ? to_hr_cq(init_attr->recv_cq) : NULL); + spin_unlock_irqrestore(&hr_dev->qp_list_lock, flags); err_store: free_qpc(hr_dev, hr_qp); err_qpc: -- cgit 1.3-korg
1912f7879850RDMA/hns: Fix unlocked call to hns_roce_qp_remove()
1 file changed · +7 −1
drivers/infiniband/hw/hns/hns_roce_qp.c+7 −1 modifieddiff --git a/drivers/infiniband/hw/hns/hns_roce_qp.c b/drivers/infiniband/hw/hns/hns_roce_qp.c index bdd879ac12dda2..5c475ac19e3cae 100644 --- a/drivers/infiniband/hw/hns/hns_roce_qp.c +++ b/drivers/infiniband/hw/hns/hns_roce_qp.c @@ -1150,6 +1150,7 @@ static int hns_roce_create_qp_common(struct hns_roce_dev *hr_dev, struct hns_roce_ib_create_qp_resp resp = {}; struct ib_device *ibdev = &hr_dev->ib_dev; struct hns_roce_ib_create_qp ucmd = {}; + unsigned long flags; int ret; mutex_init(&hr_qp->mutex); @@ -1236,7 +1237,13 @@ static int hns_roce_create_qp_common(struct hns_roce_dev *hr_dev, return 0; err_flow_ctrl: + spin_lock_irqsave(&hr_dev->qp_list_lock, flags); + hns_roce_lock_cqs(init_attr->send_cq ? to_hr_cq(init_attr->send_cq) : NULL, + init_attr->recv_cq ? to_hr_cq(init_attr->recv_cq) : NULL); hns_roce_qp_remove(hr_dev, hr_qp); + hns_roce_unlock_cqs(init_attr->send_cq ? to_hr_cq(init_attr->send_cq) : NULL, + init_attr->recv_cq ? to_hr_cq(init_attr->recv_cq) : NULL); + spin_unlock_irqrestore(&hr_dev->qp_list_lock, flags); err_store: free_qpc(hr_dev, hr_qp); err_qpc: -- cgit 1.3-korg
615d9d260c32RDMA/hns: Fix unlocked call to hns_roce_qp_remove()
1 file changed · +7 −1
drivers/infiniband/hw/hns/hns_roce_qp.c+7 −1 modifieddiff --git a/drivers/infiniband/hw/hns/hns_roce_qp.c b/drivers/infiniband/hw/hns/hns_roce_qp.c index 5f7ea6c1664460..44300f7db5b16e 100644 --- a/drivers/infiniband/hw/hns/hns_roce_qp.c +++ b/drivers/infiniband/hw/hns/hns_roce_qp.c @@ -1178,6 +1178,7 @@ static int hns_roce_create_qp_common(struct hns_roce_dev *hr_dev, struct hns_roce_ib_create_qp_resp resp = {}; struct ib_device *ibdev = &hr_dev->ib_dev; struct hns_roce_ib_create_qp ucmd = {}; + unsigned long flags; int ret; mutex_init(&hr_qp->mutex); @@ -1264,7 +1265,13 @@ static int hns_roce_create_qp_common(struct hns_roce_dev *hr_dev, return 0; err_flow_ctrl: + spin_lock_irqsave(&hr_dev->qp_list_lock, flags); + hns_roce_lock_cqs(init_attr->send_cq ? to_hr_cq(init_attr->send_cq) : NULL, + init_attr->recv_cq ? to_hr_cq(init_attr->recv_cq) : NULL); hns_roce_qp_remove(hr_dev, hr_qp); + hns_roce_unlock_cqs(init_attr->send_cq ? to_hr_cq(init_attr->send_cq) : NULL, + init_attr->recv_cq ? to_hr_cq(init_attr->recv_cq) : NULL); + spin_unlock_irqrestore(&hr_dev->qp_list_lock, flags); err_store: free_qpc(hr_dev, hr_qp); err_qpc: -- cgit 1.3-korg
fb4ae739811dRDMA/hns: Fix unlocked call to hns_roce_qp_remove()
1 file changed · +7 −1
drivers/infiniband/hw/hns/hns_roce_qp.c+7 −1 modifieddiff --git a/drivers/infiniband/hw/hns/hns_roce_qp.c b/drivers/infiniband/hw/hns/hns_roce_qp.c index 26784b296ffa6b..64516f898f8068 100644 --- a/drivers/infiniband/hw/hns/hns_roce_qp.c +++ b/drivers/infiniband/hw/hns/hns_roce_qp.c @@ -1082,6 +1082,7 @@ static int hns_roce_create_qp_common(struct hns_roce_dev *hr_dev, struct hns_roce_ib_create_qp_resp resp = {}; struct ib_device *ibdev = &hr_dev->ib_dev; struct hns_roce_ib_create_qp ucmd = {}; + unsigned long flags; int ret; mutex_init(&hr_qp->mutex); @@ -1165,7 +1166,13 @@ static int hns_roce_create_qp_common(struct hns_roce_dev *hr_dev, return 0; err_flow_ctrl: + spin_lock_irqsave(&hr_dev->qp_list_lock, flags); + hns_roce_lock_cqs(init_attr->send_cq ? to_hr_cq(init_attr->send_cq) : NULL, + init_attr->recv_cq ? to_hr_cq(init_attr->recv_cq) : NULL); hns_roce_qp_remove(hr_dev, hr_qp); + hns_roce_unlock_cqs(init_attr->send_cq ? to_hr_cq(init_attr->send_cq) : NULL, + init_attr->recv_cq ? to_hr_cq(init_attr->recv_cq) : NULL); + spin_unlock_irqrestore(&hr_dev->qp_list_lock, flags); err_store: free_qpc(hr_dev, hr_qp); err_qpc: -- cgit 1.3-korg
fcf6a832c0d5RDMA/hns: Fix unlocked call to hns_roce_qp_remove()
1 file changed · +7 −1
drivers/infiniband/hw/hns/hns_roce_qp.c+7 −1 modifieddiff --git a/drivers/infiniband/hw/hns/hns_roce_qp.c b/drivers/infiniband/hw/hns/hns_roce_qp.c index 66d4c693694e97..01ea72876fd138 100644 --- a/drivers/infiniband/hw/hns/hns_roce_qp.c +++ b/drivers/infiniband/hw/hns/hns_roce_qp.c @@ -1150,6 +1150,7 @@ static int hns_roce_create_qp_common(struct hns_roce_dev *hr_dev, struct hns_roce_ib_create_qp_resp resp = {}; struct ib_device *ibdev = &hr_dev->ib_dev; struct hns_roce_ib_create_qp ucmd = {}; + unsigned long flags; int ret; mutex_init(&hr_qp->mutex); @@ -1236,7 +1237,13 @@ static int hns_roce_create_qp_common(struct hns_roce_dev *hr_dev, return 0; err_flow_ctrl: + spin_lock_irqsave(&hr_dev->qp_list_lock, flags); + hns_roce_lock_cqs(init_attr->send_cq ? to_hr_cq(init_attr->send_cq) : NULL, + init_attr->recv_cq ? to_hr_cq(init_attr->recv_cq) : NULL); hns_roce_qp_remove(hr_dev, hr_qp); + hns_roce_unlock_cqs(init_attr->send_cq ? to_hr_cq(init_attr->send_cq) : NULL, + init_attr->recv_cq ? to_hr_cq(init_attr->recv_cq) : NULL); + spin_unlock_irqrestore(&hr_dev->qp_list_lock, flags); err_store: free_qpc(hr_dev, hr_qp); err_qpc: -- cgit 1.3-korg
Vulnerability mechanics
Root cause
"Missing lock acquisition before calling hns_roce_qp_remove() in the error-unwind path of hns_roce_create_qp_common()."
Attack vector
An attacker triggers this by causing `hns_roce_create_qp_common()` to fail after the QP has been added to internal lists but before the function returns success. The error path at the `err_flow_ctrl` label calls `hns_roce_qp_remove()` without holding `hr_dev->qp_list_lock` or the completion-queue locks, violating the locking contract. Because the call is unprotected, concurrent operations on the same QP list or CQ structures can race, leading to memory corruption. No special network path or payload is required beyond inducing a QP creation failure.
Affected code
The bug is in the `hns_roce_create_qp_common()` function in `drivers/infiniband/hw/hns/hns_roce_qp.c` [patch_id=2898612]. The `err_flow_ctrl` error-unwind label calls `hns_roce_qp_remove()` without first acquiring the locks that the function's contract requires.
What the fix does
The patch adds `spin_lock_irqsave(&hr_dev->qp_list_lock, flags)` and `hns_roce_lock_cqs()` before the call to `hns_roce_qp_remove()`, and the corresponding unlock calls after it [patch_id=2898612]. This ensures the error unwind path holds the same locks that the other two callers of `hns_roce_qp_remove()` already acquire, preventing the race condition that could corrupt memory.
Preconditions
- inputThe attacker must be able to trigger a failure in hns_roce_create_qp_common() after the QP has been added to internal tracking structures.
- configThe system must be using the Hisilicon (hns) RDMA driver.
Generated on May 28, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- git.kernel.org/stable/c/0c99acbc8b6c6dd526ae475a48ee1897b61072fbnvd
- git.kernel.org/stable/c/1912f78798505dc9c637081bbddfbf1c22494c49nvd
- git.kernel.org/stable/c/615d9d260c32bb678504ca96f29ae46f9d745155nvd
- git.kernel.org/stable/c/fb4ae739811d467409bd07d0e36cfd4140f3d26anvd
- git.kernel.org/stable/c/fcf6a832c0d5b2bc5398d6996c5570d3ee7993fbnvd
News mentions
0No linked articles in our index yet.