fs/ntfs3: Add more attributes checks in mi_enum_attr()
authorKonstantin Komarov <almaz.alexandrovich@paragon-software.com>
Fri, 30 Jun 2023 12:17:02 +0000 (16:17 +0400)
committerKonstantin Komarov <almaz.alexandrovich@paragon-software.com>
Thu, 28 Sep 2023 12:03:57 +0000 (15:03 +0300)
Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
fs/ntfs3/record.c

index c12ebff..02cc91e 100644 (file)
@@ -193,8 +193,9 @@ struct ATTRIB *mi_enum_attr(struct mft_inode *mi, struct ATTRIB *attr)
 {
        const struct MFT_REC *rec = mi->mrec;
        u32 used = le32_to_cpu(rec->used);
-       u32 t32, off, asize;
+       u32 t32, off, asize, prev_type;
        u16 t16;
+       u64 data_size, alloc_size, tot_size;
 
        if (!attr) {
                u32 total = le32_to_cpu(rec->total);
@@ -213,6 +214,7 @@ struct ATTRIB *mi_enum_attr(struct mft_inode *mi, struct ATTRIB *attr)
                if (!is_rec_inuse(rec))
                        return NULL;
 
+               prev_type = 0;
                attr = Add2Ptr(rec, off);
        } else {
                /* Check if input attr inside record. */
@@ -226,11 +228,11 @@ struct ATTRIB *mi_enum_attr(struct mft_inode *mi, struct ATTRIB *attr)
                        return NULL;
                }
 
-               if (off + asize < off) {
-                       /* Overflow check. */
+               /* Overflow check. */
+               if (off + asize < off)
                        return NULL;
-               }
 
+               prev_type = le32_to_cpu(attr->type);
                attr = Add2Ptr(attr, asize);
                off += asize;
        }
@@ -250,7 +252,11 @@ struct ATTRIB *mi_enum_attr(struct mft_inode *mi, struct ATTRIB *attr)
 
        /* 0x100 is last known attribute for now. */
        t32 = le32_to_cpu(attr->type);
-       if ((t32 & 0xf) || (t32 > 0x100))
+       if (!t32 || (t32 & 0xf) || (t32 > 0x100))
+               return NULL;
+
+       /* attributes in record must be ordered by type */
+       if (t32 < prev_type)
                return NULL;
 
        /* Check overflow and boundary. */
@@ -259,16 +265,15 @@ struct ATTRIB *mi_enum_attr(struct mft_inode *mi, struct ATTRIB *attr)
 
        /* Check size of attribute. */
        if (!attr->non_res) {
+               /* Check resident fields. */
                if (asize < SIZEOF_RESIDENT)
                        return NULL;
 
                t16 = le16_to_cpu(attr->res.data_off);
-
                if (t16 > asize)
                        return NULL;
 
-               t32 = le32_to_cpu(attr->res.data_size);
-               if (t16 + t32 > asize)
+               if (t16 + le32_to_cpu(attr->res.data_size) > asize)
                        return NULL;
 
                t32 = sizeof(short) * attr->name_len;
@@ -278,21 +283,52 @@ struct ATTRIB *mi_enum_attr(struct mft_inode *mi, struct ATTRIB *attr)
                return attr;
        }
 
-       /* Check some nonresident fields. */
-       if (attr->name_len &&
-           le16_to_cpu(attr->name_off) + sizeof(short) * attr->name_len >
-                   le16_to_cpu(attr->nres.run_off)) {
+       /* Check nonresident fields. */
+       if (attr->non_res != 1)
+               return NULL;
+
+       t16 = le16_to_cpu(attr->nres.run_off);
+       if (t16 > asize)
+               return NULL;
+
+       t32 = sizeof(short) * attr->name_len;
+       if (t32 && le16_to_cpu(attr->name_off) + t32 > t16)
+               return NULL;
+
+       /* Check start/end vcn. */
+       if (le64_to_cpu(attr->nres.svcn) > le64_to_cpu(attr->nres.evcn) + 1)
+               return NULL;
+
+       data_size = le64_to_cpu(attr->nres.data_size);
+       if (le64_to_cpu(attr->nres.valid_size) > data_size)
                return NULL;
-       }
 
-       if (attr->nres.svcn || !is_attr_ext(attr)) {
+       alloc_size = le64_to_cpu(attr->nres.alloc_size);
+       if (data_size > alloc_size)
+               return NULL;
+
+       t32 = mi->sbi->cluster_mask;
+       if (alloc_size & t32)
+               return NULL;
+
+       if (!attr->nres.svcn && is_attr_ext(attr)) {
+               /* First segment of sparse/compressed attribute */
+               if (asize + 8 < SIZEOF_NONRESIDENT_EX)
+                       return NULL;
+
+               tot_size = le64_to_cpu(attr->nres.total_size);
+               if (tot_size & t32)
+                       return NULL;
+
+               if (tot_size > alloc_size)
+                       return NULL;
+       } else {
                if (asize + 8 < SIZEOF_NONRESIDENT)
                        return NULL;
 
                if (attr->nres.c_unit)
                        return NULL;
-       } else if (asize + 8 < SIZEOF_NONRESIDENT_EX)
-               return NULL;
+       }
 
        return attr;
 }