ntfs: validate the MFT I/O geometry

MFT writes must consider both the device logical block size and the NTFS
record geometry. Validate those values at mount time, then cache the MFT
I/O unit used by all MFT write paths.

Use the larger of the device logical block and MFT record sizes as the MFT
I/O unit. Keep valid geometries that the current write paths cannot safely
handle read-only until their mapping support is generalized.

Fixes: 115380f9a2 ("ntfs: update mft operations")
Cc: stable@vger.kernel.org
Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
This commit is contained in:
Hyunchul Lee 2026-09-21 15:44:04 +09:00 committed by Namjae Jeon
parent 50ed298b6a
commit dfe7ed335b
2 changed files with 82 additions and 2 deletions

View File

@ -286,6 +286,12 @@ static int ntfs_reconfigure(struct fs_context *fc)
static const char *es = ". Cannot remount read-write.";
/* Remounting read-write. */
if (!vol->mft_write_supported) {
ntfs_error(sb,
"MFT writeback is unsupported for this geometry%s",
es);
return -EROFS;
}
if (NVolErrors(vol)) {
ntfs_error(sb, "Volume has errors and is read-only%s",
es);
@ -530,6 +536,8 @@ int ntfs_write_volume_label(struct ntfs_volume *vol, char *label)
static bool is_boot_sector_ntfs(const struct super_block *sb,
const struct ntfs_boot_sector *b, const bool silent)
{
u16 sector_size = le16_to_cpu(b->bpb.bytes_per_sector);
/*
* Check that checksum == sum of u32 values from b to the checksum
* field. If checksum is zero, no checking is done. We will work when
@ -550,8 +558,8 @@ static bool is_boot_sector_ntfs(const struct super_block *sb,
if (b->oem_id != magicNTFS)
goto not_ntfs;
/* Check bytes per sector value is between 256 and 4096. */
if (le16_to_cpu(b->bpb.bytes_per_sector) < 0x100 ||
le16_to_cpu(b->bpb.bytes_per_sector) > 0x1000)
if (sector_size < 0x100 || sector_size > 0x1000 ||
!is_power_of_2(sector_size))
goto not_ntfs;
/*
* Check sectors per cluster value is valid and the cluster size
@ -632,6 +640,60 @@ static char *read_ntfs_boot_sector(struct super_block *sb,
return boot_sector;
}
static bool ntfs_validate_mft_io_geometry(struct ntfs_volume *vol,
const unsigned int logical_block_size)
{
struct super_block *sb = vol->sb;
u32 mft_io_unit_size = 0;
bool mft_write_supported = true;
if (!is_power_of_2(logical_block_size) ||
logical_block_size > PAGE_SIZE ||
!is_power_of_2(vol->sector_size) ||
vol->sector_size < logical_block_size ||
vol->sector_size % logical_block_size ||
!is_power_of_2(vol->cluster_size) ||
!is_power_of_2(vol->mft_record_size) ||
vol->mft_record_size > PAGE_SIZE)
goto err;
mft_io_unit_size = max(logical_block_size, vol->mft_record_size);
if (mft_io_unit_size > PAGE_SIZE ||
mft_io_unit_size % logical_block_size ||
mft_io_unit_size % vol->mft_record_size)
goto err;
/*
* The current direct-write path stores at most two MFT runlist
* segments. Keep valid but larger records read-only until that path
* can map an arbitrary number of segments.
*/
if (vol->mft_record_size > 2 * (u64)vol->cluster_size)
mft_write_supported = false;
/*
* A containing device block is currently mapped through one MFT
* runlist element. Keep valid geometries that require crossing a
* cluster read-only until the mapping is generalized.
*/
if (mft_io_unit_size > vol->mft_record_size &&
(vol->cluster_size < mft_io_unit_size ||
vol->cluster_size % mft_io_unit_size))
mft_write_supported = false;
vol->mft_io_unit_size = mft_io_unit_size;
vol->mft_write_supported = mft_write_supported;
return true;
err:
ntfs_error(sb,
"Unsupported MFT I/O geometry (logical %u, block %lu, sector %u, cluster %u, MFT record %u, I/O unit %u).",
logical_block_size, sb->s_blocksize,
(unsigned int)vol->sector_size, vol->cluster_size,
vol->mft_record_size, mft_io_unit_size);
return false;
}
/*
* parse_ntfs_boot_sector - parse the boot sector and store the data in @vol
* @vol: volume structure to initialise with data from boot sector
@ -644,9 +706,11 @@ static bool parse_ntfs_boot_sector(struct ntfs_volume *vol,
const struct ntfs_boot_sector *b)
{
unsigned int sectors_per_cluster, sectors_per_cluster_bits, nr_hidden_sects;
unsigned int logical_block_size;
int clusters_per_mft_record, clusters_per_index_record;
u64 ll;
logical_block_size = bdev_logical_block_size(vol->sb->s_bdev);
vol->sector_size = le16_to_cpu(b->bpb.bytes_per_sector);
vol->sector_size_bits = ffs(vol->sector_size) - 1;
ntfs_debug("vol->sector_size = %i (0x%x)", vol->sector_size,
@ -719,6 +783,9 @@ static bool parse_ntfs_boot_sector(struct ntfs_volume *vol,
ntfs_warning(vol->sb, "Mft record size (%i) is smaller than the sector size (%i).",
vol->mft_record_size, vol->sector_size);
}
if (!ntfs_validate_mft_io_geometry(vol, logical_block_size))
return false;
clusters_per_index_record = b->clusters_per_index_record;
ntfs_debug("clusters_per_index_record = %i (0x%x)",
clusters_per_index_record, clusters_per_index_record);
@ -2290,6 +2357,11 @@ static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc)
ntfs_error(sb, "Unsupported NTFS filesystem.");
goto err_out_now;
}
if (!vol->mft_write_supported && !sb_rdonly(sb)) {
sb->s_flags |= SB_RDONLY;
ntfs_warning(sb,
"MFT writeback is unsupported for this geometry. Mounting read-only.");
}
if (vol->sector_size > blocksize) {
blocksize = sb_set_blocksize(sb, vol->sector_size);

View File

@ -43,6 +43,7 @@
* @mft_record_size: in bytes
* @mft_record_size_mask: mft_record_size - 1
* @mft_record_size_bits: log2(mft_record_size)
* @mft_write_supported: Whether the MFT write paths support the geometry.
* @index_record_size: in bytes
* @index_record_size_mask: index_record_size - 1
* @index_record_size_bits: log2(index_record_size)
@ -111,6 +112,13 @@ struct ntfs_volume {
u32 mft_record_size;
u32 mft_record_size_mask;
u8 mft_record_size_bits;
bool mft_write_supported;
/*
* Unit size used for MFT I/O. This is the MFT record size when
* it is at least as large as the device logical block, or the
* containing device logical block when the record is smaller.
*/
u32 mft_io_unit_size;
u32 index_record_size;
u32 index_record_size_mask;
u8 index_record_size_bits;