fix(ghidra): preserve POSIX runtime paths with spaces
This commit is contained in:
@@ -80,7 +80,7 @@ export async function verifyNativeValueE2e(
|
||||
const workspace = await mkdtemp(join(tmpdir(), "rea-native-values-e2e-"));
|
||||
const runtime = join(
|
||||
workspace,
|
||||
`runtime${process.platform === "darwin" ? " with spaces" : ""}-${"x".repeat(100)}`,
|
||||
`runtime${process.platform === "win32" ? "" : " with spaces"}-${"x".repeat(100)}`,
|
||||
);
|
||||
await mkdir(runtime);
|
||||
const env = { ...environment(), TMPDIR: runtime };
|
||||
|
||||
@@ -45,9 +45,9 @@ export const resolveGhidraAnalysisProfile = (
|
||||
function_references: "complete-body-and-entry-reference-manager-v2",
|
||||
location_resolution: "explicit-address-exact-name-first-v2",
|
||||
process_launch:
|
||||
installation.platform === "darwin"
|
||||
? "inspected-jvm-launch-support-v1"
|
||||
: "official-headless-script-v1",
|
||||
installation.platform === "win32"
|
||||
? "official-headless-script-v1"
|
||||
: "inspected-jvm-launch-support-v1",
|
||||
...(dos
|
||||
? {
|
||||
load_image_evidence: dosCom
|
||||
|
||||
@@ -3,6 +3,7 @@ import { execFileOutput } from "../process/ExecFileOutput.js";
|
||||
|
||||
/** Launch coordinates obtained from Ghidra's own release configuration helper. */
|
||||
export const ghidraJavaLaunch = async (options: {
|
||||
readonly platform: NodeJS.Platform;
|
||||
readonly analyzeHeadlessPath: string;
|
||||
readonly javaHome: string;
|
||||
readonly homeRoot: string;
|
||||
@@ -71,7 +72,7 @@ export const ghidraJavaLaunch = async (options: {
|
||||
environment,
|
||||
arguments: [
|
||||
...vmArguments,
|
||||
"-Xdock:name=Ghidra-Headless",
|
||||
...(options.platform === "darwin" ? ["-Xdock:name=Ghidra-Headless"] : []),
|
||||
`-Xmx${environment.GHIDRA_HEADLESS_MAXMEM || environment.GHIDRA_MAXMEM || "2G"}`,
|
||||
"-XX:ParallelGCThreads=2",
|
||||
"-XX:CICompilerCount=2",
|
||||
|
||||
@@ -139,13 +139,14 @@ export class GhidraHeadlessLauncher implements GhidraLauncher {
|
||||
platform,
|
||||
scriptCommand.command,
|
||||
);
|
||||
if (platform === "darwin" && this.options.javaHome === undefined)
|
||||
if (platform !== "win32" && this.options.javaHome === undefined)
|
||||
throw new GhidraLaunchError(
|
||||
"macOS Ghidra requires its inspected JDK home",
|
||||
"POSIX Ghidra requires its inspected JDK home",
|
||||
);
|
||||
const command =
|
||||
platform === "darwin" && this.options.javaHome !== undefined
|
||||
platform !== "win32" && this.options.javaHome !== undefined
|
||||
? await ghidraJavaLaunch({
|
||||
platform,
|
||||
analyzeHeadlessPath: this.options.analyzeHeadlessPath,
|
||||
javaHome: this.options.javaHome,
|
||||
homeRoot: paths.homeRoot,
|
||||
@@ -161,9 +162,9 @@ export class GhidraHeadlessLauncher implements GhidraLauncher {
|
||||
command: command.command,
|
||||
arguments: command.arguments,
|
||||
runId: session.runId,
|
||||
// macOS launches the inspected JVM directly: platform shell wrappers
|
||||
// with withheld environments cannot establish run-token ownership.
|
||||
expectedCommand: platform === "darwin" ? command.command : null,
|
||||
// POSIX launches the inspected JVM directly; script wrappers cannot
|
||||
// preserve JVM property paths containing spaces as single arguments.
|
||||
expectedCommand: platform !== "win32" ? command.command : null,
|
||||
windowsVerbatimArguments: platform === "win32",
|
||||
platform,
|
||||
env: command.environment,
|
||||
@@ -441,7 +442,7 @@ const ghidraLaunchEnvironment = (
|
||||
};
|
||||
};
|
||||
|
||||
/** Encode provider-owned JVM paths for the parser that actually consumes them. */
|
||||
/** Encode provider-owned JVM paths for the platform launcher that consumes them. */
|
||||
export const ghidraHeadlessJavaOptions = (
|
||||
homeRoot: string,
|
||||
tempRoot: string,
|
||||
@@ -451,20 +452,16 @@ export const ghidraHeadlessJavaOptions = (
|
||||
"JDK_JAVA_OPTIONS" | "GHIDRA_HEADLESS_JAVA_OPTIONS"
|
||||
> => {
|
||||
const options = [`-Duser.home=${homeRoot}`, `-Djava.io.tmpdir=${tempRoot}`];
|
||||
// analyzeHeadless.bat quotes VMARG_LIST, then launch.bat expands it again.
|
||||
// Inner quotes fail in cmd.exe; unquoted paths with spaces split at the JVM.
|
||||
// JDK_JAVA_OPTIONS is parsed by Java itself, outside that batch expansion.
|
||||
return platform === "win32"
|
||||
? {
|
||||
JDK_JAVA_OPTIONS: [...options, "-XX:-UsePerfData"]
|
||||
.map(quoteWindowsBatchToken)
|
||||
.join(" "),
|
||||
GHIDRA_HEADLESS_JAVA_OPTIONS: "",
|
||||
}
|
||||
: {
|
||||
JDK_JAVA_OPTIONS: "",
|
||||
GHIDRA_HEADLESS_JAVA_OPTIONS: options.join(" "),
|
||||
};
|
||||
if (platform === "win32")
|
||||
return {
|
||||
JDK_JAVA_OPTIONS: [...options, "-XX:-UsePerfData"]
|
||||
.map(quoteWindowsBatchToken)
|
||||
.join(" "),
|
||||
GHIDRA_HEADLESS_JAVA_OPTIONS: "",
|
||||
};
|
||||
// POSIX launches the JVM directly, so these properties travel as separate
|
||||
// argv items instead of the shell-split analyzeHeadless option list.
|
||||
return { JDK_JAVA_OPTIONS: "", GHIDRA_HEADLESS_JAVA_OPTIONS: "" };
|
||||
};
|
||||
|
||||
const isAborted = (signal?: AbortSignal): boolean => signal?.aborted === true;
|
||||
|
||||
@@ -18,14 +18,13 @@ describe("Ghidra headless JVM environment", () => {
|
||||
});
|
||||
|
||||
it.each(["linux", "darwin"] as const)(
|
||||
"keeps the %s headless-script option list and clears inherited JDK options",
|
||||
"passes %s isolated JVM paths through direct argv instead of script options",
|
||||
(platform) => {
|
||||
expect(
|
||||
ghidraHeadlessJavaOptions("/tmp/rea/home", "/tmp/rea/tmp", platform),
|
||||
).toEqual({
|
||||
JDK_JAVA_OPTIONS: "",
|
||||
GHIDRA_HEADLESS_JAVA_OPTIONS:
|
||||
"-Duser.home=/tmp/rea/home -Djava.io.tmpdir=/tmp/rea/tmp",
|
||||
GHIDRA_HEADLESS_JAVA_OPTIONS: "",
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@@ -73,10 +73,8 @@ const expectIsolatedEnvironment = (
|
||||
JAVA_HOME: javaHome,
|
||||
REA_PROCESS_RUN_ID: "d6fcbb66-e829-4ff6-a535-0035aec63139",
|
||||
});
|
||||
expect(environment.PATH).toMatch(
|
||||
process.platform === "win32"
|
||||
? /^C:\\Java\\jdk-21\\bin;/u
|
||||
: /^\/opt\/jdk-21\/bin:/u,
|
||||
expect(environment.PATH).toBe(
|
||||
`${join(javaHome, "bin")}${process.platform === "win32" ? ";" : ":"}${process.env.PATH ?? ""}`,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -200,7 +198,9 @@ describe("Ghidra headless launcher", () => {
|
||||
).toThrow(/metacharacters/u);
|
||||
});
|
||||
|
||||
it("keeps authority in a private descriptor and isolates Ghidra state", async () => {
|
||||
it.skipIf(process.platform !== "win32")(
|
||||
"keeps authority in a private descriptor and isolates Ghidra state through the batch launcher",
|
||||
async () => {
|
||||
vi.stubEnv("GHIDRA_JAVA_OPTIONS", "-javaagent:/unapproved/agent.jar");
|
||||
vi.stubEnv("JAVA_TOOL_OPTIONS", "-Duser.home=/unapproved/home");
|
||||
vi.stubEnv("JDK_JAVA_OPTIONS", "-XX:MaxRAMPercentage=99");
|
||||
@@ -210,13 +210,12 @@ describe("Ghidra headless launcher", () => {
|
||||
runtimes.push(runtime);
|
||||
const runtimeRoot = runtime.path;
|
||||
const token = "secret-token-that-must-not-leak";
|
||||
const javaHome =
|
||||
process.platform === "win32" ? "C:\\Java\\jdk-21" : "/opt/jdk-21";
|
||||
const javaHome = "C:\\Java\\jdk-21";
|
||||
const launcher = new GhidraHeadlessLauncher({
|
||||
analyzeHeadlessPath: fixturePath,
|
||||
// This fixture exercises the official script route. Native macOS JVM
|
||||
// launching is covered by the real Ghidra acceptance lane.
|
||||
platform: process.platform === "win32" ? "win32" : "linux",
|
||||
// POSIX follows the real inspected-JVM route; Windows uses the official
|
||||
// batch script because its launcher contract differs.
|
||||
platform: "win32",
|
||||
javaHome,
|
||||
bridgeScriptPath: "/package/bridge/ReaGhidraBridge.java",
|
||||
});
|
||||
@@ -247,9 +246,7 @@ describe("Ghidra headless launcher", () => {
|
||||
});
|
||||
expectIsolatedEnvironment(capture.environment, runtimeRoot, javaHome);
|
||||
expect(capture.environment.GHIDRA_HEADLESS_JAVA_OPTIONS).toBe(
|
||||
process.platform === "win32"
|
||||
? ""
|
||||
: `-Duser.home=${join(runtimeRoot, "home")} -Djava.io.tmpdir=${join(runtimeRoot, "tmp")}`,
|
||||
"",
|
||||
);
|
||||
if (process.platform !== "win32")
|
||||
expect(
|
||||
@@ -259,5 +256,6 @@ describe("Ghidra headless launcher", () => {
|
||||
const cleaned = await launched.value.cleanup?.();
|
||||
expect(cleaned).toMatchObject({ cleaned: true });
|
||||
await expect(access(join(runtimeRoot, "project"))).resolves.toBeUndefined();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user