mirror of
https://github.com/morluto/rea.git
synced 2026-10-31 20:06:14 +00:00
test(mips): distinguish damaged ELF admission from partial ABI evidence
This commit is contained in:
1 parent
8d3ed58cb7
commit
ac6e0e258a
1 file changed
+72
-27
@@ -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]) {
|
||||
|
||||
Reference in new issue
Block a user