[PATCH v2 1/3] ntfs: set the attribute list size before reserving space for it
From: Matthias Goergens
Date: Thu Oct 01 2026 - 10:53:57 EST
ntfs_attrlist_update_locked() resizes the attribute list and then, for
$MFT, tries to reserve the maximum list size. When that allocation
fails with -ENOSPC it falls back to ntfs_attrlist_repack(), which reads
the list through ntfs_inode_attr_pread(). The read stops at i_size,
but i_size is only updated after the reserve, so when the list has just
grown the repack gets a short read and returns -EIO. Only -ENOSPC from
the reserve is ignored, so the -EIO fails the $MFT extension and with
it the file creation that needed a new mft record.
On a 32 MiB volume with 512-byte clusters whose $MFT has a non-resident
attribute list, 591 of 1500 creations of empty files succeed and the
rest fail with EIO while 742 KiB is still free:
ntfs: (device vda): ntfs_attrlist_update_locked(): Failed to reserve attribute list space
ntfs: Failed add attr entry to attrlist
ntfs: MP update failed
ntfs: (device vda): ntfs_mft_record_alloc(): Failed to extend mft data allocation.
Update i_size as soon as the list has been resized. File creation then
goes on until the volume is full and fails with ENOSPC.
Fixes: b1d732e62a5b ("ntfs: repack $MFT/$ATTRIBUTE LIST")
Signed-off-by: Matthias Goergens <matthias.goergens@xxxxxxxxx>
Reviewed-by: Baolin Liu <liubaolin@xxxxxxxxxx>
---
The volume is frag.img from
https://github.com/matthiasgoergens/linux/tree/reproducer/2026-09-26-ntfs-mft-runlist
(mft-bootstrap/frag.img.xz); creating empty files on it until one fails
is enough. Tested with KASAN and lockdep: after the patch the files
written before the volume filled read back intact after a remount, and
ntfsfix and ntfsresize --info find nothing wrong with the image.
---
fs/ntfs/attrlist.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/fs/ntfs/attrlist.c b/fs/ntfs/attrlist.c
index 3660e7fd24b1..6edd5d2d9bf2 100644
--- a/fs/ntfs/attrlist.c
+++ b/fs/ntfs/attrlist.c
@@ -256,6 +256,9 @@ int ntfs_attrlist_update_locked(struct ntfs_inode *base_ni,
return err;
}
+ /* ntfs_attrlist_repack() below reads the list up to i_size. */
+ i_size_write(attr_vi, base_ni->attr_list_size);
+
/*
* Reserve the maximum legal list size while the MFT metadata area is
* still easy to allocate contiguously. This prevents a later list entry
@@ -283,8 +286,6 @@ int ntfs_attrlist_update_locked(struct ntfs_inode *base_ni,
}
}
- i_size_write(attr_vi, base_ni->attr_list_size);
-
if (NInoNonResident(attr_ni) && !NInoAttrListNonResident(base_ni))
NInoSetAttrListNonResident(base_ni);
--
2.56.0