[PATCH v2] dma: swiotlb: Properly tear down the default pool on exit

From: Marek Szyprowski

Date: Mon Nov 09 2026 - 12:13:06 EST


With CONFIG_SWIOTLB_DYNAMIC enabled, the default memory pool is added to
the io_tlb_default_mem.pools RCU list during initialization. When an IOMMU
takes over DMA translation, swiotlb_exit() tears the default pool down and
clears it with memset(), but leaves its node on the pools list. The list
then contains a zeroed entry with a NULL next pointer, and any later walk
of the list, for example mem_used() when reading the io_tlb_used debugfs
file, dereferences an invalid pointer and crashes.

swiotlb_exit() also leaves the io_tlb_default_mem fields that describe
the pool. io_tlb_default_mem.nslabs and can_grow stay set, so
is_swiotlb_allocated() and is_swiotlb_active() keep returning true, and
swiotlb_find_slots() can schedule dynamic pool allocation after teardown.
Without CONFIG_SWIOTLB_DYNAMIC, a later mapping can reach the zeroed
defpool and hit BUG_ON(area_index >= pool->nareas). Before the pool data
was split out of struct io_tlb_mem, swiotlb_exit() cleared the whole
structure, including nslabs.

Clear can_grow and cancel any pending dyn_alloc work, then remove the
default pool from the list and subtract its slots from nslabs, and wait
for an RCU grace period before clearing it, so that no reader can still
see it. Without CONFIG_SWIOTLB_DYNAMIC, reset nslabs directly.

As a result, with CONFIG_SWIOTLB_DYNAMIC swiotlb no longer silently keeps
working through dynamic or transient pools after an IOMMU has turned it
off, so for example dma-iommu bouncing for untrusted devices reports that
bounce buffers are inactive, as it did before the dynamic pools support.

Fixes: 158dbe9c9a3d ("swiotlb: separate memory pool data from other allocator data")
Fixes: 62708b2ba405 ("swiotlb: add a flag whether SWIOTLB is allowed to grow")
Fixes: 1aaa736815eb ("swiotlb: allocate a new memory pool when existing pools are full")
Closes: https://sashiko.dev/#/bug/linux-f75998aa-65ea-4596-9bb4-fa83773012a3
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Marek Szyprowski <m.szyprowski@xxxxxxxxxxx>
---
The patch was prepared with LLM assistance.

Best regards
Marek Szyprowski, PhD
Samsung R&D Institute Poland
---
Changelog:

v2:
- addressed more sashiko.dev pointed issues: clear io_tlb_default_mem.nslabs
and io_tlb_default_mem.can_grow, as well as cancel io_tlb_default_mem.dyn_alloc

v1: https://lore.kernel.org/all/20261026095423.966856-4-m.szyprowski@xxxxxxxxxxx/
---
kernel/dma/swiotlb.c | 11 +++++++++++
1 file changed, 11 insertions(+)

diff --git a/kernel/dma/swiotlb.c b/kernel/dma/swiotlb.c
index 9b0bc272dd82..786954bc5e6a 100644
--- a/kernel/dma/swiotlb.c
+++ b/kernel/dma/swiotlb.c
@@ -665,6 +665,17 @@ void __init swiotlb_exit(void)
return;

pr_info("tearing down default memory pool\n");
+#ifdef CONFIG_SWIOTLB_DYNAMIC
+ io_tlb_default_mem.can_grow = false;
+ cancel_work_sync(&io_tlb_default_mem.dyn_alloc);
+ spin_lock(&io_tlb_default_mem.lock);
+ list_del_rcu(&mem->node);
+ io_tlb_default_mem.nslabs -= mem->nslabs;
+ spin_unlock(&io_tlb_default_mem.lock);
+ synchronize_rcu();
+#else
+ io_tlb_default_mem.nslabs = 0;
+#endif
tbl_vaddr = (unsigned long)phys_to_virt(mem->start);
tbl_size = PAGE_ALIGN(mem->end - mem->start);
slots_size = PAGE_ALIGN(array_size(sizeof(*mem->slots), mem->nslabs));
--
2.43.0