mirror of
https://github.com/morluto/rea.git
synced 2026-11-12 01:41:21 +00:00
fix(ntfs): report contradictory FILE record extents
Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
This commit is contained in:
1 parent
29fd6a7d03
commit
4db9c166ef
2 files changed
+43
-4
No files matched your search
@@ -78,3 +78,23 @@ it("names a failed fixup in the coverage limitations", async () => {
|
||||
expect.stringMatching(/sector trailer that is not the update sequence/u),
|
||||
);
|
||||
});
|
||||
|
||||
it("reports incomplete coverage when FILE extents contradict the record slots", async () => {
|
||||
const usedTooLarge = recordBytes({});
|
||||
usedTooLarge.writeUInt32LE(4096, 24);
|
||||
const totalTooSmall = recordBytes({});
|
||||
totalTooSmall.writeUInt32LE(512, 28);
|
||||
|
||||
const inventory = await scanCanonicalArtifactInventory(
|
||||
await writeMft("$MFT", Buffer.concat([usedTooLarge, totalTooSmall])),
|
||||
);
|
||||
expect(inventory.coverage.status).toBe("unknown");
|
||||
expect(inventory.limitations).toContainEqual(
|
||||
"2 FILE record(s) declare inconsistent used or total byte counts, beginning at offset 0",
|
||||
);
|
||||
expect(inventory.occurrences[0]?.format_metadata).toMatchObject({
|
||||
record_count: 2,
|
||||
fixup_mismatch_count: 0,
|
||||
fixup_unverified_record_count: 0,
|
||||
});
|
||||
});
|
||||
@@ -96,9 +96,10 @@ export interface MftRecord {
|
||||
readonly flags: { readonly raw: number; readonly names: readonly string[] };
|
||||
readonly inUse: boolean;
|
||||
readonly directory: boolean;
|
||||
/** Bytes the record claims it uses, and the record size it was parsed at. */
|
||||
/** Used and allocated byte counts as declared by the FILE record. */
|
||||
readonly usedBytes: number;
|
||||
readonly totalBytes: number;
|
||||
readonly extentValid: boolean;
|
||||
/** The base record this is an extension of, or `null` for a base record. */
|
||||
readonly baseRecordReference: MftFileReference | null;
|
||||
/** The record number the header declares; `null` when the header predates the field. */
|
||||
@@ -199,6 +200,8 @@ export const parseMftImage = (
|
||||
|
||||
const records: MftRecord[] = [];
|
||||
let invalidSlots = 0;
|
||||
let invalidExtents = 0;
|
||||
let firstInvalidExtentOffset: number | undefined;
|
||||
let firstInvalidOffset: number | undefined;
|
||||
for (
|
||||
let offset = 0;
|
||||
@@ -215,13 +218,23 @@ export const parseMftImage = (
|
||||
if (!isRecordSize(declaredRecordBytes)) break;
|
||||
continue;
|
||||
}
|
||||
records.push(readRecord(bytes, offset, offset / recordBytes, recordBytes));
|
||||
const record = readRecord(bytes, offset, offset / recordBytes, recordBytes);
|
||||
records.push(record);
|
||||
if (!record.extentValid) {
|
||||
invalidExtents += 1;
|
||||
firstInvalidExtentOffset ??= offset;
|
||||
}
|
||||
}
|
||||
if (firstInvalidOffset !== undefined)
|
||||
limitations.push(
|
||||
`record at ${String(firstInvalidOffset)} does not begin with the FILE signature; ${String(invalidSlots)} slot(s) without a FILE signature were found`,
|
||||
);
|
||||
|
||||
if (firstInvalidExtentOffset !== undefined)
|
||||
limitations.push(
|
||||
`${String(invalidExtents)} FILE record(s) declare inconsistent used or total byte counts, beginning at offset ${String(firstInvalidExtentOffset)}`,
|
||||
);
|
||||
|
||||
const truncatedTailBytes = bytes.length % recordBytes;
|
||||
if (truncatedTailBytes !== 0)
|
||||
limitations.push(
|
||||
@@ -284,6 +297,8 @@ const readRecord = (
|
||||
|
||||
const usaOffset = view.getUint16(RECORD_HEADER.updateSequenceOffset, true);
|
||||
const fixups = verifyFixups(record, view, usaOffset);
|
||||
const usedBytes = view.getUint32(RECORD_HEADER.usedBytes, true);
|
||||
const totalBytes = view.getUint32(RECORD_HEADER.totalBytes, true);
|
||||
|
||||
return {
|
||||
index,
|
||||
@@ -293,8 +308,12 @@ const readRecord = (
|
||||
flags: { raw: flags, names: namesOf(flags) },
|
||||
inUse: (flags & FILE_RECORD_IN_USE) !== 0,
|
||||
directory: (flags & FILE_RECORD_IS_DIRECTORY) !== 0,
|
||||
usedBytes: view.getUint32(RECORD_HEADER.usedBytes, true),
|
||||
totalBytes: view.getUint32(RECORD_HEADER.totalBytes, true),
|
||||
usedBytes,
|
||||
totalBytes,
|
||||
extentValid:
|
||||
totalBytes === recordBytes &&
|
||||
usedBytes >= NTFS_MFT_MIN_RECORD_BYTES &&
|
||||
usedBytes <= totalBytes,
|
||||
baseRecordReference:
|
||||
baseRecordReference === 0n
|
||||
? null
|
||||
|
||||
Reference in new issue
Block a user