* [PATCH 0/2] dax: Fix Device DAX range allocation validation
@ 2026-09-15 9:56 Muchun Song
2026-09-15 9:56 ` [PATCH 1/2] dax/bus: fix Device DAX range alignment validation Muchun Song
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Muchun Song @ 2026-09-15 9:56 UTC (permalink / raw)
To: Dan Williams, Vishal Verma, Dave Jiang, Alison Schofield
Cc: Andrew Morton, Joao Martins, nvdimm, linux-cxl, linux-kernel,
Muchun Song, muchun.song
While working on the HugeTLB Vmemmap Optimization (HVO) generalization,
Sashiko reported that memmap_init_zone_device() could be asked to
initialize struct pages with a large-page order for a physical range
that was not naturally aligned to that order.
Following the Device DAX call path confirmed that the condition was
reachable. Device DAX range validation checks the range size, but not
its start address. An unaligned range can therefore reach
memmap_init_zone_device() after the device binds, and a subsequent write
to a userspace mapping may trigger a kernel panic. With the help of an
LLM, I was able to reproduce the failure.
The automatic resize path has a related problem. It can split an aligned
size request across arbitrary free gaps, consume an unaligned fragment,
and leave the resize partially applied when a later allocation fails.
This series validates both the start and size of every Device DAX range
and makes automatic resize account only for usable aligned space. It
also fixes the mapping sysfs attribute to propagate validation failures
instead of reporting success without allocating the requested range.
Muchun Song (2):
dax/bus: fix Device DAX range alignment validation
dax/bus: fix mapping attribute error reporting
drivers/dax/bus.c | 144 +++++++++++++++++++++++++++++++++++-----------
1 file changed, 109 insertions(+), 35 deletions(-)
base-commit: 1a1de54f7369cd2b5bac0f265910e60ad3a6b4c3
--
2.54.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH 1/2] dax/bus: fix Device DAX range alignment validation
2026-09-15 9:56 [PATCH 0/2] dax: Fix Device DAX range allocation validation Muchun Song
@ 2026-09-15 9:56 ` Muchun Song
2026-09-15 9:56 ` [PATCH 2/2] dax/bus: fix mapping attribute error reporting Muchun Song
2026-09-18 17:02 ` [PATCH 0/2] dax: Fix Device DAX range allocation validation Dave Jiang
2 siblings, 0 replies; 4+ messages in thread
From: Muchun Song @ 2026-09-15 9:56 UTC (permalink / raw)
To: Dan Williams, Vishal Verma, Dave Jiang, Alison Schofield
Cc: Andrew Morton, Joao Martins, nvdimm, linux-cxl, linux-kernel,
Muchun Song, muchun.song
dev_dax->align describes the page size used by a Device DAX mapping.
Both the start and size of every range must therefore be aligned to it;
otherwise the starting PFN cannot represent a naturally aligned page of
that size.
Only range sizes are currently validated. A dynamic device can therefore
select a large-page alignment and allocate a range whose start is not
naturally aligned to that page size. The device binds successfully, but a
subsequent write to a userspace mapping may trigger a kernel panic.
The automatic resize path can also split a size-aligned request across
arbitrary free gaps. When devices with different alignments fragment a
region, this can extend a range by less than its alignment. A later
allocation then fails, leaving the failed resize partially applied.
Validate both the start and size of allocated and adjusted ranges. Make
the resize path account only for usable aligned space before changing any
ranges, and skip gaps that cannot satisfy the device alignment. Initialize
the device alignment before allocating its initial range so that all
allocations use the same validation.
A mapping with an unaligned start is now rejected with -EINVAL, while a
naturally aligned mapping still binds successfully.
Fixes: 6d82120f4156 ("device-dax: add an 'align' attribute")
Assisted-by: LLM
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
drivers/dax/bus.c | 143 +++++++++++++++++++++++++++++++++++-----------
1 file changed, 109 insertions(+), 34 deletions(-)
diff --git a/drivers/dax/bus.c b/drivers/dax/bus.c
index b809e1a264af..54e4bbc98218 100644
--- a/drivers/dax/bus.c
+++ b/drivers/dax/bus.c
@@ -848,6 +848,44 @@ static int devm_register_dax_mapping(struct dev_dax *dev_dax, int range_id)
return 0;
}
+static inline unsigned long dev_dax_min_align(struct dev_dax *dev_dax)
+{
+ return max_t(unsigned long, dev_dax->align, memremap_compat_align());
+}
+
+static inline bool size_is_aligned(struct dev_dax *dev_dax, resource_size_t size)
+{
+ /*
+ * The minimum mapping granularity for a device instance is a
+ * single subsection, unless the arch says otherwise.
+ */
+ return IS_ALIGNED(size, dev_dax_min_align(dev_dax));
+}
+
+static inline bool range_is_aligned(struct dev_dax *dev_dax, u64 start,
+ resource_size_t size)
+{
+ return IS_ALIGNED(start | size, dev_dax_min_align(dev_dax));
+}
+
+static resource_size_t
+aligned_gap_size(struct dev_dax *dev_dax, resource_size_t *start,
+ resource_size_t end)
+{
+ resource_size_t aligned_start = ALIGN(*start, dev_dax_min_align(dev_dax));
+ resource_size_t size;
+
+ if (aligned_start < *start || aligned_start > end)
+ return 0;
+
+ size = ALIGN_DOWN(end - aligned_start + 1, dev_dax_min_align(dev_dax));
+ if (!size)
+ return 0;
+
+ *start = aligned_start;
+ return size;
+}
+
static int alloc_dev_dax_range(struct dev_dax *dev_dax, u64 start,
resource_size_t size)
{
@@ -870,6 +908,9 @@ static int alloc_dev_dax_range(struct dev_dax *dev_dax, u64 start,
return 0;
}
+ if (!range_is_aligned(dev_dax, start, size))
+ return -EINVAL;
+
alloc = __request_region(res, start, size, dev_name(dev), 0);
if (!alloc)
return -ENOMEM;
@@ -923,6 +964,9 @@ static int adjust_dev_dax_range(struct dev_dax *dev_dax, struct resource *res, r
if (dev_WARN_ONCE(dev, !size, "deletion is handled by dev_dax_shrink\n"))
return -EINVAL;
+ if (!range_is_aligned(dev_dax, range->start, size))
+ return -EINVAL;
+
rc = adjust_resource(res, range->start, size);
if (rc)
return rc;
@@ -955,15 +999,6 @@ static ssize_t size_show(struct device *dev,
return sysfs_emit(buf, "%llu\n", size);
}
-static bool alloc_is_aligned(struct dev_dax *dev_dax, resource_size_t size)
-{
- /*
- * The minimum mapping granularity for a device instance is a
- * single subsection, unless the arch says otherwise.
- */
- return IS_ALIGNED(size, max_t(unsigned long, dev_dax->align, memremap_compat_align()));
-}
-
static int dev_dax_shrink(struct dev_dax *dev_dax, resource_size_t size)
{
resource_size_t to_shrink = dev_dax_size(dev_dax) - size;
@@ -1030,30 +1065,55 @@ static bool adjust_ok(struct dev_dax *dev_dax, struct resource *res)
return true;
}
+static resource_size_t
+dax_region_aligned_avail_size(struct dax_region *dax_region,
+ struct dev_dax *dev_dax)
+{
+ struct resource *region_res = &dax_region->res;
+ resource_size_t start = region_res->start;
+ resource_size_t avail = 0;
+ struct resource *res;
+
+ lockdep_assert_held_write(&dax_region_rwsem);
+
+ for_each_dax_region_resource(dax_region, res) {
+ if (res->start > start) {
+ resource_size_t gap_start = start;
+
+ avail += aligned_gap_size(dev_dax, &gap_start,
+ res->start - 1);
+ }
+ start = res->end + 1;
+ }
+ if (start <= region_res->end)
+ avail += aligned_gap_size(dev_dax, &start, region_res->end);
+
+ return avail;
+}
+
static ssize_t dev_dax_resize(struct dax_region *dax_region,
struct dev_dax *dev_dax, resource_size_t size)
{
- resource_size_t avail = dax_region_avail_size(dax_region), to_alloc;
resource_size_t dev_size = dev_dax_size(dev_dax);
struct resource *region_res = &dax_region->res;
struct device *dev = &dev_dax->dev;
struct resource *res, *first;
- resource_size_t alloc = 0;
+ resource_size_t alloc, to_alloc;
int rc;
if (dev->driver)
return -EBUSY;
if (size == dev_size)
return 0;
- if (size > dev_size && size - dev_size > avail)
- return -ENOSPC;
if (size < dev_size)
return dev_dax_shrink(dev_dax, size);
to_alloc = size - dev_size;
- if (dev_WARN_ONCE(dev, !alloc_is_aligned(dev_dax, to_alloc),
- "resize of %pa misaligned\n", &to_alloc))
+ if (dev_WARN_ONCE(dev, !size_is_aligned(dev_dax, to_alloc),
+ "resize of %pa misaligned\n", &to_alloc))
return -ENXIO;
+ if (to_alloc > dax_region_aligned_avail_size(dax_region, dev_dax))
+ return -ENOSPC;
/*
* Expand the device into the unused portion of the region. This
@@ -1062,37 +1122,52 @@ static ssize_t dev_dax_resize(struct dax_region *dax_region,
*/
retry:
first = region_res->child;
- if (!first)
- return alloc_dev_dax_range(dev_dax, dax_region->res.start, to_alloc);
+ if (!first) {
+ resource_size_t start = region_res->start;
+
+ alloc = aligned_gap_size(dev_dax, &start, region_res->end);
+ return alloc_dev_dax_range(dev_dax, start,
+ min(alloc, to_alloc));
+ }
rc = -ENOSPC;
for (res = first; res; res = res->sibling) {
struct resource *next = res->sibling;
+ resource_size_t start, end;
/* space at the beginning of the region */
if (res == first && res->start > dax_region->res.start) {
- alloc = min(res->start - dax_region->res.start, to_alloc);
- rc = alloc_dev_dax_range(dev_dax, dax_region->res.start, alloc);
- break;
+ start = dax_region->res.start;
+ end = res->start - 1;
+ alloc = min(aligned_gap_size(dev_dax, &start, end), to_alloc);
+ if (alloc) {
+ rc = alloc_dev_dax_range(dev_dax, start, alloc);
+ break;
+ }
}
- alloc = 0;
/* space between allocations */
- if (next && next->start > res->end + 1)
- alloc = min(next->start - (res->end + 1), to_alloc);
-
- /* space at the end of the region */
- if (!alloc && !next && res->end < region_res->end)
- alloc = min(region_res->end - res->end, to_alloc);
+ if (next) {
+ if (next->start <= res->end + 1)
+ continue;
+ end = next->start - 1;
+ } else {
+ /* space at the end of the region */
+ if (res->end >= region_res->end)
+ continue;
+ end = region_res->end;
+ }
+ start = res->end + 1;
+ alloc = min(aligned_gap_size(dev_dax, &start, end), to_alloc);
if (!alloc)
continue;
- if (adjust_ok(dev_dax, res)) {
+ if (start == res->end + 1 && adjust_ok(dev_dax, res)) {
rc = adjust_dev_dax_range(dev_dax, res, resource_size(res) + alloc);
break;
}
- rc = alloc_dev_dax_range(dev_dax, res->end + 1, alloc);
+ rc = alloc_dev_dax_range(dev_dax, start, alloc);
break;
}
if (rc)
@@ -1115,7 +1190,7 @@ static ssize_t size_store(struct device *dev, struct device_attribute *attr,
if (rc)
return rc;
- if (!alloc_is_aligned(dev_dax, val)) {
+ if (!size_is_aligned(dev_dax, val)) {
dev_dbg(dev, "%s: size: %lld misaligned\n", __func__, val);
return -EINVAL;
}
@@ -1201,7 +1276,7 @@ static ssize_t mapping_store(struct device *dev, struct device_attribute *attr,
}
to_alloc = range_len(&r);
- if (alloc_is_aligned(dev_dax, to_alloc))
+ if (size_is_aligned(dev_dax, to_alloc))
rc = alloc_dev_dax_range(dev_dax, r.start, to_alloc);
up_write(&dax_dev_rwsem);
up_write(&dax_region_rwsem);
@@ -1224,9 +1299,9 @@ static ssize_t dev_dax_validate_align(struct dev_dax *dev_dax)
int i;
for (i = 0; i < dev_dax->nr_range; i++) {
- size_t len = range_len(&dev_dax->ranges[i].range);
+ struct range *range = &dev_dax->ranges[i].range;
- if (!alloc_is_aligned(dev_dax, len)) {
+ if (!range_is_aligned(dev_dax, range->start, range_len(range))) {
dev_dbg(dev, "%s: align %u invalid for range %d\n",
__func__, dev_dax->align, i);
return -EINVAL;
@@ -1464,6 +1539,7 @@ static struct dev_dax *__devm_create_dev_dax(struct dev_dax_data *data)
return ERR_PTR(-ENOMEM);
dev_dax->region = dax_region;
+ dev_dax->align = dax_region->align;
if (is_static(dax_region)) {
if (dev_WARN_ONCE(parent, data->id < 0,
"dynamic id specified to static region\n")) {
@@ -1522,7 +1598,6 @@ static struct dev_dax *__devm_create_dev_dax(struct dev_dax_data *data)
dev_dax->dax_dev = dax_dev;
dev_dax->target_node = dax_region->target_node;
- dev_dax->align = dax_region->align;
ida_init(&dev_dax->ida);
dev_dax->memmap_on_memory = data->memmap_on_memory;
--
2.54.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH 2/2] dax/bus: fix mapping attribute error reporting
2026-09-15 9:56 [PATCH 0/2] dax: Fix Device DAX range allocation validation Muchun Song
2026-09-15 9:56 ` [PATCH 1/2] dax/bus: fix Device DAX range alignment validation Muchun Song
@ 2026-09-15 9:56 ` Muchun Song
2026-09-18 17:02 ` [PATCH 0/2] dax: Fix Device DAX range allocation validation Dave Jiang
2 siblings, 0 replies; 4+ messages in thread
From: Muchun Song @ 2026-09-15 9:56 UTC (permalink / raw)
To: Dan Williams, Vishal Verma, Dave Jiang, Alison Schofield
Cc: Andrew Morton, Joao Martins, nvdimm, linux-cxl, linux-kernel,
Muchun Song, muchun.song
After the DAX configuration locking was converted to rwsems, successful
lock acquisition leaves rc set to zero in mapping_store(). If the requested
range size is misaligned, the allocation is skipped and the zero rc is
converted to len. The sysfs write therefore reports success without
allocating the requested range.
Call alloc_dev_dax_range() unconditionally and let its full range
validation return -EINVAL for a misaligned start or size.
Fixes: c05ae9d85b47 ("dax/bus.c: replace driver-core lock usage by a local rwsem")
Assisted-by: LLM
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
drivers/dax/bus.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/drivers/dax/bus.c b/drivers/dax/bus.c
index 54e4bbc98218..f232001ff5b7 100644
--- a/drivers/dax/bus.c
+++ b/drivers/dax/bus.c
@@ -1276,8 +1276,7 @@ static ssize_t mapping_store(struct device *dev, struct device_attribute *attr,
}
to_alloc = range_len(&r);
- if (size_is_aligned(dev_dax, to_alloc))
- rc = alloc_dev_dax_range(dev_dax, r.start, to_alloc);
+ rc = alloc_dev_dax_range(dev_dax, r.start, to_alloc);
up_write(&dax_dev_rwsem);
up_write(&dax_region_rwsem);
--
2.54.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH 0/2] dax: Fix Device DAX range allocation validation
2026-09-15 9:56 [PATCH 0/2] dax: Fix Device DAX range allocation validation Muchun Song
2026-09-15 9:56 ` [PATCH 1/2] dax/bus: fix Device DAX range alignment validation Muchun Song
2026-09-15 9:56 ` [PATCH 2/2] dax/bus: fix mapping attribute error reporting Muchun Song
@ 2026-09-18 17:02 ` Dave Jiang
2 siblings, 0 replies; 4+ messages in thread
From: Dave Jiang @ 2026-09-18 17:02 UTC (permalink / raw)
To: Muchun Song, Dan Williams, Vishal Verma, Alison Schofield
Cc: Andrew Morton, Joao Martins, nvdimm, linux-cxl, linux-kernel,
muchun.song
On 9/15/26 2:56 AM, Muchun Song wrote:
> While working on the HugeTLB Vmemmap Optimization (HVO) generalization,
> Sashiko reported that memmap_init_zone_device() could be asked to
> initialize struct pages with a large-page order for a physical range
> that was not naturally aligned to that order.
>
> Following the Device DAX call path confirmed that the condition was
> reachable. Device DAX range validation checks the range size, but not
> its start address. An unaligned range can therefore reach
> memmap_init_zone_device() after the device binds, and a subsequent write
> to a userspace mapping may trigger a kernel panic. With the help of an
> LLM, I was able to reproduce the failure.
>
> The automatic resize path has a related problem. It can split an aligned
> size request across arbitrary free gaps, consume an unaligned fragment,
> and leave the resize partially applied when a later allocation fails.
>
> This series validates both the start and size of every Device DAX range
> and makes automatic resize account only for usable aligned space. It
> also fixes the mapping sysfs attribute to propagate validation failures
> instead of reporting success without allocating the requested range.
>
> Muchun Song (2):
> dax/bus: fix Device DAX range alignment validation
> dax/bus: fix mapping attribute error reporting
>
> drivers/dax/bus.c | 144 +++++++++++++++++++++++++++++++++++-----------
> 1 file changed, 109 insertions(+), 35 deletions(-)
>
>
> base-commit: 1a1de54f7369cd2b5bac0f265910e60ad3a6b4c3
This commit does not exist in Linus's tree. Can you please base the series on top of the latest 7.3-rc tag so sashiko can look over it? Thanks!
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-09-18 17:02 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-15 9:56 [PATCH 0/2] dax: Fix Device DAX range allocation validation Muchun Song
2026-09-15 9:56 ` [PATCH 1/2] dax/bus: fix Device DAX range alignment validation Muchun Song
2026-09-15 9:56 ` [PATCH 2/2] dax/bus: fix mapping attribute error reporting Muchun Song
2026-09-18 17:02 ` [PATCH 0/2] dax: Fix Device DAX range allocation validation Dave Jiang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®