From ca43660672050337f4b845e36f732e3af1e308ca Mon Sep 17 00:00:00 2001 From: Hyunchul Lee Date: Tue, 22 Sep 2026 15:18:33 +0900 Subject: [PATCH] ntfs: validate attribute-list sizes before allocation The NULL point dereference could occur when an on-disk $ATTRIBUTE_LIST size was truncated before it was validated and allocated. To fix this issue and prevent excessive memory allocation, limit the $ATTRIBUTE_LIST size to 256 KiB. The following existing implementation and Windows test provide evidence for this 256 KiB compatibility limit: NTFS already treats 256 KiB as the $ATTRIBUTE_LIST limit in its attribute-size checks and $MFT attribute list repack/expand paths. For additional Windows testing, when $MFT's attribute-list data_size was above 256 KiB, Windows attached the disk partition but did not mount a NTFS volume and target access failed. Fixes: af0db57d4293 ("ntfs: update inode operations") Link: https://lore.kernel.org/ntfs/88468.1790006025@30-10-12.wireless.csail.mit.edu/T/#u Cc: stable@vger.kernel.org Cc: Robert Morris Signed-off-by: Hyunchul Lee Reviewed-by: Baolin Liu Signed-off-by: Namjae Jeon --- fs/ntfs/attrib.c | 2 +- fs/ntfs/attrlist.c | 2 -- fs/ntfs/inode.c | 21 +++++++++++++++------ fs/ntfs/ntfs.h | 2 ++ 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index d8dd40526c3b..333b3371acb4 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -1775,7 +1775,7 @@ int ntfs_attr_size_bounds_check(const struct ntfs_volume *vol, const __le32 type * $ATTRIBUTE_LIST has a maximum size of 256kiB, but this is not * listed in $AttrDef. */ - if (unlikely(type == AT_ATTRIBUTE_LIST && size > 256 * 1024)) + if (unlikely(type == AT_ATTRIBUTE_LIST && size > NTFS_MAX_ATTR_LIST_SIZE)) return -ERANGE; /* Get the $AttrDef entry for the attribute @type. */ ad = ntfs_attr_find_in_attrdef(vol, type); diff --git a/fs/ntfs/attrlist.c b/fs/ntfs/attrlist.c index 7c09d02fd941..1bbd2bc62c58 100644 --- a/fs/ntfs/attrlist.c +++ b/fs/ntfs/attrlist.c @@ -14,8 +14,6 @@ #include "attrlist.h" #include "lcnalloc.h" -#define NTFS_MAX_ATTR_LIST_SIZE (256 * 1024) - /* * ntfs_attrlist_need - check whether inode need attribute list * @ni: opened ntfs inode for which perform check diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index d8d3a7b7b8a9..9583b2c6c7a2 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -805,6 +805,8 @@ static int ntfs_read_locked_inode(struct inode *vi) goto unm_err_out; } } else { + s64 attr_list_size; + if (vi->i_ino == FILE_MFT) goto skip_attr_list_load; ntfs_debug("Attribute list found in inode 0x%llx.", ni->mft_no); @@ -827,11 +829,14 @@ static int ntfs_read_locked_inode(struct inode *vi) ni->mft_no); } /* Now allocate memory for the attribute list. */ - ni->attr_list_size = (u32)ntfs_attr_size(a); - if (!ni->attr_list_size) { - ntfs_error(vi->i_sb, "Attr_list_size is zero"); + attr_list_size = ntfs_attr_size(a); + if (attr_list_size <= 0 || + attr_list_size > NTFS_MAX_ATTR_LIST_SIZE) { + ntfs_error(vi->i_sb, "Invalid attribute list size %lld (mft_no 0x%llx).", + (long long)attr_list_size, ni->mft_no); goto unm_err_out; } + ni->attr_list_size = (u32)attr_list_size; ni->attr_list = kvzalloc(ni->attr_list_size, GFP_NOFS); if (!ni->attr_list) { ntfs_error(vi->i_sb, @@ -1955,6 +1960,7 @@ int ntfs_read_inode_mount(struct inode *vi) } else /* if (!err) */ { struct attr_list_entry *al_entry, *next_al_entry; u8 *al_end; + s64 attr_list_size; static const char *es = " Not allowed. $MFT is corrupt. You should run chkdsk."; ntfs_debug("Attribute list attribute found in $MFT."); @@ -1978,11 +1984,14 @@ int ntfs_read_inode_mount(struct inode *vi) "Resident attribute list attribute in $MFT system file is marked encrypted/sparse which is not true. However, Windows allows this and chkdsk does not detect or correct it so we will just ignore the invalid flags and pretend they are not set."); } /* Now allocate memory for the attribute list. */ - ni->attr_list_size = (u32)ntfs_attr_size(a); - if (!ni->attr_list_size) { - ntfs_error(sb, "Attr_list_size is zero"); + attr_list_size = ntfs_attr_size(a); + if (attr_list_size <= 0 || + attr_list_size > NTFS_MAX_ATTR_LIST_SIZE) { + ntfs_error(sb, "Invalid attribute list size %lld (mft_no 0x%llx).%s", + (long long)attr_list_size, ni->mft_no, es); goto put_err_out; } + ni->attr_list_size = (u32)attr_list_size; ni->attr_list = kvzalloc(round_up(ni->attr_list_size, SECTOR_SIZE), GFP_NOFS); if (!ni->attr_list) { diff --git a/fs/ntfs/ntfs.h b/fs/ntfs/ntfs.h index dd56894762a9..9557a5c36b81 100644 --- a/fs/ntfs/ntfs.h +++ b/fs/ntfs/ntfs.h @@ -80,6 +80,8 @@ enum { NTFS_MAX_LABEL_LEN = 128, }; +#define NTFS_MAX_ATTR_LIST_SIZE (256 * 1024) + enum { CASE_SENSITIVE = 0, IGNORE_CASE = 1,