From 229e8188307b9724cc676a49e9600c4acd24b571 Mon Sep 17 00:00:00 2001 From: Zhu Tianhao Date: Tue, 8 Sep 2026 05:41:00 +0900 Subject: [PATCH] ntfs: fix $MFTMirr write offset when it spans multiple folios 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. The bug triggers whenever the four mirror records do not fit in a single folio, i.e. when mft_record_size exceeds PAGE_SIZE / 4 (for example mft_record_size > 1KiB on 4KiB pages). 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). 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 Reviewed-by: Baolin Liu Signed-off-by: Namjae Jeon --- fs/ntfs/mft.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c index 6d5e56378afd..a09496622c7a 100644 --- a/fs/ntfs/mft.c +++ b/fs/ntfs/mft.c @@ -463,7 +463,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; @@ -493,15 +493,11 @@ int ntfs_sync_mft_mirror(struct ntfs_volume *vol, const u64 mft_no, 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);