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,40 +939,45 @@ mpa_err:
"attribute.\n"); "attribute.\n");
goto err_out; 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 - 1) >>
vol->cluster_size_bits) - 1; vol->cluster_size_bits);
/*
* A highest_vcn of zero means this is a single extent if (num_clusters > vcn) {
* attribute so simply terminate the runlist with LCN_ENOENT).
*/
if (deltaxcn) {
/* /*
* If there is a difference between the highest_vcn and * The runlist doesn't cover all the clusters, so there
* the highest cluster, the runlist is either corrupt * must be more extents.
* or, more likely, there are more extents following
* this one.
*/ */
if (deltaxcn < max_cluster) { ntfs_log_debug("More extents to follow; vcn = 0x%llx, "
ntfs_log_debug("More extents to follow; deltaxcn = " "num_clusters = 0x%llx\n",
"0x%llx, max_cluster = 0x%llx\n", (long long)vcn,
(long long)deltaxcn, (long long)num_clusters);
(long long)max_cluster); rl[rlpos].vcn = vcn;
rl[rlpos].vcn = vcn; vcn += rl[rlpos].length = num_clusters - vcn;
vcn += rl[rlpos].length = max_cluster - deltaxcn; rl[rlpos].lcn = (LCN)LCN_RL_NOT_MAPPED;
rl[rlpos].lcn = (LCN)LCN_RL_NOT_MAPPED; rlpos++;
rlpos++; } else if (vcn > num_clusters) {
} else if (deltaxcn > max_cluster) { /*
ntfs_log_debug("Corrupt attribute. deltaxcn = " * There are more VCNs in the runlist than expected, so
"0x%llx, max_cluster = 0x%llx\n", * the runlist is corrupt.
(long long)deltaxcn, */
(long long)max_cluster); ntfs_log_error("Corrupt attribute. vcn = 0x%llx, "
goto mpa_err; "num_clusters = 0x%llx\n",
} (long long)vcn,
(long long)num_clusters);
goto mpa_err;
} }
rl[rlpos].lcn = (LCN)LCN_ENOENT; rl[rlpos].lcn = (LCN)LCN_ENOENT;
} else /* Not the base extent. There may be more extents to follow. */ } else /* Not the base extent. There may be more extents to follow. */