From: Zhan Xusheng <zhanxusheng1024@gmail.com>
To: linkinjeon@kernel.org, hyc.lee@gmail.com
Cc: ntfs@lists.linux.dev, linux-kernel@vger.kernel.org,
zhanxusheng@xiaomi.com, stable@vger.kernel.org
Subject: [PATCH] ntfs: trim the sizes after a short write
Date: Wed, 11 Nov 2026 17:34:59 +0800 [thread overview]
Message-ID: <20261111093459.660484-1-zhanxusheng1024@gmail.com> (raw)
From: Zhan Xusheng <zhanxusheng@xiaomi.com>
ntfs_file_write_iter() moves the data size and, for a non-resident
uncompressed file, the initialized size to pos + count before the copy.
__ntfs_extend_initialized_size() only zeroes [old initialized size, pos),
so the range [pos, pos + count) is initialized only by the copy itself.
When iomap_file_buffered_write() copies less than count, it returns a
positive short count and the error rollback at out: does not run. The
file keeps the size pos + count, and [pos + ret, pos + count) reads back
whatever the freed clusters held before, also after a remount. A local
user can read the old contents of freed clusters this way.
Run the existing rollback for a short write as well, and roll the two
sizes back to the end of what was written instead of to the old sizes.
Fixes: b041ca562526 ("ntfs: update iomap and address space operations")
Cc: stable@vger.kernel.org
Signed-off-by: Zhan Xusheng <zhanxusheng@xiaomi.com>
---
A reproducer is available on request.
Tested in a VM on mainline b3dfbde62cf1, with the free space filled
with a known pattern first. Short append of 4M that stops after
1056768 bytes:
write() size grows by old data readable
before 1056768 4194304 3137536 bytes
after 1056768 1056768 0
A short write 1M past EOF gives the same results. Full writes and
compressed files are unchanged.
---
fs/ntfs/file.c | 25 ++++++++++++++++++-------
1 file changed, 18 insertions(+), 7 deletions(-)
diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c
index 3213a0c9f96b..98bb3d5d420e 100644
--- a/fs/ntfs/file.c
+++ b/fs/ntfs/file.c
@@ -928,10 +928,21 @@ ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
ret = iomap_file_buffered_write(iocb, from, &ntfs_write_iomap_ops,
&ntfs_iomap_folio_ops, NULL);
out:
- if (ret < 0 && ret != -EIOCBQUEUED) {
+ /*
+ * Both sizes were moved to end before the copy, so a short write,
+ * like a failed one, must take back what it did not write.
+ */
+ if (ret < count && ret != -EIOCBQUEUED) {
+ loff_t new_init_size = old_init_size;
+ loff_t new_data_size = old_data_size;
int init_err = 0;
int data_err = 0;
+ if (ret > 0) {
+ new_init_size = max(old_init_size, pos + ret);
+ new_data_size = max(old_data_size, pos + ret);
+ }
+
/*
* Like ntfs_setattr_size(): __ntfs_attr_truncate_vfs()
* expects the invalidate lock held across the size
@@ -940,13 +951,13 @@ ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
filemap_invalidate_lock(vi->i_mapping);
/* ntfs_attr_set_initialized_size() rejects a resident one. */
- if (ni->initialized_size != old_init_size &&
+ if (ni->initialized_size != new_init_size &&
NInoNonResident(ni)) {
struct ntfs_inode *mrec_ni = ntfs_base_inode(ni);
mutex_lock(&mrec_ni->mrec_lock);
init_err = ntfs_attr_set_initialized_size(ni,
- old_init_size);
+ new_init_size);
mutex_unlock(&mrec_ni->mrec_lock);
}
/*
@@ -955,13 +966,13 @@ ssize_t ntfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
* the uninitialized tail, so skip it unless the rollback
* above restored that size.
*/
- if (ni->data_size != old_data_size && !init_err) {
+ if (ni->data_size != new_data_size && !init_err) {
struct ntfs_inode *mrec_ni = ntfs_base_inode(ni);
- truncate_setsize(vi, old_data_size);
+ truncate_setsize(vi, new_data_size);
mutex_lock(&mrec_ni->mrec_lock);
- data_err = __ntfs_attr_truncate_vfs(ni, old_data_size,
- old_data_size);
+ data_err = __ntfs_attr_truncate_vfs(ni, new_data_size,
+ new_data_size);
mutex_unlock(&mrec_ni->mrec_lock);
/* Like ntfs_setattr_size(): stay on the durable size. */
if (data_err)
base-commit: b025f526c5d04929212c9d5fb7ffc96608f3eacc
--
2.43.0
reply other threads:[~2026-11-11 9:35 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261111093459.660484-1-zhanxusheng1024@gmail.com \
--to=zhanxusheng1024@gmail.com \
--cc=hyc.lee@gmail.com \
--cc=linkinjeon@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=ntfs@lists.linux.dev \
--cc=stable@vger.kernel.org \
--cc=zhanxusheng@xiaomi.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®