Linux filesystem development
 help / color / mirror / Atom feed
From: Senjin <senjin@hatchling.org>
To: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
Cc: Senjin <senjin@hatchling.org>,
	ntfs3@lists.linux.dev, linux-fsdevel@vger.kernel.org,
	stable@vger.kernel.org
Subject: [PATCH 1/2] fs/ntfs3: map the right page of large folios in ntfs_read_write_run()
Date: 21 Oct 2026 04:58:38 -0000	[thread overview]
Message-ID: <6ad8467ee2cb35e73a596bc5@mailersend.net> (raw)
In-Reply-To: <20261021045734.573753-1-senjin@hatchling.org>

ntfs_read_write_run() reads and writes through the block device page
cache. It looks up the folio that contains lbo with read_mapping_folio(),
but then maps offset 0 of that folio and only adds offset_in_page(lbo):

	folio = read_mapping_folio(mapping, lbo >> PAGE_SHIFT, NULL);
	off = offset_in_page(lbo);
	kaddr = kmap_local_folio(folio, 0);
	memcpy(buf, kaddr + off, op);

This is only correct for order-0 folios. If the folio is a large folio
that starts before lbo, the data is copied from (or written to) the wrong
page, (index - folio->index) * PAGE_SIZE bytes before the intended
position.

ntfs3 itself populates the block device mapping with order-0 folios, so
this goes unnoticed on a cold cache. But a userspace read of the block
device (ntfs-3g tools such as ntfscat/ntfsinfo, probing tools, ...) fills
the same mapping through readahead with large folios. After that,
reading a WOF (LZX/XPRESS) compressed file passes garbage to the
decompressor: on a test volume, lzx_decompress() failed for 33 of 35
frames of a 1.1 MB file, and the compressed input it was given matched
the real WofCompressedData stream shifted back by 4 KiB to 52 KiB.

Reproducer (WOF-compressed file, e.g. made with "compact /c /exe:lzx"):

	echo 3 > /proc/sys/vm/drop_caches
	ntfscat -f -a 0x80 -n WofCompressedData $DEV /path/file >/dev/null
	md5sum /mnt/path/file    # wrong; correct without the ntfscat step

On a corpus of 16,716 WOF-compressed files, reading each file right
after ntfscat had read its stream gave wrong contents for 1,051 of them,
nearly all files spanning more than a few 32 KiB frames. With this patch
all 16,716 read correctly, and plain reads with a cold cache were
correct both before and after.

The same helper is used for writes (ni_write_frame(), and
ni_decompress_file() via ni_read_frame()), so on a read-write mount the
data could be written to the wrong place on disk as well.

Map the page that contains lbo. op is already limited to the end of that
page, so a single kmap_local_folio() mapping is enough.

Fixes: f35590ee26f5 ("fs/ntfs3: remove ntfs_bio_pages and use page cache for compressed I/O")
Cc: stable@vger.kernel.org # 6.19+
Signed-off-by: Senjin <senjin@hatchling.org>
---
 fs/ntfs3/fsntfs.c | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/fs/ntfs3/fsntfs.c b/fs/ntfs3/fsntfs.c
index 97c04ab..3340e72 100644
--- a/fs/ntfs3/fsntfs.c
+++ b/fs/ntfs3/fsntfs.c
@@ -1557,12 +1557,17 @@ int ntfs_read_write_run(struct ntfs_sb_info *sbi, const struct runs_tree *run,
 		if (op > bytes)
 			op = bytes;
 
-		kaddr = kmap_local_folio(folio, 0);
+		/*
+		 * The folio may be a large folio that starts before
+		 * lbo (e.g. created by readahead on the block device),
+		 * so map the page that actually contains lbo.
+		 */
+		kaddr = kmap_local_folio(folio, offset_in_folio(folio, lbo));
 		if (wr) {
-			memcpy(kaddr + off, buf, op);
+			memcpy(kaddr, buf, op);
 			folio_mark_dirty(folio);
 		} else {
-			memcpy(buf, kaddr + off, op);
+			memcpy(buf, kaddr, op);
 			flush_dcache_folio(folio);
 		}
 		kunmap_local(kaddr);
-- 
2.56.0

       reply	other threads:[~2026-10-21  4:58 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <20261021045734.573753-1-senjin@hatchling.org>
2026-10-21  4:58 ` Senjin [this message]
2026-10-21  4:58 ` [PATCH 2/2] fs/ntfs3: don't mark pages uptodate when ni_read_frame() fails Senjin

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=6ad8467ee2cb35e73a596bc5@mailersend.net \
    --to=senjin@hatchling.org \
    --cc=almaz.alexandrovich@paragon-software.com \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=ntfs3@lists.linux.dev \
    --cc=stable@vger.kernel.org \
    /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