Re: [syzbot] [rdma?] WARNING in ib_dealloc_device

Jason Gunthorpe
2026-04-13T17:42:31Z

On Mon, Apr 13, 2026 at 04:12:09PM +0000, Jiri Pirko wrote:
> Will check it tmrw

I fed it to Claude and after 40 mins it is stumped too.. It should not
be possible for this to happen.

__ib_unregister_device() always calls down to disable_device()

Which always removes it from all visibility, drives the refcount to 0
and then cleans the xarray:

xa_for_each (&device->compat_devs, index, cdev)
remove_one_compat_dev(device, index);

Then ib_dealloc_device() checks it is empty:

WARN_ON(!xa_empty(&device->compat_devs));

At the point the xa_for_each is run there should be no cocurrent
threads that can see the device. The refcount is zero, it was removed
from the xarray. The add_one_compat_dev() is never called in an
condition that could see a stray device.

It should not be possible for the compat_devs of a 0 refcount
ib_device removed from the device's xarray to be mutated between those
two checks.

One notable thing about xarray is you can have a xa_for_each() iterate
over nothing and also have xa_empty() be false. Maybe that is
happening here, but I could not find any way that should happen.

I guess just keep watching this and see if it happens ever again. Add
some debugging to print out the xarray. Maybe the way we are using
xarray is unexpectedly triggering a stray 0 entry?

Jason

diff --git a/drivers/infiniband/core/device.c b/drivers/infiniband/core/device.c
index 4c174f7f1070cb..592e29b0cccf39 100644
--- a/drivers/infiniband/core/device.c
+++ b/drivers/infiniband/core/device.c
@@ -1020,6 +1020,71 @@ static void remove_compat_devs(struct ib_device *device)

xa_for_each (&device->compat_devs, index, cdev)
remove_one_compat_dev(device, index);
+
+ if (!xa_empty(&device->compat_devs)) {
+ struct xa_node *node;
+ void *head;
+ unsigned int i;
+
+ dev_warn(&device->dev,
+ "compat_devs xarray not empty after removal!\n");
+
+ xa_lock(&device->compat_devs);
+ head = xa_head_locked(&device->compat_devs);
+ dev_warn(&device->dev, " xa_head=%px xa_flags=%x\n",
+ head, device->compat_devs.xa_flags);
+
+ if (!xa_is_node(head)) {
+ /* Single entry at index 0 stored directly in head */
+ if (xa_is_zero(head))
+ dev_warn(&device->dev,
+ " head[0]: zero entry (leaked xa_reserve)\n");
+ else if (!xa_is_internal(head))
+ dev_warn(&device->dev,
+ " head[0]: pointer %px\n", head);
+ else
+ dev_warn(&device->dev,
+ " head[0]: internal %px (%lu)\n",
+ head, xa_to_internal(head));
+ } else {
+ node = xa_to_node(head);
+ dev_warn(&device->dev,
+ " node %px shift %d count %d nr_values %d\n",
+ node, node->shift, node->count,
+ node->nr_values);
+ for (i = 0; i < XA_CHUNK_SIZE; i++) {
+ void *entry = xa_entry_locked(
+ &device->compat_devs, node, i);
+
+ if (!entry)
+ continue;
+ if (xa_is_zero(entry))
+ dev_warn(&device->dev,
+ " slot[%u]: zero entry (leaked xa_reserve)\n",
+ i);
+ else if (xa_is_sibling(entry))
+ dev_warn(&device->dev,
+ " slot[%u]: sibling -> slot %lu\n",
+ i, xa_to_sibling(entry));
+ else if (xa_is_retry(entry))
+ dev_warn(&device->dev,
+ " slot[%u]: retry\n", i);
+ else if (xa_is_node(entry))
+ dev_warn(&device->dev,
+ " slot[%u]: node %px (deeper tree)\n",
+ i, xa_to_node(entry));
+ else if (!xa_is_internal(entry))
+ dev_warn(&device->dev,
+ " slot[%u]: pointer %px\n",
+ i, entry);
+ else
+ dev_warn(&device->dev,
+ " slot[%u]: unknown internal %px\n",
+ i, entry);
+ }
+ }
+ xa_unlock(&device->compat_devs);
+ }
}

static int add_compat_devs(struct ib_device *device)