Skip to content

Commit 2109b08

Browse files
fs/ntfs3: correct attr_collapse_range when file is too fragmented
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>
1 parent aee4d5a commit 2109b08

4 files changed

Lines changed: 53 additions & 48 deletions

File tree

fs/ntfs3/attrib.c

Lines changed: 41 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1860,7 +1860,7 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
18601860
struct ATTRIB *attr = NULL, *attr_b;
18611861
struct ATTR_LIST_ENTRY *le, *le_b;
18621862
struct mft_inode *mi, *mi_b;
1863-
CLST svcn, evcn1, len, dealloc, alen;
1863+
CLST svcn, evcn1, len, dealloc, alen, done;
18641864
CLST vcn, end;
18651865
u64 valid_size, data_size, alloc_size, total_size;
18661866
u32 mask;
@@ -1923,6 +1923,7 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
19231923
len = bytes >> sbi->cluster_bits;
19241924
end = vcn + len;
19251925
dealloc = 0;
1926+
done = 0;
19261927

19271928
svcn = le64_to_cpu(attr_b->nres.svcn);
19281929
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)
19311932
attr = attr_b;
19321933
le = le_b;
19331934
mi = mi_b;
1934-
} else if (!le_b) {
1935+
goto check_seg;
1936+
}
1937+
1938+
if (!le_b) {
19351939
err = -EINVAL;
19361940
goto out;
1937-
} else {
1938-
le = le_b;
1939-
attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, NULL, 0, &vcn,
1940-
&mi);
1941-
if (!attr) {
1942-
err = -EINVAL;
1943-
goto out;
1944-
}
1941+
}
19451942

1946-
svcn = le64_to_cpu(attr->nres.svcn);
1947-
evcn1 = le64_to_cpu(attr->nres.evcn) + 1;
1943+
le = le_b;
1944+
attr = ni_find_attr(ni, attr_b, &le, ATTR_DATA, NULL, 0, &vcn, &mi);
1945+
if (!attr) {
1946+
err = -EINVAL;
1947+
goto out;
19481948
}
19491949

19501950
for (;;) {
1951+
CLST vcn1, eat, next_svcn;
1952+
1953+
svcn = le64_to_cpu(attr->nres.svcn);
1954+
evcn1 = le64_to_cpu(attr->nres.evcn) + 1;
1955+
1956+
check_seg:
19511957
if (svcn >= end) {
19521958
/* Shift VCN- */
19531959
attr->nres.svcn = cpu_to_le64(svcn - len);
@@ -1957,30 +1963,33 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
19571963
ni->attr_list.dirty = true;
19581964
}
19591965
mi->dirty = true;
1960-
} else if (svcn < vcn || end < evcn1) {
1961-
CLST vcn1, eat, next_svcn;
1966+
goto next_attr;
1967+
}
19621968

1963-
/* Collapse a part of this attribute segment. */
1964-
err = attr_load_runs(attr, ni, run, &svcn);
1965-
if (err)
1966-
goto out;
1967-
vcn1 = max(vcn, svcn);
1968-
eat = min(end, evcn1) - vcn1;
1969+
run_truncate(run, 0);
1970+
err = attr_load_runs(attr, ni, run, &svcn);
1971+
if (err)
1972+
goto out;
19691973

1970-
err = run_deallocate_ex(sbi, run, vcn1, eat, &dealloc,
1971-
true);
1972-
if (err)
1973-
goto out;
1974+
vcn1 = vcn + done; /* original vcn in attr/run. */
1975+
eat = min(end, evcn1) - vcn1;
1976+
1977+
err = run_deallocate_ex(sbi, run, vcn1, eat, &dealloc, true);
1978+
if (err)
1979+
goto out;
19741980

