[PATCH] fs/ntfs3: zero clusters allocated below the valid size

From: Hengyu Liang

Date: Sun Oct 11 2026 - 23:13:00 EST


Commit 099ef9ab9203 ("fs/ntfs3: implement iomap-based file operations")
made ntfs_iomap_begin() allocate the clusters for a write into a hole of
a sparse file without zeroing them.

However, everything below the valid size is read from disk as it is, and
a write does not always fill the clusters it allocates. It can be smaller
than a cluster, or stop early when a part of the user buffer is not
readable. As of now, the rest of these clusters shows old contents of the
disk when the file is read.

For example, a volume with 64K clusters is mounted with -o sparse, a file
has a hole followed by data, and 4096 bytes are written at the start of
the hole.

Before commit 099ef9ab9203 ("fs/ntfs3: implement iomap-based file
operations"), the other 61440 bytes of the cluster read back as zeros.

After that commit, they hold the contents of a file that was deleted
earlier.

This patch will zero the clusters that are allocated below the valid
size, as it is already done when delayed allocations are converted.

Fixes: 099ef9ab9203 ("fs/ntfs3: implement iomap-based file operations")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Hengyu Liang <hengyul@xxxxxxxxxx>
---
A test program is available on request. Tested on ntfs3 master and on top
of the 11 patches posted on 2026-10-09.

fs/ntfs3/attrib.c | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/fs/ntfs3/attrib.c b/fs/ntfs3/attrib.c
index 3b4ef15162da..49ed2b06e7b8 100644
--- a/fs/ntfs3/attrib.c
+++ b/fs/ntfs3/attrib.c
@@ -1015,6 +1015,7 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen,
CLST hint, svcn, to_alloc, evcn1, next_svcn, asize, end, vcn0;
CLST alloc, evcn;
unsigned fr;
+ enum ALLOCATE_OPT opt;
u64 total_size, total_size0;
int step;

@@ -1245,10 +1246,14 @@ int attr_data_get_block_locked(struct ntfs_inode *ni, CLST vcn, CLST clen,
}

/* Allocate and zeroout new clusters. */
+ opt = zero ? ALLOCATE_ZERO : ALLOCATE_ONE_FR;
+ if (vcn < bytes_to_cluster(sbi, ni->i_valid)) {
+ /* What is below 'valid' is read from disk as it is. */
+ opt |= ALLOCATE_ZERO;
+ }
+
err = attr_allocate_clusters(sbi, run, run_da, vcn, hint + 1, to_alloc,
- NULL,
- zero ? ALLOCATE_ZERO : ALLOCATE_ONE_FR,
- len, fr, lcn, len);
+ NULL, opt, len, fr, lcn, len);
if (err)
goto out;
*new = true;
--
2.53.0