test(mips): distinguish damaged ELF admission from partial ABI evidence

This commit is contained in:
morluto committed 2026-10-24 05:25:09 +08:00
1 parent 8d3ed58cb7
commit ac6e0e258a
1 file changed
+72 -27
+72 -27
View File
@@ -7,6 +7,7 @@ import { parseBinaryTarget } from "../../../src/application/BinaryTargetResolver
import { AnalysisCancelledError } from "../../../src/domain/analysisErrorCore.js";
import { jsonObjectSchema } from "../../../src/domain/jsonValue.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { expectBinaryTargetError } from "../../fixtures/binaryTargetAdmission.js";
import type { MipsElfMetadata } from "../../../src/domain/binaryTargetTypes.js";
import { readMipsElfMetadata } from "../../../src/application/MipsElfMetadata.js";
import { ok } from "../../../src/domain/result.js";
@@ -134,6 +135,26 @@ const resolveFixture = async (bytes: Buffer) => {
assert.deepEqual(await readFile(path), bytes);
return result.value;
};
const refuseFixture = async (bytes: Buffer, reason: string) => {
const path = await writeFixture(bytes);
const error = expectBinaryTargetError(await parseBinaryTarget(path));
assert.equal(error.constraint, "file_format_rejected");
assert.equal(error.reason, reason);
assert.deepEqual(await readFile(path), bytes);
};
const withoutSectionTable = (source: Buffer, little = true): Buffer => {
const bytes = Buffer.from(source);
if (little) {
bytes.writeUInt32LE(0, 32);
bytes.writeUInt16LE(0, 48);
bytes.writeUInt16LE(0, 50);
} else {
bytes.writeUInt32BE(0, 32);
bytes.writeUInt16BE(0, 48);
bytes.writeUInt16BE(0, 50);
}
return bytes;
};
const inspectDeclarations = async (
bytes: Buffer,
little = true,
@@ -203,45 +224,70 @@ for (const little of [true, false]) {
});
}
it("supports stripped section tables using PT_MIPS_ABIFLAGS", async () => {
const bytes = fixture();
bytes.writeUInt32LE(0, 32);
bytes.writeUInt16LE(0, 48);
const result = await inspect(bytes);
assert.ok(result.ok);
assert.equal(result.value?.fpAbi, 5);
});
for (const little of [true, false]) {
it(`admits a ${little ? "LE" : "BE"} ELF with no section table using PT_MIPS_ABIFLAGS`, async () => {
const bytes = withoutSectionTable(fixture(little), little);
const resolved = await resolveFixture(bytes);
assert.equal(resolved.mips.abiFlags?.fpAbi, 5);
assert.equal(resolved.mips.limitations, undefined);
assert.equal(ghidraMipsUnsupportedReason(resolved), null);
});
for (const availableBytes of [0, 7, 47]) {
it(`resolves a carved ${little ? "LE" : "BE"} ELF with ${availableBytes} captured section-table bytes`, async () => {
it(`refuses a carved ${little ? "LE" : "BE"} ELF retaining a truncated section table with ${availableBytes} captured bytes`, async () => {
const original = fixture(little);
const bytes = Buffer.concat([original, Buffer.alloc(availableBytes)]);
const sectionOffset = original.length + (availableBytes === 0 ? 1 : 0);
if (little) bytes.writeUInt32LE(sectionOffset, 32);
else bytes.writeUInt32BE(sectionOffset, 32);
const resolved = await resolveFixture(bytes);
assert.equal(resolved.mips.abiFlags?.fpAbi, 5);
await refuseFixture(
bytes,
`truncated ELF section header table: declared file range ${sectionOffset}..${sectionOffset + 80} exceeds the ${bytes.length}-byte file`,
);
// The specialized evidence reader may still inspect captured ABI facts.
// Its partial facts do not authorize opening this damaged target.
const declarations = await inspectDeclarations(bytes, little);
assert.ok(declarations.ok);
assert.equal(declarations.value.abiFlags?.fpAbi, 5);
const partial = target({ ...metadata(little), ...declarations.value });
assert.match(
ghidraMipsUnsupportedReason(resolved) ?? "",
ghidraMipsUnsupportedReason(partial) ?? "",
/ABI declaration inspection is incomplete/u,
);
assert.match(
resolved.mips.limitations?.join(" ") ?? "",
/section header table/u,
declarations.value.limitations.join(" "),
new RegExp(
`section header table.*examined ${Math.floor(availableBytes / 40)} of 2 complete headers`,
"u",
),
);
});
}
}
it("keeps declarations in uncaptured section headers unknown when program ABI data is absent", async () => {
it("refuses uncaptured section declarations while retaining unknown ABI evidence and admitting an explicit no-table recordless control", async () => {
const bytes = fixture();
bytes.writeUInt32LE(bytes.length + 1, 32);
bytes.writeUInt32LE(0, 52);
const resolved = await resolveFixture(bytes);
assert.equal(resolved.mips.abiFlags, null);
await refuseFixture(
bytes,
`truncated ELF section header table: declared file range ${bytes.length + 1}..${bytes.length + 81} exceeds the ${bytes.length}-byte file`,
);
const declarations = await inspectDeclarations(bytes);
assert.ok(declarations.ok);
assert.equal(declarations.value.abiFlags, null);
assert.match(
ghidraMipsUnsupportedReason(resolved) ?? "",
ghidraMipsUnsupportedReason(
target({ ...metadata(), ...declarations.value }),
) ?? "",
/ABI declaration inspection is incomplete.*unavailable headers remain unknown/u,
);
const recordless = withoutSectionTable(bytes);
recordless.writeUInt32LE(0, 28);
recordless.writeUInt16LE(0, 44);
recordless.writeUInt32LE(0x50000001, 36); // Explicit legacy MIPS32 lane.
const resolved = await resolveFixture(recordless);
assert.equal(resolved.mips.abiFlags, null);
assert.equal(resolved.mips.limitations, undefined);
assert.equal(ghidraMipsUnsupportedReason(resolved), null);
});
for (const failure of ["conflicting", "outside", "duplicate"]) {
it(`retains ${failure} ABI errors in a partly captured section table`, async () => {
@@ -425,17 +471,16 @@ it("keeps missing ABI data unknown through the public target resolver", async ()
assert.match(ghidraMipsUnsupportedReason(resolved) ?? "", /inspected ABI/u);
});
it("projects a malformed out-of-probe ABI record as a target error", async () => {
const directory = await createTestTempDirectory("rea-mips-resolution-");
const path = join(directory, "fixture.elf");
it("refuses a declared out-of-file ABI payload before specialized ABI inspection", async () => {
const bytes = fixture();
bytes.writeUInt32LE(0xfffffff0, 56);
await writeFile(path, bytes);
const result = await parseBinaryTarget(path);
if (result.ok) throw new Error("Malformed ABI extent was accepted");
assert.equal(result.error._tag, "BinaryTargetError");
assert.match(result.error.message, /ABI record lies outside the file/u);
assert.deepEqual(await readFile(path), bytes);
await refuseFixture(
bytes,
`truncated ELF program 0: declared file range 4294967280..4294967304 exceeds the ${bytes.length}-byte file`,
);
const specialized = await inspect(bytes);
assert.ok(!specialized.ok);
assert.match(specialized.error, /ABI record lies outside the file/u);
});
for (const little of [true, false]) {