From: Hyunchul Lee Date: Thu, 12 Mar 2026 01:23:46 +0000 (+0900) Subject: ntfs: add bound checking to ntfs_attr_find X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=6ceb4cc81ef3409ff79dcb959771f9110787397a;p=thirdparty%2Fkernel%2Flinux.git ntfs: add bound checking to ntfs_attr_find Add bound validations in ntfs_attr_find to ensure attribute value offsets and lengths are safe to access. It verifies that resident attributes meet type-specific minimum length requirements and check the mapping_pairs_offset boundaries for non-resident attributes. Signed-off-by: Hyunchul Lee Signed-off-by: Namjae Jeon --- diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c index 1477dbd3af824..8aa889953e2a1 100644 --- a/fs/ntfs/attrib.c +++ b/fs/ntfs/attrib.c @@ -570,6 +570,35 @@ retry_remap: return ERR_PTR(err); } +static u32 ntfs_resident_attr_min_value_length(const __le32 type) +{ + switch (type) { + case AT_STANDARD_INFORMATION: + return offsetof(struct standard_information, ver) + + sizeof(((struct standard_information *)0)->ver.v1.reserved12); + case AT_ATTRIBUTE_LIST: + return offsetof(struct attr_list_entry, name); + case AT_FILE_NAME: + return offsetof(struct file_name_attr, file_name); + case AT_OBJECT_ID: + return sizeof(struct guid); + case AT_SECURITY_DESCRIPTOR: + return sizeof(struct security_descriptor_relative); + case AT_VOLUME_INFORMATION: + return sizeof(struct volume_information); + case AT_INDEX_ROOT: + return sizeof(struct index_root); + case AT_REPARSE_POINT: + return offsetof(struct reparse_point, reparse_data); + case AT_EA_INFORMATION: + return sizeof(struct ea_information); + case AT_EA: + return offsetof(struct ea_attr, ea_name) + 1; + default: + return 0; + } +} + /* * ntfs_attr_find - find (next) attribute in mft record * @type: attribute type to find @@ -712,38 +741,69 @@ static int ntfs_attr_find(const __le32 type, const __le16 *name, continue; } } + + /* Validate attribute's value offset/length */ + if (!a->non_resident) { + u32 min_len; + u32 value_length = le32_to_cpu(a->data.resident.value_length); + u16 value_offset = le16_to_cpu(a->data.resident.value_offset); + + if (value_length > le32_to_cpu(a->length) || + value_offset > le32_to_cpu(a->length) - value_length) + break; + + min_len = ntfs_resident_attr_min_value_length(a->type); + if (min_len && value_length < min_len) { + ntfs_error(vol->sb, + "Too small %#x resident attribute value in MFT record %lld\n", + le32_to_cpu(a->type), (long long)ctx->ntfs_ino->mft_no); + break; + } + } else { + u32 min_len; + u16 mp_offset; + + min_len = offsetof(struct attr_record, data.non_resident.initialized_size) + + sizeof(a->data.non_resident.initialized_size); + if (le32_to_cpu(a->length) < min_len) + break; + + mp_offset = le16_to_cpu(a->data.non_resident.mapping_pairs_offset); + if (mp_offset < min_len || + mp_offset > le32_to_cpu(a->length)) + break; + } + /* * The names match or @name not present and attribute is * unnamed. If no @val specified, we have found the attribute * and are done. */ - if (!val) + if (!val || a->non_resident) return 0; /* @val is present; compare values. */ else { - register int rc; + u32 value_length = le32_to_cpu(a->data.resident.value_length); + int rc; rc = memcmp(val, (u8 *)a + le16_to_cpu( a->data.resident.value_offset), - min_t(u32, val_len, le32_to_cpu( - a->data.resident.value_length))); + min_t(u32, val_len, value_length)); /* * If @val collates before the current attribute's * value, there is no matching attribute. */ if (!rc) { - register u32 avl; - - avl = le32_to_cpu(a->data.resident.value_length); - if (val_len == avl) + if (val_len == value_length) return 0; - if (val_len < avl) + if (val_len < value_length) return -ENOENT; } else if (rc < 0) return -ENOENT; } } - ntfs_error(vol->sb, "Inode is corrupt. Run chkdsk."); + ntfs_error(vol->sb, "mft %#llx, type %#x is corrupt. Run chkdsk.", + (long long)ctx->ntfs_ino->mft_no, le32_to_cpu(type)); NVolSetErrors(vol); return -EIO; } diff --git a/fs/ntfs/reparse.c b/fs/ntfs/reparse.c index 4cddd918defcb..8f60ec6f66c19 100644 --- a/fs/ntfs/reparse.c +++ b/fs/ntfs/reparse.c @@ -450,7 +450,7 @@ static int ntfs_set_ntfs_reparse_data(struct ntfs_inode *ni, char *value, size_t xrni = xr->idx_ni; if (!ntfs_attr_exist(ni, AT_REPARSE_POINT, AT_UNNAMED, 0)) { - u8 dummy = 0; + struct reparse_point rp = {0, }; /* * no reparse data attribute : add one, @@ -463,7 +463,7 @@ static int ntfs_set_ntfs_reparse_data(struct ntfs_inode *ni, char *value, size_t goto out; } - err = ntfs_attr_add(ni, AT_REPARSE_POINT, AT_UNNAMED, 0, &dummy, 0); + err = ntfs_attr_add(ni, AT_REPARSE_POINT, AT_UNNAMED, 0, (u8 *)&rp, sizeof(rp)); if (err) { ntfs_index_ctx_put(xr); goto out;