mirror of
https://github.com/morluto/rea.git
synced 2026-11-04 22:06:10 +00:00
Reject native callee successes without an explicit truncation marker while preserving provider failure reasons. Verify positive procedure inventory/search and function-result parity in the real-provider lane; support independent string/xref oracles and label their absence as unverified.
Verified with Windows IDA Pro 9.3: eight normal production CLI/MCP checks passed, and six real-provider response-fault cases reproduced old false positives and validated the correction. Windows IDA Free was not used. No personal paths or license registration information are included.
Repair inconsistent complete-file Ghidra test probe sizes after the shared ELF-header validation change, deriving sizes from the fixture buffers. Reproduced the three failures before the correction; 115 related installation/doctor and ELF tests pass afterward. Runtime parsing and installation behavior are unchanged; no real-Ghidra verification is claimed.
207 focused IDA tests and static/documentation checks passed. Automated review completed on d1e0b1095 without findings. CI run 38327862093 passed all 36 jobs, including Windows, all six test shards, coverage/timing, and real Apple CLI/MCP workflows.
Follow-up to #1544.
130 lines
3.9 KiB
TypeScript
130 lines
3.9 KiB
TypeScript
import { z } from "zod";
|
|
import { AnalysisProtocolError } from "../domain/analysisErrorCore.js";
|
|
import {
|
|
cursorSchema,
|
|
disassemblySchema,
|
|
idaAddressSchema,
|
|
modernCalleesSchema,
|
|
modernStringsSchema,
|
|
modernXrefsSchema,
|
|
} from "./IdaProtocolValues.js";
|
|
|
|
/** Native MCP renders an assembly header followed by bare hexadecimal coordinates. */
|
|
const nativeAssemblySchema = disassemblySchema
|
|
.omit({ lines: true })
|
|
.extend({
|
|
lines: z.string(),
|
|
})
|
|
.transform((asm) => {
|
|
const [header, ...body] = asm.lines.split("\n");
|
|
const match = header?.match(/^(.+) \((.*) @ (0x[0-9a-f]+)\):$/iu);
|
|
if (
|
|
match?.[1] !== asm.name ||
|
|
idaAddressSchema.parse(match?.[3]) !== asm.start_ea
|
|
)
|
|
throw new AnalysisProtocolError(
|
|
"Native IDA assembly header contradicts its function identity.",
|
|
);
|
|
return {
|
|
...asm,
|
|
lines: body.map((line) => {
|
|
const item = line.match(/^([0-9a-f]+) (.*)$/iu);
|
|
if (item === null)
|
|
throw new AnalysisProtocolError(
|
|
"Native IDA returned an unsupported assembly line.",
|
|
);
|
|
return {
|
|
address: idaAddressSchema.parse(item[1]),
|
|
instruction: item[2] ?? "",
|
|
};
|
|
}),
|
|
};
|
|
});
|
|
|
|
/** Native assembly retains the same explicit pagination cursor as modern MCP. */
|
|
export const nativeDisassemblySchema = z.object({
|
|
asm: nativeAssemblySchema.nullable(),
|
|
error: z.string().optional(),
|
|
cursor: cursorSchema,
|
|
});
|
|
|
|
/** GUI decompilation reports the resolved entry and an explicit success/failure. */
|
|
export const nativeDecompileSchema = z.object({
|
|
addr: idaAddressSchema,
|
|
ok: z.boolean(),
|
|
pseudocode: z.string().min(1).optional(),
|
|
lines: z.number().int().nonnegative().optional(),
|
|
error: z.string().optional(),
|
|
});
|
|
|
|
/** Native relationship successes explicitly carry null errors. Normalize that omission here. */
|
|
const nativeErrorSchema = z
|
|
.string()
|
|
.nullish()
|
|
.transform((value) => value ?? undefined);
|
|
export const nativeCalleesSchema = z
|
|
.array(
|
|
modernCalleesSchema.element
|
|
.extend({ error: nativeErrorSchema })
|
|
.transform((page) => {
|
|
if (
|
|
page.error === undefined &&
|
|
page.callees !== null &&
|
|
page.more === undefined
|
|
)
|
|
throw new AnalysisProtocolError(
|
|
"Native IDA callees omitted an explicit truncation marker.",
|
|
);
|
|
return page;
|
|
}),
|
|
)
|
|
.length(1);
|
|
export const nativeXrefsSchema = z
|
|
.array(modernXrefsSchema.element.extend({ error: nativeErrorSchema }))
|
|
.length(1);
|
|
|
|
/** Native regex failures carry an empty page, which must not imply absence. */
|
|
export const nativeStringsSchema = modernStringsSchema
|
|
.extend({ error: z.string().optional() })
|
|
.transform((page) => {
|
|
if (page.error !== undefined)
|
|
throw new AnalysisProtocolError(
|
|
`Native IDA string search failed: ${page.error}`,
|
|
);
|
|
return page;
|
|
});
|
|
|
|
/**
|
|
* Native regexes fold bytes, while REA's insensitive literal search folds
|
|
* Unicode strings. Include every non-ASCII value as a candidate, then apply
|
|
* REA's exact predicate. Ill-formed UTF-16 cannot cross upstream JSON/UTF-8.
|
|
*/
|
|
export const nativeLiteralSearchPattern = (
|
|
pattern: string,
|
|
caseSensitive: boolean,
|
|
): string | undefined => {
|
|
if (/[\uD800-\uDFFF]/u.test(pattern)) return undefined;
|
|
let literal = (caseSensitive ? pattern : pattern.toLowerCase()).replace(
|
|
/[.*+?^${}()|[\]\\]/gu,
|
|
"\\$&",
|
|
);
|
|
// Spell ASCII variants explicitly rather than depending on an upstream locale.
|
|
if (!caseSensitive)
|
|
literal = literal.replace(
|
|
/[a-z]/gu,
|
|
(char) => `[${char}${char.toUpperCase()}]`,
|
|
);
|
|
return caseSensitive ? literal : `(?:${literal})|[\\x80-\\xff]`;
|
|
};
|
|
|
|
/** Read-only tools required from the native bridge; GUI lifecycle tools are excluded. */
|
|
export const nativeTools = [
|
|
"list_funcs",
|
|
"lookup_funcs",
|
|
"find_regex",
|
|
"decompile",
|
|
"disasm",
|
|
"callees",
|
|
"xrefs_to",
|
|
];
|