* [PATCH net] dibs: fix use-after-free of dmb_node in loopback attach/detach/unregister
@ 2026-07-27 9:35 Hidayath Khan
2026-07-28 9:35 ` sashiko-bot
0 siblings, 1 reply; 2+ messages in thread
From: Hidayath Khan @ 2026-07-27 9:35 UTC (permalink / raw)
To: davem, edumazet, kuba, pabeni, wintera, alibuda, dust.li, sidraya,
wenjia, mjambigi
Cc: tonylu, guwen, horms, hca, gor, agordeev, borntraeger, svens,
pasic, gbayer, andrew+netdev, netdev, linux-s390, linux-rdma,
linux-kernel
dibs_lo_attach_dmb(), dibs_lo_detach_dmb() and dibs_lo_unregister_dmb()
look up the dmb_node under dmb_ht_lock, drop the lock and only then
operate on the node's refcount. Nothing keeps the node alive across
that window: __dibs_lo_unregister_dmb() removes the node from the hash
table under the write lock and immediately frees it.
A concurrent final put can therefore free the node between the lookup
and the refcount operation:
CPU0 (attach) CPU1 (owner unregisters)
read_lock_bh(&dmb_ht_lock)
find dmb_node (refcnt == 1)
read_unlock_bh(&dmb_ht_lock)
refcount_dec_and_test() 1 -> 0
write_lock_bh(&dmb_ht_lock)
hash_del(&dmb_node->list)
write_unlock_bh(&dmb_ht_lock)
kfree(dmb_node)
refcount_inc_not_zero(&dmb_node->refcnt) <-- use-after-free
The same window exists for the refcount_dec_and_test() calls in the
detach and unregister paths.
Close the race structurally by making hash table membership and the
refcount transitions atomic with respect to each other:
- Perform the final refcount_dec_and_test() and hash_del() in a single
dmb_ht_lock write-side critical section, in both the unregister and
the detach path. Freeing the node still happens after the lock is
dropped, which is safe because a node whose refcount reached zero has
left the hash table and can no longer be found.
- This establishes the invariant that any node found in the hash table
holds at least one reference, and that the final reference can only
be dropped under the write lock. dibs_lo_attach_dmb() can thus take
its reference with a plain refcount_inc() while still holding the
read lock; refcount_inc_not_zero() is no longer needed.
__dibs_lo_unregister_dmb() no longer touches the hash table and is
renamed to dibs_lo_free_dmb() accordingly.
Note: commit cc21191b584c ("dibs: Move data path to dibs layer") moved
the code to its current location; the race was introduced earlier by
commit c3a910f2380f ("net/smc: implement DMB-merged operations of
loopback-ism").
Tested SMC-D via ISM and dibs loopback.
Fixes: c3a910f2380f ("net/smc: implement DMB-merged operations of loopback-ism")
Cc: stable@vger.kernel.org
Signed-off-by: Hidayath Khan <hidayath@linux.ibm.com>
Reviewed-by: Alexandra Winter <wintera@linux.ibm.com>
---
drivers/dibs/dibs_loopback.c | 47 ++++++++++++++++++------------------
1 file changed, 24 insertions(+), 23 deletions(-)
diff --git a/drivers/dibs/dibs_loopback.c b/drivers/dibs/dibs_loopback.c
index 0f2e09311152..fd5caf1e19a8 100644
--- a/drivers/dibs/dibs_loopback.c
+++ b/drivers/dibs/dibs_loopback.c
@@ -118,14 +118,9 @@ static int dibs_lo_register_dmb(struct dibs_dev *dibs, struct dibs_dmb *dmb,
return rc;
}
-static void __dibs_lo_unregister_dmb(struct dibs_lo_dev *ldev,
- struct dibs_lo_dmb_node *dmb_node)
+static void dibs_lo_free_dmb(struct dibs_lo_dev *ldev,
+ struct dibs_lo_dmb_node *dmb_node)
{
- /* remove dmb from hash table */
- write_lock_bh(&ldev->dmb_ht_lock);
- hash_del(&dmb_node->list);
- write_unlock_bh(&ldev->dmb_ht_lock);
-
clear_bit(dmb_node->sba_idx, ldev->sba_idx_mask);
folio_put(virt_to_folio(dmb_node->cpu_addr));
kfree(dmb_node);
@@ -139,27 +134,33 @@ static int dibs_lo_unregister_dmb(struct dibs_dev *dibs, struct dibs_dmb *dmb)
struct dibs_lo_dmb_node *dmb_node = NULL, *tmp_node;
struct dibs_lo_dev *ldev;
unsigned long flags;
+ bool last;
ldev = dibs->drv_priv;
/* find dmb from hash table */
- read_lock_bh(&ldev->dmb_ht_lock);
+ write_lock_bh(&ldev->dmb_ht_lock);
hash_for_each_possible(ldev->dmb_ht, tmp_node, list, dmb->dmb_tok) {
if (tmp_node->token == dmb->dmb_tok) {
dmb_node = tmp_node;
break;
}
}
- read_unlock_bh(&ldev->dmb_ht_lock);
- if (!dmb_node)
+ if (!dmb_node) {
+ write_unlock_bh(&ldev->dmb_ht_lock);
return -EINVAL;
+ }
+ last = refcount_dec_and_test(&dmb_node->refcnt);
+ if (last)
+ hash_del(&dmb_node->list);
+ write_unlock_bh(&ldev->dmb_ht_lock);
- if (refcount_dec_and_test(&dmb_node->refcnt)) {
+ if (last) {
spin_lock_irqsave(&dibs->lock, flags);
dibs->dmb_clientid_arr[dmb_node->sba_idx] = NO_DIBS_CLIENT;
spin_unlock_irqrestore(&dibs->lock, flags);
- __dibs_lo_unregister_dmb(ldev, dmb_node);
+ dibs_lo_free_dmb(ldev, dmb_node);
}
return 0;
}
@@ -188,14 +189,9 @@ static int dibs_lo_attach_dmb(struct dibs_dev *dibs, struct dibs_dmb *dmb)
read_unlock_bh(&ldev->dmb_ht_lock);
return -EINVAL;
}
+ refcount_inc(&dmb_node->refcnt);
read_unlock_bh(&ldev->dmb_ht_lock);
- if (!refcount_inc_not_zero(&dmb_node->refcnt))
- /* the dmb is being unregistered, but has
- * not been removed from the hash table.
- */
- return -EINVAL;
-
/* provide dmb information */
dmb->idx = dmb_node->sba_idx;
dmb->dmb_tok = dmb_node->token;
@@ -209,11 +205,12 @@ static int dibs_lo_detach_dmb(struct dibs_dev *dibs, u64 token)
{
struct dibs_lo_dmb_node *dmb_node = NULL, *tmp_node;
struct dibs_lo_dev *ldev;
+ bool last;
ldev = dibs->drv_priv;
/* find dmb_node according to dmb->dmb_tok */
- read_lock_bh(&ldev->dmb_ht_lock);
+ write_lock_bh(&ldev->dmb_ht_lock);
hash_for_each_possible(ldev->dmb_ht, tmp_node, list, token) {
if (tmp_node->token == token) {
dmb_node = tmp_node;
@@ -221,13 +218,17 @@ static int dibs_lo_detach_dmb(struct dibs_dev *dibs, u64 token)
}
}
if (!dmb_node) {
- read_unlock_bh(&ldev->dmb_ht_lock);
+ write_unlock_bh(&ldev->dmb_ht_lock);
return -EINVAL;
}
- read_unlock_bh(&ldev->dmb_ht_lock);
+ last = refcount_dec_and_test(&dmb_node->refcnt);
+ if (last)
+ hash_del(&dmb_node->list);
+ write_unlock_bh(&ldev->dmb_ht_lock);
+
+ if (last)
+ dibs_lo_free_dmb(ldev, dmb_node);
- if (refcount_dec_and_test(&dmb_node->refcnt))
- __dibs_lo_unregister_dmb(ldev, dmb_node);
return 0;
}
base-commit: 9dca6104d80f94a285aad6eb6712d004b7151e7d
--
2.52.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [PATCH net] dibs: fix use-after-free of dmb_node in loopback attach/detach/unregister
2026-07-27 9:35 [PATCH net] dibs: fix use-after-free of dmb_node in loopback attach/detach/unregister Hidayath Khan
@ 2026-07-28 9:35 ` sashiko-bot
0 siblings, 0 replies; 2+ messages in thread
From: sashiko-bot @ 2026-07-28 9:35 UTC (permalink / raw)
To: Hidayath Khan
Cc: linux-s390, Vasily Gorbik, Alexander Gordeev, Heiko Carstens
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
Pre-existing issues:
- [High] dibs_lo_detach_dmb() fails to clear dibs->dmb_clientid_arr when dropping the final reference, leading to permanent client resource leaks.
- [High] Double free of dibs->dmb_clientid_arr in dibs_lo_dev_probe() error path.
- [High] Race condition in dibs_lo_move_data() routes spurious interrupts to wrong clients.
--
--- Patch [1]: [PATCH net] dibs: fix use-after-free of dmb_node in loopback attach/detach/unregister ---
commit 90d8ce044f1442abf2a546d5baabbf36276d4b45
Author: Hidayath Khan <hidayath@linux.ibm.com>
dibs: fix use-after-free of dmb_node in loopback attach/detach/unregister
This commit fixes a use-after-free race condition in the DMB loopback
operations. It closes the race by making the hash table membership and refcount
transitions atomic with respect to each other under the write lock. Freeing the
node is now handled safely after the lock is dropped.
> diff --git a/drivers/dibs/dibs_loopback.c b/drivers/dibs/dibs_loopback.c
> index 0f2e093111526..fd5caf1e19a8f 100644
> --- a/drivers/dibs/dibs_loopback.c
> +++ b/drivers/dibs/dibs_loopback.c
[ ... ]
> @@ -209,11 +205,12 @@ static int dibs_lo_detach_dmb(struct dibs_dev *dibs, u64 token)
> {
[ ... ]
> + last = refcount_dec_and_test(&dmb_node->refcnt);
> + if (last)
> + hash_del(&dmb_node->list);
> + write_unlock_bh(&ldev->dmb_ht_lock);
> +
> + if (last)
> + dibs_lo_free_dmb(ldev, dmb_node);
[Severity: High]
This isn't a bug introduced by this patch, but does this code fail to clear
the client ID array when dropping the final reference?
When an owner unregisters a DMB that still has active attachers (refcount > 1),
dibs_lo_unregister_dmb() does not clear the array because last == false.
When the final attacher detaches, dibs_lo_detach_dmb() drops the refcount to 0,
frees the node and the sba_idx, but structurally fails to clear
dibs->dmb_clientid_arr[sba_idx].
Could this leave a stale client ID in dmb_clientid_arr indefinitely, causing
future unregistration to fail with -EBUSY and permanently leaking the client
resource?
>
> - if (refcount_dec_and_test(&dmb_node->refcnt))
> - __dibs_lo_unregister_dmb(ldev, dmb_node);
> return 0;
> }
[Severity: High]
This is a pre-existing issue, but is there a double free of
dibs->dmb_clientid_arr in the dibs_lo_dev_probe() error path?
If dibs_dev_add() fails internally, it frees the array without nullifying it:
drivers/dibs/dibs_main.c:dibs_dev_add() {
...
free_client_arr:
kfree(dibs->dmb_clientid_arr);
return ret;
}
The caller receives the error and unconditionally frees the already-freed array
again:
drivers/dibs/dibs_loopback.c:dibs_lo_dev_probe() {
...
err_reg:
kfree(dibs->dmb_clientid_arr);
...
}
Could this double-free cause slab allocator corruption?
[Severity: High]
This is a pre-existing issue, but is there a race condition in
dibs_lo_move_data() that routes spurious interrupts to wrong clients?
The target sba_idx is cached and the protection lock is dropped:
drivers/dibs/dibs_loopback.c:dibs_lo_move_data() {
...
sba_idx = rmb_node->sba_idx;
read_unlock_bh(&ldev->dmb_ht_lock);
...
}
Before it acquires the lock to trigger the IRQ, another CPU could drop the
final reference to the DMB, free the node, and clear its sba_idx.
If a completely different client then immediately registers a new DMB and
reallocates the same sba_idx, will the IRQ be routed to the wrong client when
the code continues?
drivers/dibs/dibs_loopback.c:dibs_lo_move_data() {
...
spin_lock(&dibs->lock);
client_id = dibs->dmb_clientid_arr[sba_idx];
s_mask = ror16(0x1000, idx);
if (likely(client_id != NO_DIBS_CLIENT && dibs->subs[client_id]))
dibs->subs[client_id]->ops->handle_irq(dibs, sba_idx, s_mask);
...
}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727093530.968834-1-hidayath@linux.ibm.com?part=1
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-07-28 9:35 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-27 9:35 [PATCH net] dibs: fix use-after-free of dmb_node in loopback attach/detach/unregister Hidayath Khan
2026-07-28 9:35 ` sashiko-bot
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.