[PATCH v2 3/3] ntfs: bound the attribute-list entry in ntfs_read_inode_mount()

From: Bryam Vargas

Date: Wed Jun 03 2026 - 14:01:27 EST


The $MFT attribute-list walk in ntfs_read_inode_mount() validates each
entry only with "(u8 *)al_entry + 6 > al_end" and
"(u8 *)al_entry + le16_to_cpu(al_entry->length) > al_end", but then reads
al_entry->lowest_vcn (an __le64 at offset 8) and al_entry->mft_reference
(offset 16) -- fields beyond the 6 bytes proven in range. al_entry->length
is attacker-controlled and only required non-zero, so a short entry (e.g.
length 8) placed at the tail passes both checks while the lowest_vcn /
mft_reference reads fall past al_end.

al_end is ni->attr_list + attr_list_size (the on-disk size); the buffer is
kvzalloc(round_up(attr_list_size, SECTOR_SIZE)), so the sector rounding
usually absorbs the over-read -- but when attr_list_size is a multiple of
SECTOR_SIZE there is no slack and a crafted $MFT attribute list produces an
out-of-bounds read at mount time.

Require the fixed attribute-list-entry header to be in range before
dereferencing it, matching the bound ntfs_external_attr_find() already
applies to the entries in its own attribute-list walk.

Signed-off-by: Bryam Vargas <hexlabsecurity@xxxxxxxxx>
---
v2: dropped the redundant Reported-by; reproducer omitted on the public list
(available to the maintainers on request). Posting per security@xxxxxxxxxx
guidance that crafted-image filesystem bugs are out of the security threat
model.

Sibling of the ntfs_external_attr_find() look-ahead OOB (patch 2/3): the same
struct attr_list_entry fixed fields (lowest_vcn at 8, mft_reference at 16) are
read here with a weaker bound. The expression is carried from the legacy
fs/ntfs driver (removed v6.9; present since v2.6.12), where ntfs_malloc_nofs()
rounded every allocation up to kmalloc(PAGE_SIZE) and masked it. Please add the
appropriate Fixes: tag for the commit that introduced ntfs_read_inode_mount()
in the new driver.

fs/ntfs/inode.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c
index 360bebd1ee3f..70fb216a1bd0 100644
--- a/fs/ntfs/inode.c
+++ b/fs/ntfs/inode.c
@@ -1996,7 +1996,8 @@ int ntfs_read_inode_mount(struct inode *vi)
goto em_put_err_out;
if (!al_entry->length)
goto em_put_err_out;
- if ((u8 *)al_entry + 6 > al_end ||
+ if ((u8 *)al_entry + offsetof(struct attr_list_entry, name) >
+ al_end ||
(u8 *)al_entry + le16_to_cpu(al_entry->length) > al_end)
goto em_put_err_out;
next_al_entry = (struct attr_list_entry *)((u8 *)al_entry +
--
2.43.0