summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHyunchul Lee <hyc.lee@gmail.com>2026-03-12 04:23:46 +0300
committerNamjae Jeon <linkinjeon@kernel.org>2026-04-07 12:36:05 +0300
commit6ceb4cc81ef3409ff79dcb959771f9110787397a (patch)
tree2a69fd7c73c89489bf6810adafef094f8b499341
parent77f58db7391e227f8ff6ef5d83966347d759daed (diff)
downloadlinux-6ceb4cc81ef3409ff79dcb959771f9110787397a.tar.xz
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>
-rw-r--r--fs/ntfs/attrib.c80
-rw-r--r--fs/ntfs/reparse.c4
2 files changed, 72 insertions, 12 deletions
diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c
index 1477dbd3af82..8aa889953e2a 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 4cddd918defc..8f60ec6f66c1 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;