[PATCH] ntfs: fix $MFTMirr write offset for MFT record sizes larger than PAGE_SIZE

From: 朱 天浩

Date: Sat Sep 05 2026 - 08:39:31 EST


When commit 115380f9a2f9 ("ntfs: update mft operations") refactored the
MFT code to use folios, it assumed $MFTMirr is allocated contiguously,
which is indeed what mkfs arranges. However, the folio rewrite kept a
leftover from the old buffer-head/runlist based implementation:
vol->cluster_size_mask is still applied to the mirror write offset.
In fact it is no longer needed -- and in some configurations it is
actively wrong.

For example, on the built-in 4Kn SSD (Apple SSD AP0256J) of a
MacBookPro14,1 with 4096-byte MFT records, mirror record 3 is still
written to the location of record 0. $MFTMirr therefore gets out of
sync with $MFT and the following warning is printed on remount:

ntfs: (device nvme0n1p3): check_mft_mirror(): $MFT and $MFTMirr
record 0 do not match. Run chkdsk.

Fix it by always adding the folio offset (folio->index << PAGE_SHIFT)
to the base LCN address instead of applying the cluster_size_mask
truncation. This is the standard file-offset calculation and is
correct for every combination of cluster size, page size and MFT
record size, provided $MFTMirr data is contiguous from mftmirr_lcn
(which mkfs always arranges).

While here, verify that the folio returned by read_mapping_folio() is
large enough to hold the whole mirror record (folio_ofs +
mft_record_size <= folio_size(folio)), guarding against records that
straddle a folio boundary when mft_record_size exceeds PAGE_SIZE,
although this cannot happen in practice.

Tested on the volume above: before the change mirror records 1-3 are
all written to record 0's sector; after the change the write-back
probe reports:

ntfs: NTFSDBG pre mft_no=0x3 folio_idx=0x3 ofs=0x0 mftmirr_lcn=0x2540c5 clu_bits=12 rec_bits=12 PAGE_SHIFT=12 sect=0x12a0640
ntfs: NTFSDBG pre mft_no=0x0 folio_idx=0x0 ofs=0x0 mftmirr_lcn=0x2540c5 clu_bits=12 rec_bits=12 PAGE_SHIFT=12 sect=0x12a0628

which matches the expected sectors computed as:

(NTFS_CLU_TO_B(mftmirr_lcn) + (folio->index << PAGE_SHIFT)) >> SECTOR_SHIFT

record 0: (0x2540c5 << 12) + 0x0000 = 0x2540c5000 >> 9 = 0x12a0628
record 3: (0x2540c5 << 12) + 0x3000 = 0x2540c8000 >> 9 = 0x12a0640

A 13 MB file write succeeds on this volume and the file is byte-for-byte
identical after a umount/mount cycle.

Fixes: 115380f9a2f9 ("ntfs: update mft operations")
Assisted-by: UOS-AI
Assisted-by: CodeBuddy:Hy4 preview
Signed-off-by: Zhu Tianhao <zhutianhao75@xxxxxxxxxxx>
---
fs/ntfs/mft.c | 26 ++++++++++++++++----------
1 file changed, 16 insertions(+), 10 deletions(-)

diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c
index 7e58c99f1728..a62b1d41697c 100644
--- a/fs/ntfs/mft.c
+++ b/fs/ntfs/mft.c
@@ -462,7 +462,7 @@ int ntfs_sync_mft_mirror(struct ntfs_volume *vol, const u64 mft_no,
{
u8 *kmirr;
struct folio *folio;
- unsigned int folio_ofs, lcn_folio_off = 0;
+ unsigned int folio_ofs;
int err = 0;
struct bio *bio;

@@ -482,25 +482,31 @@ int ntfs_sync_mft_mirror(struct ntfs_volume *vol, const u64 mft_no,
goto err_out;
}

+ /* Offset of the mft mirror record inside the folio. */
+ folio_ofs = NTFS_MFT_NR_TO_POFS(vol, mft_no);
+
+ /* The mirror record must fit entirely within this folio. */
+ if (folio_ofs + vol->mft_record_size > folio_size(folio)) {
+ ntfs_error(vol->sb, "Mft mirror record 0x%llx does not fit in folio.",
+ mft_no);
+ folio_put(folio);
+ err = -EIO;
+ goto err_out;
+ }
+
folio_lock(folio);
folio_clear_uptodate(folio);
- /* Offset of the mft mirror record inside the page. */
- folio_ofs = NTFS_MFT_NR_TO_POFS(vol, mft_no);
- /* The address in the page of the mirror copy of the mft record @m. */
+ /* The address in the folio of the mirror copy of the mft record @m. */
kmirr = kmap_local_folio(folio, 0) + folio_ofs;
/* Copy the mst protected mft record to the mirror. */
memcpy(kmirr, m, vol->mft_record_size);
kunmap_local(kmirr);

- if (vol->cluster_size_bits > PAGE_SHIFT) {
- lcn_folio_off = folio->index << PAGE_SHIFT;
- lcn_folio_off &= vol->cluster_size_mask;
- }
-
bio = bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE, GFP_NOIO);
bio->bi_iter.bi_sector =
ntfs_bytes_to_bio_sector(NTFS_CLU_TO_B(vol, vol->mftmirr_lcn) +
- lcn_folio_off + folio_ofs);
+ ((u64)folio->index << PAGE_SHIFT) +
+ folio_ofs);

if (bio_add_folio(bio, folio, vol->mft_record_size, folio_ofs))
err = submit_bio_wait(bio);
--
2.51.0