]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ntfs: add bound checking to ntfs_attr_find
authorHyunchul Lee <hyc.lee@gmail.com>
Thu, 12 Mar 2026 01:23:46 +0000 (10:23 +0900)
committerNamjae Jeon <linkinjeon@kernel.org>
Tue, 7 Apr 2026 09:36:05 +0000 (18:36 +0900)
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>
fs/ntfs/attrib.c
fs/ntfs/reparse.c

index 1477dbd3af8246b182f75cf0297cc49c556dc38e..8aa889953e2a10fd17174c3f3f70be6adf58919b 100644 (file)
@@ -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;
 }
index 4cddd918defcb7557ff587f1ea62107bcfe39733..8f60ec6f66c19e67a4c77901e784196d45d7e951 100644 (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;