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 <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
This commit is contained in:
Hyunchul Lee 2026-03-12 10:23:46 +09:00 committed by Namjae Jeon
parent 77f58db739
commit 6ceb4cc81e
2 changed files with 72 additions and 12 deletions

View File

@ -570,6 +570,35 @@ struct runlist_element *ntfs_attr_find_vcn_nolock(struct ntfs_inode *ni, const s
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;
}

View File

@ -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;