* [PATCH 0/3] ntfs: three fixes for crafted images, found by fuzzing
@ 2026-11-06 9:14 Wahid Khan
2026-11-06 9:14 ` [PATCH 1/3] ntfs: do not read an index block outside $INDEX_ALLOCATION Wahid Khan
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: Wahid Khan @ 2026-11-06 9:14 UTC (permalink / raw)
To: Namjae Jeon, Hyunchul Lee; +Cc: ntfs, linux-fsdevel, linux-kernel, Wahid Khan
I fuzz the new fs/ntfs driver with mutated images: an image from mkntfs
populated with ntfs-3g (directories, streams, sparse files, long names), a
few bytes changed, then mount, walk the tree and write, under KASAN.
Three problems from that are still present in ntfs-next (e62b384ca).
Each patch has its own trace and the reproduction steps.
1. ntfs: do not read an index block outside $INDEX_ALLOCATION
A directory without a usable $INDEX_ALLOCATION hangs on its first
lookup. The task waits for an mft record lock that it already holds.
It needs one changed byte in a crafted image.
2. ntfs: open the index allocation before reading or writing an index block
NULL pointer dereference in ntfs_ib_write() on link() when the lookup
ended in the index root. This is not the path fixed by 9365c4a1357e.
It needs one changed byte in a crafted image.
3. ntfs: open a named $DATA stream by name when making room in a record
WARN_ON() in ntfs_attr_make_non_resident(). This one does not need a
damaged volume: a file with an alternate data stream and a full mft
record is enough.
All three need an image to be mounted, so a crafted image, not a remote
attacker. I can send the images (about 380 KB each, compressed) if they
are useful.
Wahid Khan (3):
ntfs: do not read an index block outside $INDEX_ALLOCATION
ntfs: open the index allocation before reading or writing an index
block
ntfs: open a named $DATA stream by name when making room in a record
fs/ntfs/attrib.c | 2 +-
fs/ntfs/dir.c | 14 ++++++++++++++
fs/ntfs/index.c | 17 +++++++++++++++++
3 files changed, 32 insertions(+), 1 deletion(-)
--
2.53.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 1/3] ntfs: do not read an index block outside $INDEX_ALLOCATION
2026-11-06 9:14 [PATCH 0/3] ntfs: three fixes for crafted images, found by fuzzing Wahid Khan
@ 2026-11-06 9:14 ` Wahid Khan
2026-11-06 9:14 ` [PATCH 2/3] ntfs: open the index allocation before reading or writing an index block Wahid Khan
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Wahid Khan @ 2026-11-06 9:14 UTC (permalink / raw)
To: Namjae Jeon, Hyunchul Lee
Cc: ntfs, linux-fsdevel, linux-kernel, Wahid Khan, stable
A directory whose $INDEX_ROOT has a child node but whose
$INDEX_ALLOCATION is missing or damaged gets an empty index inode from
ntfs_read_locked_index_inode(): size 0 and not non-resident.
ntfs_lookup_inode_by_name() still follows the child node and reads a
folio from that inode while it holds the mft record lock of the
directory.
Since the resident iomap read started to take base_ni->mrec_lock, that
read waits for a lock the caller already holds:
INFO: task init:1 is blocked on a mutex likely owned by task init:1.
__mutex_lock
__ntfs_read_iomap_begin
ntfs_read_iomap_next
iomap_read_folio
ntfs_read_folio
ntfs_lookup_inode_by_name
ntfs_lookup
The task never returns, and neither does anything else that needs the
directory's mft record.
It takes one corrupted byte on an image from mkntfs: change the first
byte of the type of the $INDEX_ALLOCATION attribute header of a
directory with index blocks from 0xa0 to 0x00. The first lookup in that
directory hangs.
Check that the index inode is non-resident and that the vcn lies inside
it before every index block read, and fail with -EIO otherwise. This
also rejects a negative vcn, which was only checked on later descents.
Fixes: d9e00c457d4a ("ntfs: serialize resident iomap reads with mrec_lock")
Cc: stable@vger.kernel.org # v7.3+
Signed-off-by: Wahid Khan <wahidzk0091@gmail.com>
---
fs/ntfs/dir.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/fs/ntfs/dir.c b/fs/ntfs/dir.c
index fce10baa2..9fb5bb3d3 100644
--- a/fs/ntfs/dir.c
+++ b/fs/ntfs/dir.c
@@ -308,6 +308,20 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname,
ia_mapping = ia_vi->i_mapping;
descend_into_child_node:
+ /*
+ * The child node has to be inside $INDEX_ALLOCATION. A directory that
+ * lacks the attribute gets an empty index inode that is not
+ * non-resident, and reading a folio from it locks the mft record that
+ * is already locked here.
+ */
+ if (!NInoNonResident(NTFS_I(ia_vi)) || vcn < 0 ||
+ (u64)vcn >= (u64)i_size_read(ia_vi) >> dir_ni->itype.index.vcn_size_bits) {
+ ntfs_error(sb,
+ "Child node vcn %lld is outside $INDEX_ALLOCATION in directory inode 0x%llx.",
+ vcn, dir_ni->mft_no);
+ err = -EIO;
+ goto err_out;
+ }
/*
* Convert vcn to index into the index allocation attribute in units
* of PAGE_SIZE and map the page cache page, reading it from
--
2.53.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 2/3] ntfs: open the index allocation before reading or writing an index block
2026-11-06 9:14 [PATCH 0/3] ntfs: three fixes for crafted images, found by fuzzing Wahid Khan
2026-11-06 9:14 ` [PATCH 1/3] ntfs: do not read an index block outside $INDEX_ALLOCATION Wahid Khan
@ 2026-11-06 9:14 ` Wahid Khan
2026-11-06 9:14 ` [PATCH 3/3] ntfs: open a named $DATA stream by name when making room in a record Wahid Khan
2026-11-06 12:27 ` [PATCH 0/3] ntfs: three fixes for crafted images, found by fuzzing Namjae Jeon
3 siblings, 0 replies; 5+ messages in thread
From: Wahid Khan @ 2026-11-06 9:14 UTC (permalink / raw)
To: Namjae Jeon, Hyunchul Lee
Cc: ntfs, linux-fsdevel, linux-kernel, Wahid Khan, stable
ntfs_ib_write() and ntfs_ib_read() use icx->ia_ni without checking it.
ntfs_ir_reparent() only opens the index allocation through
ntfs_ia_add() when the root is a small index. For a root that is already
marked as a large index it calls ntfs_ib_write() directly.
The lookup does not open the index allocation either when it ends in the
root. If the last entry of the root has no child node, ia_ni is still
NULL when the root has to be moved into an index block, and the write
dereferences it:
Oops: general protection fault, KASAN: null-ptr-deref in range
[0x0000000000000128-0x000000000000012f]
RIP: 0010:ntfs_inode_attr_pwrite+0x52/0x1600
ntfs_ir_make_space
ntfs_ie_add
ntfs_index_add_filename
__ntfs_link
ntfs_link
vfs_link
A single byte of an image from mkntfs is enough. In a directory that has
one empty index block, clear INDEX_ENTRY_NODE (flags 3 -> 2) of the last
entry of the $INDEX_ROOT and link() a file into the directory.
commit 9365c4a1357e ("ntfs: fix NULL dereference on index allocation
open failure") covers the case where ntfs_ia_open() fails in the lookup.
Here it was never tried. Open the index allocation on demand in both
helpers and fail with -EIO if that does not work.
Fixes: 0a8ac0c1fa0b ("ntfs: update directory operations")
Cc: stable@vger.kernel.org # v7.1+
Signed-off-by: Wahid Khan <wahidzk0091@gmail.com>
---
fs/ntfs/index.c | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/fs/ntfs/index.c b/fs/ntfs/index.c
index 66411a712..f808c887f 100644
--- a/fs/ntfs/index.c
+++ b/fs/ntfs/index.c
@@ -97,12 +97,25 @@ static s64 ntfs_ib_pos_to_vcn(struct ntfs_index_context *icx, s64 pos)
return pos >> icx->vcn_size_bits;
}
+/* Open the index allocation inode if no earlier step of the operation did. */
+static int ntfs_icx_ia_ensure(struct ntfs_index_context *icx)
+{
+ if (icx->ia_ni)
+ return 0;
+ icx->ia_ni = ntfs_ia_open(icx, icx->idx_ni);
+ return icx->ia_ni ? 0 : -EIO;
+}
+
static int ntfs_ib_write(struct ntfs_index_context *icx, struct index_block *ib)
{
s64 ret, vcn = le64_to_cpu(ib->index_block_vcn);
ntfs_debug("vcn: %lld\n", vcn);
+ ret = ntfs_icx_ia_ensure(icx);
+ if (ret)
+ return ret;
+
ret = pre_write_mst_fixup((struct ntfs_record *)ib, icx->block_size);
if (ret)
return -EIO;
@@ -768,6 +781,10 @@ static int ntfs_ib_read(struct ntfs_index_context *icx, s64 vcn, struct index_bl
ntfs_debug("vcn: %lld\n", vcn);
+ ret = ntfs_icx_ia_ensure(icx);
+ if (ret)
+ return ret;
+
pos = ntfs_ib_vcn_to_pos(icx, vcn);
ret = ntfs_inode_attr_pread(VFS_I(icx->ia_ni), pos, icx->block_size, (u8 *)dst);
--
2.53.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 3/3] ntfs: open a named $DATA stream by name when making room in a record
2026-11-06 9:14 [PATCH 0/3] ntfs: three fixes for crafted images, found by fuzzing Wahid Khan
2026-11-06 9:14 ` [PATCH 1/3] ntfs: do not read an index block outside $INDEX_ALLOCATION Wahid Khan
2026-11-06 9:14 ` [PATCH 2/3] ntfs: open the index allocation before reading or writing an index block Wahid Khan
@ 2026-11-06 9:14 ` Wahid Khan
2026-11-06 12:27 ` [PATCH 0/3] ntfs: three fixes for crafted images, found by fuzzing Namjae Jeon
3 siblings, 0 replies; 5+ messages in thread
From: Wahid Khan @ 2026-11-06 9:14 UTC (permalink / raw)
To: Namjae Jeon, Hyunchul Lee
Cc: ntfs, linux-fsdevel, linux-kernel, Wahid Khan, stable
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@vger.kernel.org # v7.1+
Signed-off-by: Wahid Khan <wahidzk0091@gmail.com>
---
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
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH 0/3] ntfs: three fixes for crafted images, found by fuzzing
2026-11-06 9:14 [PATCH 0/3] ntfs: three fixes for crafted images, found by fuzzing Wahid Khan
` (2 preceding siblings ...)
2026-11-06 9:14 ` [PATCH 3/3] ntfs: open a named $DATA stream by name when making room in a record Wahid Khan
@ 2026-11-06 12:27 ` Namjae Jeon
3 siblings, 0 replies; 5+ messages in thread
From: Namjae Jeon @ 2026-11-06 12:27 UTC (permalink / raw)
To: Wahid Khan; +Cc: Hyunchul Lee, ntfs, linux-fsdevel, linux-kernel
On Fri, Nov 6, 2026 at 6:14 PM Wahid Khan <wahidzk0091@gmail.com> wrote:
>
> I fuzz the new fs/ntfs driver with mutated images: an image from mkntfs
> populated with ntfs-3g (directories, streams, sparse files, long names), a
> few bytes changed, then mount, walk the tree and write, under KASAN.
> Three problems from that are still present in ntfs-next (e62b384ca).
> Each patch has its own trace and the reproduction steps.
>
> 1. ntfs: do not read an index block outside $INDEX_ALLOCATION
> A directory without a usable $INDEX_ALLOCATION hangs on its first
> lookup. The task waits for an mft record lock that it already holds.
> It needs one changed byte in a crafted image.
>
> 2. ntfs: open the index allocation before reading or writing an index block
> NULL pointer dereference in ntfs_ib_write() on link() when the lookup
> ended in the index root. This is not the path fixed by 9365c4a1357e.
> It needs one changed byte in a crafted image.
>
> 3. ntfs: open a named $DATA stream by name when making room in a record
> WARN_ON() in ntfs_attr_make_non_resident(). This one does not need a
> damaged volume: a file with an alternate data stream and a full mft
> record is enough.
>
> All three need an image to be mounted, so a crafted image, not a remote
> attacker. I can send the images (about 380 KB each, compressed) if they
> are useful.
>
>
> Wahid Khan (3):
> ntfs: do not read an index block outside $INDEX_ALLOCATION
> ntfs: open the index allocation before reading or writing an index
> block
> ntfs: open a named $DATA stream by name when making room in a record
Please check Sashiko's review comments:
https://sashiko.dev/#/patchset/20261106091420.2461683-1-wahidzk0091%40gmail.com
Thanks.
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-11-06 12:27 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-06 9:14 [PATCH 0/3] ntfs: three fixes for crafted images, found by fuzzing Wahid Khan
2026-11-06 9:14 ` [PATCH 1/3] ntfs: do not read an index block outside $INDEX_ALLOCATION Wahid Khan
2026-11-06 9:14 ` [PATCH 2/3] ntfs: open the index allocation before reading or writing an index block Wahid Khan
2026-11-06 9:14 ` [PATCH 3/3] ntfs: open a named $DATA stream by name when making room in a record Wahid Khan
2026-11-06 12:27 ` [PATCH 0/3] ntfs: three fixes for crafted images, found by fuzzing Namjae Jeon
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®