[PATCH 3/3] ntfs: open a named $DATA stream by name when making room in a record

From: Wahid Khan

Date: Fri Nov 06 2026 - 04:15:15 EST


When an attribute has to grow and the mft record is full,
ntfs_resident_attr_resize() walks the other resident attributes and
converts them to non-resident ones. For $DATA it always opens the
unnamed data inode of the base inode, even if the attribute found is a
named stream:

tvi = ntfs_iget(sb, base_ni->mft_no);
ntfs_attr_make_non_resident(NTFS_I(tvi), value_len);

value_len is the length of the stream, so it does not match the
attribute that ntfs_attr_make_non_resident() looks up for the unnamed
inode, and the WARN_ON() on entry fires:

WARNING: fs/ntfs/attrib.c:2115 at ntfs_attr_make_non_resident
ntfs_resident_attr_resize
ntfs_attr_expand_locked
ntfs_expand_for_write
ntfs_file_write_iter
vfs_write

With debug output the values were attr_size 13 (unnamed $DATA) and
data_size 550 (named stream) for the same mft record.

It does not need a damaged volume. A file with an alternate data stream
whose record has no room left is enough. The fuzzer found it through a
single changed byte in a file name of an image from mkntfs, which made
a write grow a resident attribute in such a record.

Use the unnamed inode only for the unnamed $DATA attribute and open any
other attribute, including a named $DATA, by type and name.

Fixes: 495e90fa3348 ("ntfs: update attrib operations")
Cc: stable@xxxxxxxxxxxxxxx # v7.1+
Signed-off-by: Wahid Khan <wahidzk0091@xxxxxxxxx>
---
fs/ntfs/attrib.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c
index fce7144d7..0ff671e9d 100644
--- a/fs/ntfs/attrib.c
+++ b/fs/ntfs/attrib.c
@@ -4930,7 +4930,7 @@ static int ntfs_resident_attr_resize(struct ntfs_inode *attr_ni, const s64 newsi
if (a->type == AT_DATA && !value_len)
continue;

- if (a->type == AT_DATA)
+ if (a->type == AT_DATA && !a->name_length)
tvi = ntfs_iget(sb, base_ni->mft_no);
else
tvi = ntfs_attr_iget(VFS_I(base_ni), a->type,
--
2.53.0