All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] dax: fix wrong size passed to dax_iomap_direct_access() in dax_range_compare_iter()
@ 2026-10-10  7:14 jianyungao89
  2026-10-10  7:35 ` sashiko-bot
  0 siblings, 1 reply; 3+ messages in thread
From: jianyungao89 @ 2026-10-10  7:14 UTC (permalink / raw)
  To: linux-kernel
  Cc: Jianyun Gao, stable, Alexander Viro, Christian Brauner, Jan Kara,
	Dan Williams, Matthew Wilcox, Goldwyn Rodrigues, Darrick J. Wong,
	Andrew Morton, Shiyang Ruan,
	open list:FILESYSTEMS (VFS and infrastructure),
	open list:FILESYSTEM DIRECT ACCESS (DAX)

From: Jianyun Gao <jianyungao89@gmail.com>

dax_range_compare_iter() passes ALIGN(pos + len, PAGE_SIZE) as the size
argument to dax_iomap_direct_access(), but this is an absolute file
offset rather than a length. This causes the underlying
dax_direct_access() to request a massively inflated nr_pages via
PHYS_PFN(size), leading to unnecessarily large access ranges and
potential spurious -EHWPOISON from driver badblock scans.

Fix by computing the correct aligned size:
  ALIGN(len + (pos & ~PAGE_MASK), PAGE_SIZE)

which accounts for the page-in-page offset of pos, matching the pattern
used in dax_iomap_copy_around().

Fixes: 6f7db3894ae2 ("fsdax: dedup file range to use a compare function")
Cc: <stable@vger.kernel.org>
Signed-off-by: Jianyun Gao <jianyungao89@gmail.com>
---
 fs/dax.c | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/fs/dax.c b/fs/dax.c
index 6ba50142eeb2..213b1c9e197c 100644
--- a/fs/dax.c
+++ b/fs/dax.c
@@ -2197,6 +2197,7 @@ static int dax_range_compare_iter(struct iomap_iter *it_src,
 	const struct iomap *dmap = &it_dest->iomap;
 	loff_t pos1 = it_src->pos, pos2 = it_dest->pos;
 	void *saddr, *daddr;
+	size_t size1, size2;
 	int id, ret;
 
 	len = min(len, min(smap->length, dmap->length));
@@ -2212,13 +2213,13 @@ static int dax_range_compare_iter(struct iomap_iter *it_src,
 	}
 
 	id = dax_read_lock();
-	ret = dax_iomap_direct_access(smap, pos1, ALIGN(pos1 + len, PAGE_SIZE),
-				      &saddr, NULL);
+	size1 = ALIGN(len + (pos1 & ~PAGE_MASK), PAGE_SIZE);
+	ret = dax_iomap_direct_access(smap, pos1, size1, &saddr, NULL);
 	if (ret < 0)
 		goto out_unlock;
 
-	ret = dax_iomap_direct_access(dmap, pos2, ALIGN(pos2 + len, PAGE_SIZE),
-				      &daddr, NULL);
+	size2 = ALIGN(len + (pos2 & ~PAGE_MASK), PAGE_SIZE);
+	ret = dax_iomap_direct_access(dmap, pos2, size2, &daddr, NULL);
 	if (ret < 0)
 		goto out_unlock;
 
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [PATCH] dax: fix wrong size passed to dax_iomap_direct_access() in dax_range_compare_iter()
  2026-10-10  7:14 [PATCH] dax: fix wrong size passed to dax_iomap_direct_access() in dax_range_compare_iter() jianyungao89
@ 2026-10-10  7:35 ` sashiko-bot
  2026-10-11  6:35   ` Jianyun Gao
  0 siblings, 1 reply; 3+ messages in thread
From: sashiko-bot @ 2026-10-10  7:35 UTC (permalink / raw)
  To: jianyungao89; +Cc: nvdimm

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] fsdax: dax_range_compare_iter() memcmp ignores page-in-page offset
- [High] fsdax: out-of-bounds access due to bypassed length check in dax_iomap_direct_access()
--

commit 05244bba82891c8d959214ace5da854f182c9e20
Author: Jianyun Gao <jianyungao89@gmail.com>

dax: fix wrong size passed to dax_iomap_direct_access() in dax_range_compare_iter()

dax_range_compare_iter() passes ALIGN(pos + len, PAGE_SIZE) as the size
argument to dax_iomap_direct_access(), but this is an absolute file
offset rather than a length. This causes the underlying
dax_direct_access() to request a massively inflated nr_pages via
PHYS_PFN(size), leading to unnecessarily large access ranges and
potential spurious -EHWPOISON from driver badblock scans.

> diff --git a/fs/dax.c b/fs/dax.c
> index 08adeaed2baf1..fb12d0c0dcd49 100644
> --- a/fs/dax.c
> +++ b/fs/dax.c
> @@ -2190,6 +2190,7 @@ static int dax_range_compare_iter(struct iomap_iter *it_src,
>  	const struct iomap *dmap = &it_dest->iomap;
>  	loff_t pos1 = it_src->pos, pos2 = it_dest->pos;
>  	void *saddr, *daddr;
> +	size_t size1, size2;
>  	int id, ret;
>  
>  	len = min(len, min(smap->length, dmap->length));
> @@ -2205,13 +2206,13 @@ static int dax_range_compare_iter(struct iomap_iter *it_src,
>  	}
>  
>  	id = dax_read_lock();
> -	ret = dax_iomap_direct_access(smap, pos1, ALIGN(pos1 + len, PAGE_SIZE),
> -				      &saddr, NULL);
> +	size1 = ALIGN(len + (pos1 & ~PAGE_MASK), PAGE_SIZE);
> +	ret = dax_iomap_direct_access(smap, pos1, size1, &saddr, NULL);

[Severity: High]
Does this code risk an out-of-bounds access in the subsequent memcmp() if the
underlying physical DAX memory mapped is shorter than requested?

Looking at dax_iomap_direct_access(), it bypasses the length validation when
the pfnp argument is NULL:

fs/dax.c:dax_iomap_direct_access() {
    ...
	if (!pfnp)
		goto out_check_addr;
	rc = -EINVAL;
	if (PFN_PHYS(length) < size)
		goto out;
    ...
}

If the iomap extent crosses a device mapper target boundary, such as on
dm-linear, dm_dax_direct_access() truncates the mapping length and returns
fewer pages than requested.

Because dax_iomap_direct_access() is called here with a NULL pfnp, it skips
the length check and returns success even for short mappings.

Could this regression cause the subsequent memcmp() to read beyond the
physically contiguous mapped memory?

>  	if (ret < 0)
>  		goto out_unlock;
>  
> -	ret = dax_iomap_direct_access(dmap, pos2, ALIGN(pos2 + len, PAGE_SIZE),
> -				      &daddr, NULL);
> +	size2 = ALIGN(len + (pos2 & ~PAGE_MASK), PAGE_SIZE);
> +	ret = dax_iomap_direct_access(dmap, pos2, size2, &daddr, NULL);
>  	if (ret < 0)
>  		goto out_unlock;
>  

[Severity: High]
Is it possible that the memcmp() that follows compares the wrong file data
because it ignores the page-in-page offset?

The dax_iomap_direct_access() function translates a file offset to a kernel
virtual address, but the returned pointers saddr and daddr are strictly
page-aligned, ignoring any page-in-page offset (pos & ~PAGE_MASK).

When filesystem block sizes are smaller than the architecture's page size,
deduplication requests can be block-aligned but not page-aligned.

If memcmp() is called directly on saddr and daddr without adding the
pos1 & ~PAGE_MASK and pos2 & ~PAGE_MASK offsets:

fs/dax.c:dax_range_compare_iter() {
    ...
	*same = !memcmp(saddr, daddr, len);
    ...
}

Will this regression compare memory starting from the page boundary instead of
the actual file offset? Could this lead to FIDEDUPERANGE incorrectly
deduplicating non-identical blocks and causing data corruption?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261010071453.6028-1-jianyungao89@gmail.com?part=1

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH] dax: fix wrong size passed to dax_iomap_direct_access() in dax_range_compare_iter()
  2026-10-10  7:35 ` sashiko-bot
