Fixed special case of decompressing a runlist

When the unreadable directory has an ATTRIBUTE_LIST attribute and an
INDEX_ALLOCATION attribute occupying split over several extents, the first
of which defines a single cluster, the first INDEX_ALLOCATION extent has
lowest_vcn=0 and highest_vcn=0, and the second one has lowest_vcn=1.

This unusual case, which can be created by the combination of a small
volume and near-full MFT records, triggers some special-case behavior in
ntfs_mapping_pairs_decompress_i(). That behavior is incorrect if the
attribute's first extent only contains a single cluster, since in that case
highest_vcn=0 as well.

This configuration has been tested on Windows and it *is* able to
successfully read the directory.  This supports the hypothesis that the
volume is valid and NTFS-3g has a bug on the read side.

This bug could, in theory, occur with any non-resident attribute, not just
INDEX_ALLOCATION attributes.

(Contributed by Eric Biggers)
This commit is contained in:
Jean-Pierre André 2015-11-20 16:17:48 +01:00
parent f85b82c8e1
commit aeb1d7fb74

View File

@ -939,41 +939,46 @@ mpa_err:
"attribute.\n");
goto err_out;
}
/* Setup not mapped runlist element if this is the base extent. */
if (!attr->lowest_vcn) {
VCN max_cluster;
max_cluster = ((sle64_to_cpu(attr->allocated_size) +
/*
* If this is the base of runlist (if 'lowest_vcn' is 0), then
* 'allocated_size' is valid, and we can use it to compute the total
* number of clusters across all extents. If the runlist covers all
* clusters, then it fits into a single extent and we can terminate
* the runlist with LCN_NOENT. Otherwise, we must terminate the runlist
* with LCN_RL_NOT_MAPPED and let the caller look for more extents.
*/
if (!attr->lowest_vcn) {
VCN num_clusters;
num_clusters = ((sle64_to_cpu(attr->allocated_size) +
vol->cluster_size - 1) >>
vol->cluster_size_bits) - 1;
vol->cluster_size_bits);
if (num_clusters > vcn) {
/*
* A highest_vcn of zero means this is a single extent
* attribute so simply terminate the runlist with LCN_ENOENT).
* The runlist doesn't cover all the clusters, so there
* must be more extents.
*/
if (deltaxcn) {
/*
* If there is a difference between the highest_vcn and
* the highest cluster, the runlist is either corrupt
* or, more likely, there are more extents following
* this one.
*/
if (deltaxcn < max_cluster) {
ntfs_log_debug("More extents to follow; deltaxcn = "
"0x%llx, max_cluster = 0x%llx\n",
(long long)deltaxcn,
(long long)max_cluster);
ntfs_log_debug("More extents to follow; vcn = 0x%llx, "
"num_clusters = 0x%llx\n",
(long long)vcn,
(long long)num_clusters);
rl[rlpos].vcn = vcn;
vcn += rl[rlpos].length = max_cluster - deltaxcn;
vcn += rl[rlpos].length = num_clusters - vcn;
rl[rlpos].lcn = (LCN)LCN_RL_NOT_MAPPED;
rlpos++;
} else if (deltaxcn > max_cluster) {
ntfs_log_debug("Corrupt attribute. deltaxcn = "
"0x%llx, max_cluster = 0x%llx\n",
(long long)deltaxcn,
(long long)max_cluster);
} else if (vcn > num_clusters) {
/*
* There are more VCNs in the runlist than expected, so
* the runlist is corrupt.
*/
ntfs_log_error("Corrupt attribute. vcn = 0x%llx, "
"num_clusters = 0x%llx\n",
(long long)vcn,
(long long)num_clusters);
goto mpa_err;
}
}
rl[rlpos].lcn = (LCN)LCN_ENOENT;
} else /* Not the base extent. There may be more extents to follow. */
rl[rlpos].lcn = (LCN)LCN_RL_NOT_MAPPED;