From: Dave Jiang <dave.jiang@intel.com>
To: Muchun Song <songmuchun@bytedance.com>,
Dan Williams <djbw@kernel.org>,
Vishal Verma <vishal.l.verma@intel.com>,
Alison Schofield <alison.schofield@intel.com>
Cc: Andrew Morton <akpm@linux-foundation.org>,
Joao Martins <joao.m.martins@oracle.com>,
nvdimm@lists.linux.dev, linux-cxl@vger.kernel.org,
linux-kernel@vger.kernel.org, muchun.song@linux.dev
Subject: Re: [PATCH 1/2] dax/bus: fix Device DAX range alignment validation
Date: Mon, 21 Sep 2026 14:01:20 -0700 [thread overview]
Message-ID: <8a90ff33-97e4-4796-a8a6-f484760557ca@intel.com> (raw)
In-Reply-To: <20260915095621.3744167-2-songmuchun@bytedance.com>
On 9/15/26 2:56 AM, Muchun Song wrote:
> 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)
Leave the name alloc_is_aligned() may generate less churn in this patch
DJ
> +{
> + /*
> + * 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;
next prev parent reply other threads:[~2026-09-21 21:01 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
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-21 21:01 ` Dave Jiang [this message]
2026-09-22 2:21 ` Muchun Song
2026-09-15 9:56 ` [PATCH 2/2] dax/bus: fix mapping attribute error reporting Muchun Song
2026-09-21 19:23 ` Dave Jiang
2026-09-22 2:27 ` Muchun Song
2026-09-18 17:02 ` [PATCH 0/2] dax: Fix Device DAX range allocation validation Dave Jiang
2026-09-19 5:03 ` Muchun Song
2026-09-21 18:58 ` Dave Jiang
2026-09-22 2:25 ` Muchun Song
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=8a90ff33-97e4-4796-a8a6-f484760557ca@intel.com \
--to=dave.jiang@intel.com \
--cc=akpm@linux-foundation.org \
--cc=alison.schofield@intel.com \
--cc=djbw@kernel.org \
--cc=joao.m.martins@oracle.com \
--cc=linux-cxl@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=muchun.song@linux.dev \
--cc=nvdimm@lists.linux.dev \
--cc=songmuchun@bytedance.com \
--cc=vishal.l.verma@intel.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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®