Compare commits

...
5 changed files with 71 additions and 8 deletions
+5 -2
View File
@@ -565,8 +565,11 @@ Java major range, 64-bit JDK bitness, and the presence of `javac`/`javac.exe`.
It also checks that the reported JVM `os.arch` and `os.name` map to the same native decompiler
directory validated for the host; unrecognized reported architectures or a
non-host operating system are refused. LaunchSupport overrides of either
property, including through helper-environment JVM option variables, are
rejected at launch.
property, including quoted helper-environment JVM options, are rejected at
launch. Indirect JVM `@argument-file` options are also refused because their
effective platform properties cannot be established before launch; provide
explicit options instead. OS-name matching follows Ghidra's operating-system
identifiers: macOS requires `Mac OS X`, not `Darwin` or a generic `mac*` alias.
When Java is found through `PATH`, setup records its observed JDK home so GUI
MCP clients do not depend on an incidental shell path. Setup shows every exact
environment entry in its plan, writes only after approval, and never downloads,
+30
View File
@@ -1041,6 +1041,36 @@ describe("Ghidra JVM native directory admission", () => {
});
describe("Ghidra JVM OS admission", () => {
it.each([
["Darwin", false],
["macOS", false],
["Macintosh", false],
["Mac OS X", true],
["MAC OS X 26", true],
["JVM on Mac OS X", true],
["Mac OS X / Windows", false],
["Mac OS X / Linux", false],
] as const)(
"matches Ghidra's ordered OS-name predicate for %s (available=%s)",
(osName, available) => {
const result = inspectGhidraInstallation(
{
environment: {},
installDir: INSTALL,
platform: "darwin",
architecture: "arm64",
},
host({ probeJava: () => ({ ...JAVA, osArch: "aarch64", osName }) }),
);
expect(result.status).toBe(available ? "available" : "unavailable");
expect(projectGhidraDoctorInspection(result).available).toBe(available);
expect(result.javaOsName).toBe(osName);
expect(
result.checks.find(({ name }) => name === "native_decompiler"),
).toMatchObject({ status: available ? "passed" : "failed" });
},
);
it("rejects a successful Java probe that omits os.name", () => {
const { osName: _observedOsName, ...withoutOsName } = JAVA;
const result = inspectGhidraInstallation(
+11 -5
View File
@@ -741,11 +741,17 @@ const jvmOsNameMatchesPlatform = (
osName: string,
): boolean => {
const normalized = osName.toLowerCase();
if (platform === "win32") return normalized.startsWith("windows");
if (platform === "darwin")
return normalized.startsWith("mac") || normalized.startsWith("darwin");
if (platform === "linux") return normalized.startsWith("linux");
return false;
// Ghidra 12.1.4 OperatingSystem.findCurrentOperatingSystem uses ordered,
// case-insensitive substring matching, not Node's platform names or broad
// mac*/Darwin aliases. Preserve that order for names containing several tags.
const selected = normalized.includes("windows")
? "win32"
: normalized.includes("linux")
? "linux"
: normalized.includes("mac os x")
? "darwin"
: undefined;
return selected === platform;
};
/**
+9 -1
View File
@@ -26,6 +26,13 @@ const jvmEnvironmentArguments = (value: string): readonly string[] => {
};
const rejectJvmPlatformOverrides = (arguments_: readonly string[]): void => {
// Indirect options can change the JVM platform after this admission check.
// Do not read arbitrary, mutable argument files and pretend their contents
// establish the later launch; require explicit inspectable options instead.
if (arguments_.some((argument) => argument.startsWith("@")))
throw new Error(
"Ghidra LaunchSupport may not supply JVM argument files; their effective platform properties cannot be validated before launch.",
);
if (
arguments_.some((argument) => /^-Dos\.(?:arch|name)(?:=|$)/u.test(argument))
)
@@ -113,7 +120,8 @@ export const ghidraJavaLaunch = async (options: {
// The merged environment becomes the analysis JVM's environment, and the
// JVM applies these options variables automatically. An injected
// -Dos.arch/-Dos.name there selects an unvalidated native directory without
// appearing in vmArguments, so reject the effective values as well.
// appearing in vmArguments, so reject the effective values as well. Argument
// files are refused because their expanded options are not established here.
for (const key of [
"JAVA_TOOL_OPTIONS",
"_JAVA_OPTIONS",
@@ -273,6 +273,22 @@ describe.skipIf(process.platform === "win32")(
expect(analysisArguments).toHaveLength(1);
});
it.each(["@options", '"@options with spaces"'])(
"refuses indirect JVM options (%s) instead of assuming platform alignment",
async (value) => {
const { session, launcher, analysisArguments } = await fixture(
`env-injection:JDK_JAVA_OPTIONS=${value}`,
);
const launched = await launcher.launch(session);
expect(launched.ok).toBe(false);
expect(analysisArguments).toEqual([]);
if (launched.ok) throw new Error("Expected an argument-file refusal");
expect(launched.error.cause).toMatchObject({
message: expect.stringContaining("may not supply JVM argument files"),
});
},
);
it("bounds retained UTF-8 warnings and reports original byte counts", async () => {
const { session, launcher } = await fixture("large");
const launched = await launcher.launch(session);