[PATCH v2] xarray: unlink a node before freeing it
From: Nickolai Zeldovich
Date: Sun Nov 01 2026 - 09:04:43 EST
xas_delete_node() passes an empty node to xa_node_free(), which queues
it with call_rcu(), before it stores NULL over the pointer to the node
in the parent's slot or in xa_head. A lockless walker that loads that
pointer after the grace period has started is not waited for, and
reads the node after it is freed.
Free the node after unlinking it, as delete_node() in lib/radix-tree.c
does.
The radix-tree test suite's multiorder test hits this under
AddressSanitizer: a heap-use-after-free in xas_start() in 19 of 200
runs, none with this patch.
Fixes: 58d6ea3085f2 ("xarray: Add XArray unconditional store operations")
Signed-off-by: Nickolai Zeldovich <nickolai@xxxxxxxxxxxxx>
---
Notes:
This patch was prepared with the help of an AI tool.
Changes in v2:
- Resend with a shorter changelog, per Andrew Morton and Matthew Wilcox.
No code change.
v1: https://lore.kernel.org/all/20261012174230.772325-1-nickolai@xxxxxxxxxxxxx/
lib/xarray.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lib/xarray.c b/lib/xarray.c
index bfe7bef80f34..d48754c5f8b8 100644
--- a/lib/xarray.c
+++ b/lib/xarray.c
@@ -500,17 +500,18 @@ static void xas_delete_node(struct xa_state *xas)
parent = xa_parent_locked(xas->xa, node);
xas->xa_node = parent;
xas->xa_offset = node->offset;
- xa_node_free(node);
if (!parent) {
xas->xa->xa_head = NULL;
xas->xa_node = XAS_BOUNDS;
+ xa_node_free(node);
return;
}
parent->slots[xas->xa_offset] = NULL;
parent->count--;
XA_NODE_BUG_ON(parent, parent->count > XA_CHUNK_SIZE);
+ xa_node_free(node);
node = parent;
xas_update(xas, node);
}
base-commit: cbbddb6b08cc9900a87cccaa9b22bbe06aa4f50a
--
2.53.0