mirror of
https://github.com/morluto/rea.git
synced 2026-11-12 01:41:21 +00:00
fix(ntfs): accept older FILE record headers
Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
This commit is contained in:
1 parent
d7338876d6
commit
29fd6a7d03
2 files changed
+36
-9
No files matched your search
@@ -17,6 +17,7 @@ interface RecordOptions {
|
||||
readonly corruptSector?: number;
|
||||
/** Truncate the update sequence array to this many entries; 0 omits it. */
|
||||
readonly usaEntries?: number;
|
||||
readonly usaOffset?: number;
|
||||
}
|
||||
|
||||
/** One FILE record with a zeroed body and fixups applied the way NTFS writes them. */
|
||||
@@ -30,7 +31,7 @@ const recordOf = (options: RecordOptions = {}): Uint8Array => {
|
||||
|
||||
const usaEntries = options.usaEntries ?? sectors + 1;
|
||||
if (usaEntries > 0) {
|
||||
const usaOffset = 48;
|
||||
const usaOffset = options.usaOffset ?? 48;
|
||||
view.setUint16(4, usaOffset, true);
|
||||
view.setUint16(6, usaEntries, true);
|
||||
view.setUint16(usaOffset, sequenceNumber, true);
|
||||
@@ -110,6 +111,26 @@ describe("parseMftImage", () => {
|
||||
expect(parsed.value.limitations).toEqual([]);
|
||||
});
|
||||
|
||||
it("verifies the older FILE header without reading its fixup words as a record number", () => {
|
||||
const oldRecord = recordOf({ usaOffset: 42 });
|
||||
const newRecord = recordOf();
|
||||
new DataView(newRecord.buffer).setUint32(44, 1, true);
|
||||
|
||||
const parsed = parseMftImage(streamOf(oldRecord, newRecord));
|
||||
if (!parsed.ok) throw new Error(parsed.error.message);
|
||||
expect(parsed.value.records).toMatchObject([
|
||||
{
|
||||
declaredRecordNumber: null,
|
||||
fixups: { malformed: false, verifiedSectors: 2 },
|
||||
},
|
||||
{
|
||||
declaredRecordNumber: 1,
|
||||
fixups: { malformed: false, verifiedSectors: 2 },
|
||||
},
|
||||
]);
|
||||
expect(parsed.value.limitations).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps a record whose sector trailer is not the sequence number, and reports it", () => {
|
||||
const parsed = parseMftImage(
|
||||
streamOf(recordOf(), recordOf({ sequenceNumber: 9, corruptSector: 1 })),
|
||||
@@ -278,7 +299,7 @@ describe("parseMftImage refusals and limits", () => {
|
||||
expect(parsed.ok).toBe(false);
|
||||
if (parsed.ok) return;
|
||||
expect(parsed.error.message).toMatch(
|
||||
/shorter than one 48-byte record header/u,
|
||||
/shorter than one 42-byte record header/u,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import type {
|
||||
export const MFT_FILE_RECORD_MAGIC = "FILE";
|
||||
export const MFT_FILE_RECORD_MAGIC_BYTES = 4;
|
||||
/** The shortest header worth reading: a FILE record header ends at `nextAttributeId`. */
|
||||
export const NTFS_MFT_MIN_RECORD_BYTES = 48;
|
||||
const NTFS_MFT_MIN_RECORD_BYTES = 42;
|
||||
|
||||
/**
|
||||
* The `FILE` signature at file offset 0. An MFT stream is a run of fixed-size
|
||||
@@ -50,7 +50,7 @@ const RECORD_HEADER = {
|
||||
nextAttributeId: 40,
|
||||
recordNumber: 44,
|
||||
} as const;
|
||||
const RECORD_HEADER_BYTES = NTFS_MFT_MIN_RECORD_BYTES;
|
||||
const RECORD_HEADER_WITH_NUMBER_BYTES = 48;
|
||||
|
||||
const FILE_RECORD_IN_USE = 0x0001;
|
||||
const FILE_RECORD_IS_DIRECTORY = 0x0002;
|
||||
@@ -282,6 +282,9 @@ const readRecord = (
|
||||
true,
|
||||
);
|
||||
|
||||
const usaOffset = view.getUint16(RECORD_HEADER.updateSequenceOffset, true);
|
||||
const fixups = verifyFixups(record, view, usaOffset);
|
||||
|
||||
return {
|
||||
index,
|
||||
fileOffset,
|
||||
@@ -297,7 +300,7 @@ const readRecord = (
|
||||
? null
|
||||
: decodeMftFileReference(baseRecordReference),
|
||||
declaredRecordNumber:
|
||||
recordBytes >= RECORD_HEADER.recordNumber + 4
|
||||
!fixups.malformed && usaOffset >= RECORD_HEADER_WITH_NUMBER_BYTES
|
||||
? view.getUint32(RECORD_HEADER.recordNumber, true)
|
||||
: null,
|
||||
logFileSequenceNumber: Number(
|
||||
@@ -308,7 +311,7 @@ const readRecord = (
|
||||
true,
|
||||
),
|
||||
nextAttributeId: view.getUint16(RECORD_HEADER.nextAttributeId, true),
|
||||
fixups: verifyFixups(record, view),
|
||||
fixups,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -318,8 +321,11 @@ const readRecord = (
|
||||
* trailer words. A sector whose trailer is not the sequence number was not
|
||||
* cleanly written, so the record's attributes cannot be trusted.
|
||||
*/
|
||||
const verifyFixups = (record: Uint8Array, view: DataView): MftRecordFixups => {
|
||||
const usaOffset = view.getUint16(RECORD_HEADER.updateSequenceOffset, true);
|
||||
const verifyFixups = (
|
||||
record: Uint8Array,
|
||||
view: DataView,
|
||||
usaOffset: number,
|
||||
): MftRecordFixups => {
|
||||
const usaCount = view.getUint16(RECORD_HEADER.updateSequenceCount, true);
|
||||
const sectors = Math.floor(record.length / FIXUP_SECTOR_BYTES);
|
||||
// Entry 0 holds the sequence number, so sector n's saved trailer is entry n+1.
|
||||
@@ -328,7 +334,7 @@ const verifyFixups = (record: Uint8Array, view: DataView): MftRecordFixups => {
|
||||
if (
|
||||
usaCount < USA_MIN_ENTRIES ||
|
||||
coveredSectors > sectors ||
|
||||
usaOffset < RECORD_HEADER_BYTES ||
|
||||
usaOffset < NTFS_MFT_MIN_RECORD_BYTES ||
|
||||
usaOffset % 2 !== 0 ||
|
||||
usaOffset + usaCount * 2 > FIXUP_SECTOR_BYTES - 2
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user