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); 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 * ntfs_attr_find - find (next) attribute in mft record
* @type: attribute type to find * @type: attribute type to find
@ -712,38 +741,69 @@ static int ntfs_attr_find(const __le32 type, const __le16 *name,
continue; 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 * The names match or @name not present and attribute is
* unnamed. If no @val specified, we have found the attribute * unnamed. If no @val specified, we have found the attribute
* and are done. * and are done.
*/ */
if (!val) if (!val || a->non_resident)
return 0; return 0;
/* @val is present; compare values. */ /* @val is present; compare values. */
else { 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( rc = memcmp(val, (u8 *)a + le16_to_cpu(
a->data.resident.value_offset), a->data.resident.value_offset),
min_t(u32, val_len, le32_to_cpu( min_t(u32, val_len, value_length));
a->data.resident.value_length)));
/* /*
* If @val collates before the current attribute's * If @val collates before the current attribute's
* value, there is no matching attribute. * value, there is no matching attribute.
*/ */
if (!rc) { if (!rc) {
register u32 avl; if (val_len == value_length)
avl = le32_to_cpu(a->data.resident.value_length);
if (val_len == avl)
return 0; return 0;
if (val_len < avl) if (val_len < value_length)
return -ENOENT; return -ENOENT;
} else if (rc < 0) } else if (rc < 0)
return -ENOENT; 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); NVolSetErrors(vol);
return -EIO; 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; xrni = xr->idx_ni;
if (!ntfs_attr_exist(ni, AT_REPARSE_POINT, AT_UNNAMED, 0)) { 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, * 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; 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) { if (err) {
ntfs_index_ctx_put(xr); ntfs_index_ctx_put(xr);
goto out; goto out;