fs/ntfs3: Fix case when unmarked clusters intersect with zone
authorKonstantin Komarov <almaz.alexandrovich@paragon-software.com>
Thu, 10 Oct 2024 17:09:24 +0000 (20:09 +0300)
committerKonstantin Komarov <almaz.alexandrovich@paragon-software.com>
Fri, 1 Nov 2024 08:19:50 +0000 (11:19 +0300)
Reported-by: syzbot+7f3761b790fa41d0f3d5@syzkaller.appspotmail.com
Signed-off-by: Konstantin Komarov <almaz.alexandrovich@paragon-software.com>
fs/ntfs3/run.c

index 58e988c..48566df 100644 (file)
@@ -1055,8 +1055,8 @@ int run_unpack_ex(struct runs_tree *run, struct ntfs_sb_info *sbi, CLST ino,
 {
        int ret, err;
        CLST next_vcn, lcn, len;
-       size_t index;
-       bool ok;
+       size_t index, done;
+       bool ok, zone;
        struct wnd_bitmap *wnd;
 
        ret = run_unpack(run, sbi, ino, svcn, evcn, vcn, run_buf, run_buf_size);
@@ -1087,8 +1087,9 @@ int run_unpack_ex(struct runs_tree *run, struct ntfs_sb_info *sbi, CLST ino,
                        continue;
 
                down_read_nested(&wnd->rw_lock, BITMAP_MUTEX_CLUSTERS);
+               zone = max(wnd->zone_bit, lcn) < min(wnd->zone_end, lcn + len);
                /* Check for free blocks. */
-               ok = wnd_is_used(wnd, lcn, len);
+               ok = !zone && wnd_is_used(wnd, lcn, len);
                up_read(&wnd->rw_lock);
                if (ok)
                        continue;
@@ -1096,14 +1097,33 @@ int run_unpack_ex(struct runs_tree *run, struct ntfs_sb_info *sbi, CLST ino,
                /* Looks like volume is corrupted. */
                ntfs_set_state(sbi, NTFS_DIRTY_ERROR);
 
-               if (down_write_trylock(&wnd->rw_lock)) {
-                       /* Mark all zero bits as used in range [lcn, lcn+len). */
-                       size_t done;
-                       err = wnd_set_used_safe(wnd, lcn, len, &done);
-                       up_write(&wnd->rw_lock);
-                       if (err)
-                               return err;
+               if (!down_write_trylock(&wnd->rw_lock))
+                       continue;
+
+               if (zone) {
+                       /*
+                        * Range [lcn, lcn + len) intersects with zone.
+                        * To avoid complex with zone just turn it off.
+                        */
+                       wnd_zone_set(wnd, 0, 0);
+               }
+
+               /* Mark all zero bits as used in range [lcn, lcn+len). */
+               err = wnd_set_used_safe(wnd, lcn, len, &done);
+               if (zone) {
+                       /* Restore zone. Lock mft run. */
+                       struct rw_semaphore *lock;
+                       lock = is_mounted(sbi) ? &sbi->mft.ni->file.run_lock :
+                                                NULL;
+                       if (lock)
+                               down_read(lock);
+                       ntfs_refresh_zone(sbi);
+                       if (lock)
+                               up_read(lock);
                }
+               up_write(&wnd->rw_lock);
+               if (err)
+                       return err;
        }
 
        return ret;