fix(firmware): treat empty DTB extents as non-overlapping

Refs #7315

Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
This commit is contained in:
morlutoandgensokyo-dev authored and Ysopwnable committed 2026-11-14 16:31:24 +01:00
1 parent 056b78b636
commit 8d546bd028
3 files changed
+54 -18

No files matched your search

@@ -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 };
+7 -4
View File
@@ -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);
+19 -14
View File
@@ -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);
}
},
);
}