1975-
if (!run_collapse_range(run, vcn1, eat)) {
1981+
if (svcn + eat < evcn1) {
1982+
/* Collapse a part of this attribute segment. */
1983+
1984+
if (!run_collapse_range(run, vcn1, eat, done)) {
19761985
err = -ENOMEM;
19771986
goto out;
19781987
}
19791988

19801989
if (svcn >= vcn) {
19811990
/* Shift VCN */
19821991
attr->nres.svcn = cpu_to_le64(vcn);
1983-
if (le) {
1992+
if (le && attr->nres.svcn != le->vcn) {
19841993
le->vcn = attr->nres.svcn;
19851994
ni->attr_list.dirty = true;
19861995
}
@@ -1991,7 +2000,7 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
19912000
goto out;
19922001

19932002
next_svcn = le64_to_cpu(attr->nres.evcn) + 1;
1994-
if (next_svcn + eat < evcn1) {
2003+
if (next_svcn + eat + done < evcn1) {
19952004
err = ni_insert_nonresident(
19962005
ni, ATTR_DATA, NULL, 0, run, next_svcn,
19972006
evcn1 - eat - next_svcn, a_flags, &attr,
@@ -2005,18 +2014,9 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
20052014

20062015
/* Free all allocated memory. */
20072016
run_truncate(run, 0);
2017+
done += eat;
20082018
} else {
20092019
u16 le_sz;
2010-
u16 roff = le16_to_cpu(attr->nres.run_off);
2011-
2012-
if (roff > le32_to_cpu(attr->size)) {
2013-
err = -EINVAL;
2014-
goto out;
2015-
}
2016-
2017-
run_unpack_ex(RUN_DEALLOCATE, sbi, ni->mi.rno, svcn,
2018-
evcn1 - 1, svcn, Add2Ptr(attr, roff),
2019-
le32_to_cpu(attr->size) - roff);
20202020

20212021
/* Delete this attribute segment. */
20222022
mi_remove_attr(NULL, mi, attr);
@@ -2029,6 +2029,7 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
20292029
goto out;
20302030
}
20312031

2032+
done += evcn1 - svcn;
20322033
if (evcn1 >= alen)
20332034
break;
20342035

@@ -2046,11 +2047,12 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
20462047
err = -EINVAL;
20472048
goto out;
20482049
}
2049-
goto next_attr;
2050+
continue;
20502051
}
20512052
le = (struct ATTR_LIST_ENTRY *)((u8 *)le - le_sz);
20522053
}
20532054

2055+
next_attr:
20542056
if (evcn1 >= alen)
20552057
break;
20562058

@@ -2059,10 +2061,6 @@ int attr_collapse_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
20592061
err = -EINVAL;
20602062
goto out;
20612063
}
2062-
2063-
next_attr:
2064-
svcn = le64_to_cpu(attr->nres.svcn);
2065-
evcn1 = le64_to_cpu(attr->nres.evcn) + 1;
20662064
}
20672065

20682066
if (!attr_b) {
@@ -2552,7 +2550,7 @@ int attr_insert_range(struct ntfs_inode *ni, u64 vbo, u64 bytes)
25522550
if (attr_load_runs(attr, ni, run, NULL))
25532551
goto bad_inode;
25542552

2555-
if (!run_collapse_range(run, vcn, len))
2553+
if (!run_collapse_range(run, vcn, len, 0))
25562554
goto bad_inode;
25572555

25582556
if (mi_pack_runs(mi, attr, run, evcn1 + len - svcn))

fs/ntfs3/ntfs_fs.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -777,7 +777,7 @@ bool mi_remove_attr(struct ntfs_inode *ni, struct mft_inode *mi,
777777
struct ATTRIB *attr);
778778
bool mi_resize_attr(struct mft_inode *mi, struct ATTRIB *attr, int bytes);
779779
int mi_pack_runs(struct mft_inode *mi, struct ATTRIB *attr,
780-
struct runs_tree *run, CLST len);
780+
const struct runs_tree *run, CLST len);
781781
static inline bool mi_is_ref(const struct mft_inode *mi,
782782
const struct MFT_REF *ref)
783783
{
@@ -812,7 +812,7 @@ void run_truncate_head(struct runs_tree *run, CLST vcn);
812812
void run_truncate_around(struct runs_tree *run, CLST vcn);
813813
bool run_add_entry(struct runs_tree *run, CLST vcn, CLST lcn, CLST len,
814814
bool is_mft);
815-
bool run_collapse_range(struct runs_tree *run, CLST vcn, CLST len);
815+
bool run_collapse_range(struct runs_tree *run, CLST vcn, CLST len, CLST sub);
816816
bool run_insert_range(struct runs_tree *run, CLST vcn, CLST len);
817817
bool run_get_entry(const struct runs_tree *run, size_t index, CLST *vcn,
818818
CLST *lcn, CLST *len);

fs/ntfs3/record.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,7 @@ bool mi_resize_attr(struct mft_inode *mi, struct ATTRIB *attr, int bytes)
621621
* If failed record is not changed.
622622
*/
623623
int mi_pack_runs(struct mft_inode *mi, struct ATTRIB *attr,
624-
struct runs_tree *run, CLST len)
624+
const struct runs_tree *run, CLST len)
625625
{
626626
int err = 0;
627627
struct ntfs_sb_info *sbi = mi->sbi;

fs/ntfs3/run.c

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,7 @@ bool run_add_entry(struct runs_tree *run, CLST vcn, CLST lcn, CLST len,
487487
* Helper for attr_collapse_range(),
488488
* which is helper for fallocate(collapse_range).
489489
*/
490-
bool run_collapse_range(struct runs_tree *run, CLST vcn, CLST len)
490+
bool run_collapse_range(struct runs_tree *run, CLST vcn, CLST len, CLST sub)
491491
{
492492
size_t index, eat;
493493
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)
511511
/* Collapse a middle part of normal run, split. */
512512
if (!run_add_entry(run, vcn, SPARSE_LCN, len, false))
513513
return false;
514-
return run_collapse_range(run, vcn, len);
514+
return run_collapse_range(run, vcn, len, sub);
515515
}
516516

517517
r += 1;
@@ -545,6 +545,13 @@ bool run_collapse_range(struct runs_tree *run, CLST vcn, CLST len)
545545
memmove(eat_start, eat_end, (e - eat_end) * sizeof(*r));
546546
run->count -= eat;
547547

548+
if (sub) {
549+
e -= eat;
550+
for (r = run->runs; r < e; r++) {
551+
r->vcn -= sub;
552+
}
553+
}
554+
548555
return true;
549556
}
550557

0 commit comments

Comments
 (0)