]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
IB/core: Fix zero dmac race in neighbor resolution
authorChen Zhao <chezhao@nvidia.com>
Sun, 5 Apr 2026 15:44:55 +0000 (18:44 +0300)
committerJason Gunthorpe <jgg@nvidia.com>
Thu, 9 Apr 2026 15:22:02 +0000 (12:22 -0300)
dst_fetch_ha() checks nud_state without holding the neighbor lock, then
copies ha under the seqlock. A race in __neigh_update() where nud_state
is set to NUD_REACHABLE before ha is written allows dst_fetch_ha() to
read a zero MAC address while the seqlock reports no concurrent writer.

netevent_callback amplifies this by waking ALL pending addr_req workers
when ANY neighbor becomes NUD_VALID. At scale (N peers resolving ARP
concurrently), the hit probability scales as N^2, making it near-certain
for large RDMA workloads.

N(A): neigh_update(A)                   W(A): addr_resolve(A)
 |                                       [sleep]
 | write_lock_bh(&A->lock)               |
 | A->nud_state = NUD_REACHABLE          |
 | // A->ha is still 0                   |
 |                                       [woken by netevent_cb() of
 |                                         another neighbour]
 |                                       | dst_fetch_ha(A)
 |                                       |   A->nud_state & NUD_VALID
 |                                       |   read_seqbegin(&A->ha_lock)
 |                                       |   snapshot = A->ha  /* 0 */
 |                                       |   read_seqretry(&A->ha_lock)
 |                                       |   return snapshot
 | seqlock(&A->ha_lock)
 | A->ha = mac_A     /* too late */
 | sequnlock(&A->ha_lock)
 | write_unlock_bh(&A->lock)

The incorrect/zero mac is read and programmed in the device QP while it
was not yet updated. This causes silent packet loss and eventual
RETRY_EXC_ERR.

Fix by holding the neighbor read lock across the nud_state check and
ha copy in dst_fetch_ha(), ensuring it synchronizes with
__neigh_update() which is updating while holding the write lock.

Cc: stable@vger.kernel.org
Fixes: 92ebb6a0a13a ("IB/cm: Remove now useless rcu_lock in dst_fetch_ha")
Link: https://patch.msgid.link/r/20260405-fix-dmac-race-v1-1-cfa1ec2ce54a@nvidia.com
Signed-off-by: Chen Zhao <chezhao@nvidia.com>
Reviewed-by: Parav Pandit <parav@nvidia.com>
Signed-off-by: Leon Romanovsky <leonro@nvidia.com>
Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
drivers/infiniband/core/addr.c

index 866746695712aeae425100eefb231e44d52d52d4..6526fda8f9c0bbbcddcf54f5c953d3f7a9785d66 100644 (file)
@@ -321,11 +321,14 @@ static int dst_fetch_ha(const struct dst_entry *dst,
        if (!n)
                return -ENODATA;
 
+       read_lock_bh(&n->lock);
        if (!(n->nud_state & NUD_VALID)) {
+               read_unlock_bh(&n->lock);
                neigh_event_send(n, NULL);
                ret = -ENODATA;
        } else {
                neigh_ha_snapshot(dev_addr->dst_dev_addr, n, dst->dev);
+               read_unlock_bh(&n->lock);
        }
 
        neigh_release(n);