fix(ghidra): refuse PSP targets with unexamined ABI declarations (#3904)
The generic Ghidra MIPS lane refuses a target whose ABI declaration inspection is incomplete, such as a section-header table beyond the captured file, because unexamined headers may hold declarations that would refuse it. The PSP Allegrex lane skipped that check. It admitted the target and resolved a supported profile while target inspection said those declarations remained unknown. The incomplete-declaration rule is now one shared helper, ghidraMipsIncompleteDeclarationsReason, used by both the generic MIPS and the PSP lanes. Fixes #3329 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
57f30029b1
commit
ab45bf641e
3 files changed
+80
-25
No files matched your search
@@ -69,6 +69,20 @@ const hasLegacyMipsHeaderShape = (target: BinaryTarget): boolean => {
|
||||
};
|
||||
|
||||
/** Return a refusal for GNU ABI attributes this existing MIPS lane cannot model. */
|
||||
/**
|
||||
* Every MIPS lane, including PSP, needs every ABI declaration source examined:
|
||||
* headers outside the captured file may hold declarations that would refuse it.
|
||||
*/
|
||||
export const ghidraMipsIncompleteDeclarationsReason = (
|
||||
metadata: MipsElfMetadata,
|
||||
): string | null =>
|
||||
(metadata.limitations?.length ?? 0) > 0
|
||||
? [
|
||||
"MIPS ABI declaration inspection is incomplete; unsupported attributes or conflicting declaration sources may remain unknown.",
|
||||
...(metadata.limitations ?? []),
|
||||
].join(" ")
|
||||
: null;
|
||||
|
||||
export const ghidraMipsGnuAttributesUnsupportedReason = (
|
||||
metadata: MipsElfMetadata,
|
||||
): string | null => {
|
||||
@@ -164,11 +178,8 @@ export const ghidraMipsUnsupportedReason = (
|
||||
if (load.symbolRelativeCount !== 0)
|
||||
return "Symbol-bearing R_MIPS_REL32 is unsupported: Ghidra normalizes MIPS16 function addresses and can lose the source function-pointer mode tag. Symbol-index-zero REL32 is verified separately.";
|
||||
}
|
||||
if ((metadata.limitations?.length ?? 0) > 0)
|
||||
return [
|
||||
"MIPS ABI declaration inspection is incomplete; unsupported attributes or conflicting declaration sources may remain unknown.",
|
||||
...(metadata.limitations ?? []),
|
||||
].join(" ");
|
||||
const incomplete = ghidraMipsIncompleteDeclarationsReason(metadata);
|
||||
if (incomplete !== null) return incomplete;
|
||||
const abi = metadata.abiFlags;
|
||||
if (shared && abi === null && (metadata.limitations?.length ?? 0) === 0)
|
||||
return null; // Inspected absence leaves FP semantics unknown, never inferred.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { BinaryTarget } from "../domain/binaryTargetTypes.js";
|
||||
import {
|
||||
ghidraMipsGnuAttributesUnsupportedReason,
|
||||
ghidraMipsIncompleteDeclarationsReason,
|
||||
ghidraMipsUnsupportedReason,
|
||||
} from "./GhidraMipsProfile.js";
|
||||
import { ghidraPowerPcUnsupportedReason } from "./GhidraPowerPcProfile.js";
|
||||
@@ -33,6 +34,8 @@ export const ghidraProcessorUnsupportedReason = (
|
||||
const gnuAttributesReason =
|
||||
ghidraMipsGnuAttributesUnsupportedReason(metadata);
|
||||
if (gnuAttributesReason !== null) return gnuAttributesReason;
|
||||
const incomplete = ghidraMipsIncompleteDeclarationsReason(metadata);
|
||||
if (incomplete !== null) return incomplete;
|
||||
if (metadata.elfClass !== 32 || metadata.byteOrder !== "little")
|
||||
return "The PSP Allegrex profile requires little-endian ELF32.";
|
||||
if (metadata.type !== 2)
|
||||
|
||||
@@ -326,31 +326,72 @@ describe.skipIf(process.platform === "win32")(
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* A PSPSDK-shaped ELF32 whose PT_MIPS_ABIFLAGS record matches the PSP profile.
|
||||
* Optionally declare a section-header table beyond the captured file.
|
||||
*/
|
||||
const pspElfBytes = ({ sectionHeadersBeyondEof = false } = {}): Buffer => {
|
||||
const abiOffset = 8192;
|
||||
const bytes = Buffer.alloc(abiOffset + 24);
|
||||
bytes.set([0x7f, 0x45, 0x4c, 0x46, 1, 1, 1]);
|
||||
bytes.writeUInt16LE(2, 16);
|
||||
bytes.writeUInt16LE(8, 18);
|
||||
bytes.writeUInt32LE(1, 20);
|
||||
bytes.writeUInt32LE(52, 28);
|
||||
bytes.writeUInt32LE(0x10a23001, 36);
|
||||
bytes.writeUInt16LE(52, 40);
|
||||
bytes.writeUInt16LE(32, 42);
|
||||
bytes.writeUInt16LE(1, 44);
|
||||
if (sectionHeadersBeyondEof) {
|
||||
bytes.writeUInt32LE(bytes.length + 64, 32); // e_shoff
|
||||
bytes.writeUInt16LE(40, 46); // e_shentsize
|
||||
bytes.writeUInt16LE(2, 48); // e_shnum
|
||||
}
|
||||
bytes.writeUInt32LE(0x70000003, 52);
|
||||
bytes.writeUInt32LE(abiOffset, 56);
|
||||
bytes.writeUInt32LE(24, 68);
|
||||
bytes.writeUInt32LE(24, 72);
|
||||
// GNU readelf -x .MIPS.abiflags, not bytes generated by REA.
|
||||
bytes.set(
|
||||
Buffer.from("000002000101000200000000000000000100000000000000", "hex"),
|
||||
abiOffset,
|
||||
);
|
||||
return bytes;
|
||||
};
|
||||
|
||||
describe.skipIf(process.platform === "win32")(
|
||||
"PSP inspected ABI declaration boundary",
|
||||
() => {
|
||||
it("refuses a PSP image whose section headers lie beyond the captured file", async () => {
|
||||
const { directory, provider } = await installation();
|
||||
const path = join(directory, "psp-truncated-sections.elf");
|
||||
await writeFile(path, pspElfBytes({ sectionHeadersBeyondEof: true }));
|
||||
const parsed = await parseBinaryTarget(path);
|
||||
if (!parsed.ok) throw parsed.error;
|
||||
if (
|
||||
parsed.value.kind !== "executable" ||
|
||||
parsed.value.architecture !== "mips"
|
||||
)
|
||||
throw new Error("PSP ELF was not recognized as MIPS");
|
||||
expect(parsed.value.mips.abiFlags).toEqual(pspAbiFlags);
|
||||
expect(parsed.value.mips.limitations).toContainEqual(
|
||||
expect.stringContaining("ABI declarations in unavailable headers"),
|
||||
);
|
||||
expect(provider.inspectTargetSupport(parsed.value).status).toBe(
|
||||
"unsupported",
|
||||
);
|
||||
const result = await provider.resolveAnalysisProfile(parsed.value);
|
||||
expect(result.ok).toBe(false);
|
||||
if (result.ok) throw new Error("Incomplete ABI inspection was admitted");
|
||||
expect(result.error._tag).toBe("AnalysisUnsupportedTargetError");
|
||||
expect(result.error.message).toContain(
|
||||
"MIPS ABI declaration inspection is incomplete",
|
||||
);
|
||||
});
|
||||
|
||||
it("retains independently inspected PSP ABI bytes through the real target reader", async () => {
|
||||
const { directory, provider } = await installation();
|
||||
const abiOffset = 8192;
|
||||
const bytes = Buffer.alloc(abiOffset + 24);
|
||||
bytes.set([0x7f, 0x45, 0x4c, 0x46, 1, 1, 1]);
|
||||
bytes.writeUInt16LE(2, 16);
|
||||
bytes.writeUInt16LE(8, 18);
|
||||
bytes.writeUInt32LE(1, 20);
|
||||
bytes.writeUInt32LE(52, 28);
|
||||
bytes.writeUInt32LE(0x10a23001, 36);
|
||||
bytes.writeUInt16LE(52, 40);
|
||||
bytes.writeUInt16LE(32, 42);
|
||||
bytes.writeUInt16LE(1, 44);
|
||||
bytes.writeUInt32LE(0x70000003, 52);
|
||||
bytes.writeUInt32LE(abiOffset, 56);
|
||||
bytes.writeUInt32LE(24, 68);
|
||||
bytes.writeUInt32LE(24, 72);
|
||||
// GNU readelf -x .MIPS.abiflags, not bytes generated by REA.
|
||||
bytes.set(
|
||||
Buffer.from("000002000101000200000000000000000100000000000000", "hex"),
|
||||
abiOffset,
|
||||
);
|
||||
const bytes = pspElfBytes();
|
||||
const path = join(directory, "psp-with-abi.elf");
|
||||
await writeFile(path, bytes);
|
||||
const parsed = await parseBinaryTarget(path);
|
||||
|
||||
Reference in new issue
Block a user