]> Gentwo Git Trees - linux/.git/commitdiff
fs/ntfs3: correct attr_collapse_range when file is too fragmented
authorKonstantin Komarov <almaz.alexandrovich@paragon-software.com>
Thu, 30 Oct 2025 20:35:24 +0000 (23:35 +0300)
committerKonstantin Komarov <almaz.alexandrovich@paragon-software.com>
Mon, 17 Nov 2025 08:08:49 +0000 (09:08 +0100)
Fix incorrect VCN adjustments in attr_collapse_range() that caused
filesystem errors or corruption on very fragmented NTFS files when
performing collapse-range operations.

Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
fs/ntfs3/attrib.c
fs/ntfs3/ntfs_fs.h
fs/ntfs3/record.c
fs/ntfs3/run.c

index d0373254f82aaa5c8b5451d2e064bb34dd9b46fc..980ae9157248da1b54660f2ee959415c6dae3079 100644 (file)
@@ -1860,7 +1860,7 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
        struct ATTRIB *attr = NULL, *attr_b;
        struct ATTR_LIST_ENTRY *le, *le_b;
        struct mft_inode *mi, *mi_b;
-       CLST svcn, evcn1, len, dealloc, alen;
+       CLST svcn, evcn1, len, dealloc, alen, done;
        CLST vcn, end;
        u64 valid_size, data_size, alloc_size, total_size;
        u32 mask;
@@ -1923,6 +1923,7 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
        len = bytes >> sbi->cluster_bits;
        end = vcn + len;
        dealloc = 0;
+       done = 0;
 
        svcn = le64_to_cpu(attr_b->nres.svcn);
        evcn1 = le64_to_cpu(attr_b->nres.evcn) + 1;
@@ -1931,23 +1932,28 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
                attr = attr_b;
                le = le_b;
                mi = mi_b;
-       } else if (!le_b) {
+               goto check_seg;
+       }
+
+       if (!le_b) {
                err = -EINVAL;
                goto out;
-       } else {
-               le = le_b;
-               attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, NULL, 0, &vcn,
-                                   &mi);
-               if (!attr) {
-                       err = -EINVAL;
-                       goto out;
-               }
+       }
 
-               svcn = le64_to_cpu(attr->nres.svcn);
-               evcn1 = le64_to_cpu(attr->nres.evcn) + 1;
+       le = le_b;
+       attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, NULL, 0, &vcn, &mi);
+       if (!attr) {
+               err = -EINVAL;
+               goto out;
        }
 
        for (;;) {
+               CLST vcn1, eat, next_svcn;
+
+               svcn = le64_to_cpu(attr->nres.svcn);
+               evcn1 = le64_to_cpu(attr->nres.evcn) + 1;
+
+check_seg:
                if (svcn >= end) {
                        /* Shift VCN- */
                        attr->nres.svcn = cpu_to_le64(svcn - len);
@@ -1957,22 +1963,25 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
                                ni->attr_list.dirty = true;
                        }
                        mi->dirty = true;
-               } else if (svcn < vcn || end < evcn1) {
-                       CLST vcn1, eat, next_svcn;
+                       goto next_attr;
+               }
 
-                       /* Collapse a part of this attribute segment. */
-                       err = attr_load_runs(attr, ni, run, &svcn);
-                       if (err)
-                               goto out;
-                       vcn1 = max(vcn, svcn);
-                       eat = min(end, evcn1) - vcn1;
+               run_truncate(run, 0);
+               err = attr_load_runs(attr, ni, run, &svcn);
+               if (err)
+                       goto out;
 
-                       err = run_deallocate_ex(sbi, run, vcn1, eat, &dealloc,
-                                               true);
-                       if (err)
-                               goto out;
+               vcn1 = vcn + done; /* original vcn in attr/run. */
+               eat = min(end, evcn1) - vcn1;
+
+               err = run_deallocate_ex(sbi, run, vcn1, eat, &dealloc, true);
+               if (err)
+                       goto out;
 
