From d7338876d620681e98314ffcc8f737771fa4dc80 Mon Sep 17 00:00:00 2001 From: N0zoM1z0 <161784452+N0zoM1z0@users.noreply.github.com> Date: Wed, 11 Nov 2026 00:15:51 +0000 Subject: [PATCH] fix(ntfs): reject malformed MFT update sequence arrays Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com> --- src/domain/windows/ntfsMft.test.ts | 22 ++++++++++++++++++++++ src/domain/windows/ntfsMft.ts | 7 ++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/domain/windows/ntfsMft.test.ts b/src/domain/windows/ntfsMft.test.ts index 1f2e6c0c81..fee133b7b6 100644 --- a/src/domain/windows/ntfsMft.test.ts +++ b/src/domain/windows/ntfsMft.test.ts @@ -143,6 +143,28 @@ describe("parseMftImage", () => { ); }); + it.each([ + [48, 4], + [49, 3], + [508, 3], + ])( + "rejects a malformed update sequence array at %i with %i entries", + (offset, count) => { + const bytes = recordOf(); + const view = new DataView(bytes.buffer); + view.setUint16(4, offset, true); + view.setUint16(6, count, true); + + const parsed = parseMftImage(bytes); + if (!parsed.ok) throw new Error(parsed.error.message); + expect(parsed.value.records[0]?.fixups).toMatchObject({ + malformed: true, + verifiedSectors: 0, + }); + expect(parsed.value.metadata.fixup_unverified_record_count).toBe(1); + }, + ); + it("reports an array that covers fewer sectors than the record has", () => { // A 4 KiB record holds eight sectors; a three-entry array leaves five unverified. const parsed = parseMftImage( diff --git a/src/domain/windows/ntfsMft.ts b/src/domain/windows/ntfsMft.ts index 9100ffcaa6..028647c5c2 100644 --- a/src/domain/windows/ntfsMft.ts +++ b/src/domain/windows/ntfsMft.ts @@ -323,13 +323,14 @@ const verifyFixups = (record: Uint8Array, view: DataView): 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. - const coveredSectors = Math.min(sectors, Math.max(0, usaCount - 1)); + const coveredSectors = Math.max(0, usaCount - 1); if ( usaCount < USA_MIN_ENTRIES || + coveredSectors > sectors || usaOffset < RECORD_HEADER_BYTES || - usaOffset + usaCount * 2 > record.length || - coveredSectors === 0 + usaOffset % 2 !== 0 || + usaOffset + usaCount * 2 > FIXUP_SECTOR_BYTES - 2 ) return { sequenceNumber: 0,