[PATCH] dax/bus: avoid deadlock when reading mapping attributes
From: Guixiong Wei
Date: Fri Oct 30 2026 - 01:07:14 EST
Reading a mapping's start, end, or page_offset attribute takes
dax_region_rwsem while holding a kernfs active reference. When
size_store() shrinks a device, it holds the rwsem while unregistering
mappings. Unregistration waits in kernfs_drain() for active references
to be released, so a concurrent attribute read can deadlock:
Reader size_store()
kernfs_get_active()
down_write(dax_region_rwsem)
device_unregister(mapping)
kernfs_drain()
down_write(dax_region_rwsem)
Setting range_id to -1 before unregistering does not break the cycle
because get_dax_range() checks it only after acquiring the rwsem.
Use down_write_trylock() and restart the read on contention. Returning
from the show callback allows kernfs to drop the active reference, so
mapping unregistration can finish. Keep active protection throughout
the callback to preserve the existing teardown synchronization.
The same inversion existed with the region device lock before it was
replaced by dax_region_rwsem, so the problem dates back to the
introduction of mapping devices.
Fixes: 0b07ce872a9e ("device-dax: introduce 'mapping' devices")
Cc: stable@xxxxxxxxxxxxxxx # 5.10+
Signed-off-by: Guixiong Wei <weiguixiong@xxxxxxxxxxxxx>
---
drivers/dax/bus.c | 26 +++++++++++++++-----------
1 file changed, 15 insertions(+), 11 deletions(-)
diff --git a/drivers/dax/bus.c b/drivers/dax/bus.c
index e40c25401cf06..823d60353fc83 100644
--- a/drivers/dax/bus.c
+++ b/drivers/dax/bus.c
@@ -3,6 +3,7 @@
#include <linux/memremap.h>
#include <linux/device.h>
#include <linux/mutex.h>
+#include <linux/sched/signal.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/dax.h>
@@ -719,14 +720,17 @@ static struct dev_dax_range *get_dax_range(struct device *dev)
{
struct dax_mapping *mapping = to_dax_mapping(dev);
struct dev_dax *dev_dax = to_dev_dax(dev->parent);
- int rc;
- rc = down_write_killable(&dax_region_rwsem);
- if (rc)
- return NULL;
+ /*
+ * Mapping removal drains this attribute with dax_region_rwsem held.
+ * Restart the read on contention to release the kernfs active
+ * reference without waiting for the rwsem.
+ */
+ if (!down_write_trylock(&dax_region_rwsem))
+ return ERR_PTR(restart_syscall());
if (mapping->range_id < 0) {
up_write(&dax_region_rwsem);
- return NULL;
+ return ERR_PTR(-ENXIO);
}
return &dev_dax->ranges[mapping->range_id];
@@ -744,8 +748,8 @@ static ssize_t start_show(struct device *dev,
ssize_t rc;
dax_range = get_dax_range(dev);
- if (!dax_range)
- return -ENXIO;
+ if (IS_ERR(dax_range))
+ return PTR_ERR(dax_range);
rc = sysfs_emit(buf, "%#llx\n", dax_range->range.start);
put_dax_range();
@@ -760,8 +764,8 @@ static ssize_t end_show(struct device *dev,
ssize_t rc;
dax_range = get_dax_range(dev);
- if (!dax_range)
- return -ENXIO;
+ if (IS_ERR(dax_range))
+ return PTR_ERR(dax_range);
rc = sysfs_emit(buf, "%#llx\n", dax_range->range.end);
put_dax_range();
@@ -776,8 +780,8 @@ static ssize_t pgoff_show(struct device *dev,
ssize_t rc;
dax_range = get_dax_range(dev);
- if (!dax_range)
- return -ENXIO;
+ if (IS_ERR(dax_range))
+ return PTR_ERR(dax_range);
rc = sysfs_emit(buf, "%#lx\n", dax_range->pgoff);
put_dax_range();
--
2.55.0