-                       if (!run_collapse_range(run, vcn1, eat)) {
+               if (svcn + eat < evcn1) {
+                       /* Collapse a part of this attribute segment. */
+
+                       if (!run_collapse_range(run, vcn1, eat, done)) {
                                err = -ENOMEM;
                                goto out;
                        }
@@ -1980,7 +1989,7 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
                        if (svcn >= vcn) {
                                /* Shift VCN */
                                attr->nres.svcn = cpu_to_le64(vcn);
-                               if (le) {
+                               if (le && attr->nres.svcn != le->vcn) {
                                        le->vcn = attr->nres.svcn;
                                        ni->attr_list.dirty = true;
                                }
@@ -1991,7 +2000,7 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
                                goto out;
 
                        next_svcn = le64_to_cpu(attr->nres.evcn) + 1;
-                       if (next_svcn + eat < evcn1) {
+                       if (next_svcn + eat + done < evcn1) {
                                err = ni_insert_nonresident(
                                        ni, ATTR_DATA, NULL, 0, run, next_svcn,
                                        evcn1 - eat - next_svcn, a_flags, &attr,
@@ -2005,18 +2014,9 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
 
                        /* Free all allocated memory. */
                        run_truncate(run, 0);
+                       done += eat;
                } else {
                        u16 le_sz;
-                       u16 roff = le16_to_cpu(attr->nres.run_off);
-
-                       if (roff > le32_to_cpu(attr->size)) {
-                               err = -EINVAL;
-                               goto out;
-                       }
-
-                       run_unpack_ex(RUN_DEALLOCATE, sbi, ni->mi.rno, svcn,
-                                     evcn1 - 1, svcn, Add2Ptr(attr, roff),
-                                     le32_to_cpu(attr->size) - roff);
 
                        /* Delete this attribute segment. */
                        mi_remove_attr(NULL, mi, attr);
@@ -2029,6 +2029,7 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
                                goto out;
                        }
 
+                       done += evcn1 - svcn;
                        if (evcn1 >= alen)
                                break;
 
@@ -2046,11 +2047,12 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
                                        err = -EINVAL;
                                        goto out;
                                }
-                               goto next_attr;
+                               continue;
                        }
                        le = (struct ATTR_LIST_ENTRY *)((u8 *)le - le_sz);
                }
 
+next_attr:
                if (evcn1 >= alen)
                        break;
 
@@ -2059,10 +2061,6 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
                        err = -EINVAL;
                        goto out;
                }
-
-next_attr:
-               svcn = le64_to_cpu(attr->nres.svcn);
-               evcn1 = le64_to_cpu(attr->nres.evcn) + 1;
        }
 
        if (!attr_b) {
@@ -2552,7 +2550,7 @@ int attr_insert_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
        if (attr_load_runs(attr, ni, run, NULL))
                goto bad_inode;
 
-       if (!run_collapse_range(run, vcn, len))
+       if (!run_collapse_range(run, vcn, len, 0))
                goto bad_inode;
 
        if (mi_pack_runs(mi, attr, run, evcn1 + len - svcn))
index 86f825cf1c29dec7347b366ed7a0fae2b5fb72be..8ff49c5a2973d1d49f3bc16b0d6d693b2907c7a6 100644 (file)
@@ -777,7 +777,7 @@ bool mi_remove_attr(struct ntfs_inode *ni, struct mft_inode *mi,
                    struct ATTRIB *attr);
 bool mi_resize_attr(struct mft_inode *mi, struct ATTRIB *attr, int bytes);
 int mi_pack_runs(struct mft_inode *mi, struct ATTRIB *attr,
-                struct runs_tree *run, CLST len);
+                const struct runs_tree *run, CLST len);
 static inline bool mi_is_ref(const struct mft_inode *mi,
                             const struct MFT_REF *ref)
 {
@@ -812,7 +812,7 @@ void run_truncate_head(struct runs_tree *run, CLST vcn);
 void run_truncate_around(struct runs_tree *run, CLST vcn);
 bool run_add_entry(struct runs_tree *run, CLST vcn, CLST lcn, CLST len,
                   bool is_mft);
-bool run_collapse_range(struct runs_tree *run, CLST vcn, CLST len);
+bool run_collapse_range(struct runs_tree *run, CLST vcn, CLST len, CLST sub);
 bool run_insert_range(struct runs_tree *run, CLST vcn, CLST len);
 bool run_get_entry(const struct runs_tree *run, size_t index, CLST *vcn,
                   CLST *lcn, CLST *len);
index 714c7ecedca830757df17743b043443240c074e5..167093e8d2873620cba4ed3c9751a30d688fb027 100644 (file)
@@ -621,7 +621,7 @@ bool mi_resize_attr(struct mft_inode *mi, struct ATTRIB *attr, int bytes)
  * If failed record is not changed.
  */
 int mi_pack_runs(struct mft_inode *mi, struct ATTRIB *attr,
-                struct runs_tree *run, CLST len)
+                const struct runs_tree *run, CLST len)
 {
        int err = 0;
        struct ntfs_sb_info *sbi = mi->sbi;
index 5df55e4adbb114eac49e38ab2c3da6485abb45d4..395b20492525833ec3105fb6dc4fd98457560d67 100644 (file)
@@ -487,7 +487,7 @@ bool run_add_entry(struct runs_tree *run, CLST vcn, CLST lcn, CLST len,
  * Helper for attr_collapse_range(),
  * which is helper for fallocate(collapse_range).
  */
-bool run_collapse_range(struct runs_tree *run, CLST vcn, CLST len)
+bool run_collapse_range(struct runs_tree *run, CLST vcn, CLST len, CLST sub)
 {
        size_t index, eat;
        struct ntfs_run *r, *e, *eat_start, *eat_end;
@@ -511,7 +511,7 @@ bool run_collapse_range(struct runs_tree *run, CLST vcn, CLST len)
                        /* Collapse a middle part of normal run, split. */
                        if (!run_add_entry(run, vcn, SPARSE_LCN, len, false))
                                return false;
-                       return run_collapse_range(run, vcn, len);
+                       return run_collapse_range(run, vcn, len, sub);
                }
 
                r += 1;
@@ -545,6 +545,13 @@ bool run_collapse_range(struct runs_tree *run, CLST vcn, CLST len)
        memmove(eat_start, eat_end, (e - eat_end) * sizeof(*r));
        run->count -= eat;
 
+       if (sub) {
+               e -= eat;
+               for (r = run->runs; r < e; r++) {
+                       r->vcn -= sub;
+               }
+       }
+
        return true;
 }