@ 2026-10-11  6:35   ` Jianyun Gao
  0 siblings, 0 replies; 3+ messages in thread
From: Jianyun Gao @ 2026-10-11  6:35 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: nvdimm

Thank you for the review!

Regarding the first issue (memcmp ignores page-in-page offset), you
are correct. This will be fixed in the v2 version of this patch, which
adds offset_in_page(pos1)/offset_in_page(pos2) to the saddr/daddr
arguments in the memcmp() call.

Regarding the second issue (bypassed length check when pfnp is NULL),
this is a pre-existing behavior of dax_iomap_direct_access() shared by
all callers that pass a NULL pfnp (e.g., dax_iomap_copy_around(),
dax_unshare_iter()). In the dedupe path, the access range is
guaranteed to be within the iomap mapping via len = min(len,
min(smap->length, dmap->length)), and filesystem iomap mappings do not
cross device boundaries. This is outside the scope of this patch and
would be better addressed as a separate fix to
dax_iomap_direct_access() itself.

On Sat, Oct 10, 2026 at 3:35 PM <sashiko-bot@kernel.org> wrote:
>
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [High] fsdax: dax_range_compare_iter() memcmp ignores page-in-page offset
> - [High] fsdax: out-of-bounds access due to bypassed length check in dax_iomap_direct_access()
> --
>
> commit 05244bba82891c8d959214ace5da854f182c9e20
> Author: Jianyun Gao <jianyungao89@gmail.com>
>
> dax: fix wrong size passed to dax_iomap_direct_access() in dax_range_compare_iter()
>
> dax_range_compare_iter() passes ALIGN(pos + len, PAGE_SIZE) as the size
> argument to dax_iomap_direct_access(), but this is an absolute file
> offset rather than a length. This causes the underlying
> dax_direct_access() to request a massively inflated nr_pages via
> PHYS_PFN(size), leading to unnecessarily large access ranges and
> potential spurious -EHWPOISON from driver badblock scans.
>
> > diff --git a/fs/dax.c b/fs/dax.c
> > index 08adeaed2baf1..fb12d0c0dcd49 100644
> > --- a/fs/dax.c
> > +++ b/fs/dax.c
> > @@ -2190,6 +2190,7 @@ static int dax_range_compare_iter(struct iomap_iter *it_src,
> >       const struct iomap *dmap = &it_dest->iomap;
> >       loff_t pos1 = it_src->pos, pos2 = it_dest->pos;
> >       void *saddr, *daddr;
> > +     size_t size1, size2;
> >       int id, ret;
> >
> >       len = min(len, min(smap->length, dmap->length));
> > @@ -2205,13 +2206,13 @@ static int dax_range_compare_iter(struct iomap_iter *it_src,
> >       }
> >
> >       id = dax_read_lock();
> > -     ret = dax_iomap_direct_access(smap, pos1, ALIGN(pos1 + len, PAGE_SIZE),
> > -                                   &saddr, NULL);
> > +     size1 = ALIGN(len + (pos1 & ~PAGE_MASK), PAGE_SIZE);
> > +     ret = dax_iomap_direct_access(smap, pos1, size1, &saddr, NULL);
>
> [Severity: High]
> Does this code risk an out-of-bounds access in the subsequent memcmp() if the
> underlying physical DAX memory mapped is shorter than requested?
>
> Looking at dax_iomap_direct_access(), it bypasses the length validation when
> the pfnp argument is NULL:
>
> fs/dax.c:dax_iomap_direct_access() {
>     ...
>         if (!pfnp)
>                 goto out_check_addr;
>         rc = -EINVAL;
>         if (PFN_PHYS(length) < size)
>                 goto out;
>     ...
> }
>
> If the iomap extent crosses a device mapper target boundary, such as on
> dm-linear, dm_dax_direct_access() truncates the mapping length and returns
> fewer pages than requested.
>
> Because dax_iomap_direct_access() is called here with a NULL pfnp, it skips
> the length check and returns success even for short mappings.
>
> Could this regression cause the subsequent memcmp() to read beyond the
> physically contiguous mapped memory?
>
> >       if (ret < 0)
> >               goto out_unlock;
> >
> > -     ret = dax_iomap_direct_access(dmap, pos2, ALIGN(pos2 + len, PAGE_SIZE),
> > -                                   &daddr, NULL);
> > +     size2 = ALIGN(len + (pos2 & ~PAGE_MASK), PAGE_SIZE);
> > +     ret = dax_iomap_direct_access(dmap, pos2, size2, &daddr, NULL);
> >       if (ret < 0)
> >               goto out_unlock;
> >
>
> [Severity: High]
> Is it possible that the memcmp() that follows compares the wrong file data
> because it ignores the page-in-page offset?
>
> The dax_iomap_direct_access() function translates a file offset to a kernel
> virtual address, but the returned pointers saddr and daddr are strictly
> page-aligned, ignoring any page-in-page offset (pos & ~PAGE_MASK).
>
> When filesystem block sizes are smaller than the architecture's page size,
> deduplication requests can be block-aligned but not page-aligned.
>
> If memcmp() is called directly on saddr and daddr without adding the
> pos1 & ~PAGE_MASK and pos2 & ~PAGE_MASK offsets:
>
> fs/dax.c:dax_range_compare_iter() {
>     ...
>         *same = !memcmp(saddr, daddr, len);
>     ...
> }
>
> Will this regression compare memory starting from the page boundary instead of
> the actual file offset? Could this lead to FIDEDUPERANGE incorrectly
> deduplicating non-identical blocks and causing data corruption?
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20261010071453.6028-1-jianyungao89@gmail.com?part=1

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-10-11  6:36 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-10  7:14 [PATCH] dax: fix wrong size passed to dax_iomap_direct_access() in dax_range_compare_iter() jianyungao89
2026-10-10  7:35 ` sashiko-bot
2026-10-11  6:35   ` Jianyun Gao

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.