From 8d546bd028ebe5fd539035c2762fb152c8cd370a Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Thu, 29 Oct 2026 07:11:52 +0800 Subject: [PATCH] fix(firmware): treat empty DTB extents as non-overlapping Refs #7315 Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com> --- .../devicetree/DevicetreeParser.test.ts | 28 ++++++++++++++++ src/firmware/devicetree/DevicetreeParser.ts | 11 ++++--- tests/acceptance/devicetreeCli.test.ts | 33 +++++++++++-------- 3 files changed, 54 insertions(+), 18 deletions(-) diff --git a/src/firmware/devicetree/DevicetreeParser.test.ts b/src/firmware/devicetree/DevicetreeParser.test.ts index cbc8c9ddfd..8be6519d66 100644 --- a/src/firmware/devicetree/DevicetreeParser.test.ts +++ b/src/firmware/devicetree/DevicetreeParser.test.ts @@ -259,6 +259,34 @@ it.each([16, 17] as const)( expect(report.reservations).toEqual([]); }, ); +it.each([ + [16, 0], + [16, 44], + [16, 60], + [16, 72], + [17, 0], + [17, 44], + [17, 60], + [17, 72], +] as const)( + "accepts a v%s empty strings block at offset %s", + (version, offset) => { + const bytes = emptyStringsAtReservationBoundaryFixture(version); + bytes.writeUInt32BE(offset, 12); + expect(parse(bytes)).toMatchObject({ + nodes: [{ path: "/" }], + reservations: [], + }); + }, +); +it.each([16, 17] as const)( + "rejects an empty strings offset beyond totalsize in v%s", + (version) => { + const bytes = emptyStringsAtReservationBoundaryFixture(version); + bytes.writeUInt32BE(bytes.length + 1, 12); + expect(() => parse(bytes)).toThrow("FDT block outside totalsize"); + }, +); it("bounds expansion of repeated paths before allocation", () => { const longName = "a".repeat(4096); let node: FixtureNode = { name: longName }; diff --git a/src/firmware/devicetree/DevicetreeParser.ts b/src/firmware/devicetree/DevicetreeParser.ts index b4016559d1..6120bab4aa 100644 --- a/src/firmware/devicetree/DevicetreeParser.ts +++ b/src/firmware/devicetree/DevicetreeParser.ts @@ -76,8 +76,10 @@ export const parseDevicetree = ( { start: 0, end: headerBytes }, ]; const range = (start: number, size: number) => { - if (size < 0 || start < headerBytes || start > end - size) + if (size < 0 || (size > 0 && start < headerBytes) || start > end - size) malformed("FDT block outside totalsize"); + // An empty extent occupies no bytes and cannot overlap another block. + if (size === 0) return; const r = { start, end: start + size }; if (ranges.some((other) => r.start < other.end && other.start < r.end)) malformed("Overlapping FDT blocks"); @@ -91,9 +93,10 @@ export const parseDevicetree = ( header.size_dt_struct === null ? Math.min( end, - ...[header.off_dt_strings, header.off_mem_rsvmap].filter( - (n) => n > header.off_dt_struct, - ), + ...[ + ...(header.size_dt_strings > 0 ? [header.off_dt_strings] : []), + header.off_mem_rsvmap, + ].filter((n) => n > header.off_dt_struct), ) : header.off_dt_struct + header.size_dt_struct; range(header.off_dt_struct, structEnd - header.off_dt_struct); diff --git a/tests/acceptance/devicetreeCli.test.ts b/tests/acceptance/devicetreeCli.test.ts index 56879511de..b3ed7d7aef 100644 --- a/tests/acceptance/devicetreeCli.test.ts +++ b/tests/acceptance/devicetreeCli.test.ts @@ -146,22 +146,27 @@ cliTest("inspects a relative DTB and preserves the source", async ({ cli }) => { }); for (const version of [16, 17] as const) { cliTest( - `inspects a v${version} DTB with empty strings at the reservation boundary`, + `inspects a v${version} DTB with empty strings at arbitrary in-blob offsets`, async ({ cli }) => { const root = await createTestTempDirectory("rea-dtb-empty-strings-"); - const path = join(root, "board.dtb"); - await writeFile(path, emptyStringsAtReservationBoundaryFixture(version)); - const result = await cli.run({ - arguments: ["inspect-devicetree", path, "--json"], - }); - expect(result.exitCode, result.stderr).toBe(0); - const report = toolContract("inspect_devicetree").outputSchema.parse( - result.json, - ).normalized_result; - expect(report).toMatchObject({ - nodes: [{ path: "/" }], - reservations: [], - }); + for (const offset of [0, 44, 56, 60, 72]) { + const path = join(root, `board-${String(offset)}.dtb`); + const bytes = emptyStringsAtReservationBoundaryFixture(version); + bytes.writeUInt32BE(offset, 12); + await writeFile(path, bytes); + const result = await cli.run({ + arguments: ["inspect-devicetree", path, "--json"], + }); + expect(result.exitCode, result.stderr).toBe(0); + const report = toolContract("inspect_devicetree").outputSchema.parse( + result.json, + ).normalized_result; + expect(report).toMatchObject({ + nodes: [{ path: "/" }], + reservations: [], + }); + expect(Buffer.compare(await readFile(path), bytes)).toBe(0); + } }, ); }