From a83e82b0ec3ae523028e24813f23f18b43e8fc1c Mon Sep 17 00:00:00 2001 From: Hongling Zeng Date: Thu, 6 Aug 2026 10:58:30 +0900 Subject: [PATCH] ntfs: validate non-resident attribute offsets ntfs_attr_update_meta() shifts the attribute name when converting between non-sparse and sparse attributes. Converting to sparse also adds the compressed_size field before the name and mapping pairs, requiring eight additional bytes in the attribute record. However, the validator does not check that name_offset is within safe boundaries for these operations or that the additional space is available. A malicious MFT record could set name_offset such that: 1. The name is positioned at the very end of a non-sparse attribute. Converting to sparse would shift the name forward by 8 bytes, writing beyond the attribute boundary. 2. The name overlaps with the mapping pairs, causing corruption during conversion. Add validation to ensure: - For named attributes, name_offset is within valid bounds - Name does not extend beyond the attribute or overlap with mapping pairs - For non-sparse, non-compressed attributes, eight bytes are available after mapping_pairs_offset for the compressed_size field The space check also covers unnamed attributes, for which name_offset = 0 is valid and no name range needs to be checked. Fixes: 7e2a1c554bc4 ("ntfs: Fix min_len for compressed/sparse attributes in ntfs_non_resident_attr_value_is_valid()") Cc: stable@vger.kernel.org Signed-off-by: Hongling Zeng Co-developed-by: Namjae Jeon Signed-off-by: Namjae Jeon --- fs/ntfs/attrib.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index d354c3b0fae1..c62c8ca8b987 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -693,6 +693,8 @@ static bool ntfs_non_resident_attr_value_is_valid(const struct attr_record *a) u32 attr_len; u32 min_len; u16 mp_offset; + u16 name_offset; + u32 name_end; attr_len = le32_to_cpu(a->length); min_len = offsetof(struct attr_record, data.non_resident.initialized_size) + @@ -706,7 +708,27 @@ static bool ntfs_non_resident_attr_value_is_valid(const struct attr_record *a) return false; mp_offset = le16_to_cpu(a->data.non_resident.mapping_pairs_offset); - return mp_offset >= min_len && mp_offset <= attr_len; + if (mp_offset < min_len || mp_offset > attr_len) + return false; + + if (a->name_length) { + name_offset = le16_to_cpu(a->name_offset); + + if (name_offset < min_len || name_offset >= attr_len) + return false; + + name_end = name_offset + a->name_length * sizeof(__le16); + if (name_end > attr_len || name_end > mp_offset) + return false; + } + + /* Ensure there's room for the compressed_size field if needed. */ + if (!(a->flags & (ATTR_IS_SPARSE | ATTR_COMPRESSION_MASK)) && + attr_len - mp_offset < + sizeof(a->data.non_resident.compressed_size)) + return false; + + return true; } static bool ntfs_attr_value_is_valid(struct ntfs_volume *vol,