diff --git a/src/ghidra/GhidraInstallation.ts b/src/ghidra/GhidraInstallation.ts index c6ca5212d0..9675d133fb 100644 --- a/src/ghidra/GhidraInstallation.ts +++ b/src/ghidra/GhidraInstallation.ts @@ -72,6 +72,15 @@ const NATIVE_ARCHITECTURES: readonly { /** Bound installation discovery reads; larger headers remain unknown. */ const EXECUTABLE_HEADER_PROBE_BYTES = 4096; +/** + * Bound the Java version probe so a chatty or hostile JVM cannot force an + * unbounded in-memory capture. Well-behaved JVMs print a few kilobytes for + * `-XshowSettings:properties`; the same 1 MiB budget used for other Java + * listings leaves ample headroom. A probe that exceeds the budget yields no + * observation and is reported through the existing failed-probe path. + */ +const JAVA_PROBE_MAX_OUTPUT_BYTES = 1024 * 1024; + /** Compatibility of one present native decompiler with the inspected host. */ type NativeDecompilerCompatibility = | { @@ -763,7 +772,7 @@ const systemGhidraInstallationHost = (): GhidraInstallationHost => ({ encoding: "utf8", env: environment, timeout: 5_000, - maxBuffer: Number.POSITIVE_INFINITY, + maxBuffer: JAVA_PROBE_MAX_OUTPUT_BYTES, windowsHide: true, }, ); diff --git a/tests/boundary/filesystem/ghidraJavaProbe.test.ts b/tests/boundary/filesystem/ghidraJavaProbe.test.ts index c0a6974d5e..415fbf3ddb 100644 --- a/tests/boundary/filesystem/ghidraJavaProbe.test.ts +++ b/tests/boundary/filesystem/ghidraJavaProbe.test.ts @@ -87,3 +87,43 @@ it.each([ ); }, ); + +it("bounds the Java version probe and reports an over-budget probe as a failed Java check", async () => { + const root = await createTestTempDirectory("rea-java-probe-bound-"); + const configuredHome = join(root, "selected-jdk"); + const installDir = join(root, "ghidra"); + await mkdir(join(installDir, "Ghidra"), { recursive: true }); + await writeFile( + join(installDir, "Ghidra", "application.properties"), + `application.version=${SUPPORTED_GHIDRA_VERSION}\napplication.java.min=17\n`, + ); + // A chatty or hostile JVM that overruns the capture budget: spawnSync reports + // ENOBUFS, which the probe already treats as a failed observation. + mockSpawnSync.mockReturnValue({ + stdout: "", + stderr: "java.version = 21.0.11\n", + status: null, + error: Object.assign(new Error("spawnSync /java ENOBUFS"), { + code: "ENOBUFS", + }), + }); + + const inspection = inspectGhidraInstallation({ + environment: {}, + installDir, + javaHome: configuredHome, + }); + + const [, , options] = mockSpawnSync.mock.calls[0] as [ + string, + readonly string[], + { readonly maxBuffer?: number }, + ]; + expect(options.maxBuffer).toBe(1024 * 1024); + expect(inspection.javaVersion).toBeNull(); + expect(inspection.javaHome).toBe(configuredHome); + expect(inspection.checks.find(({ name }) => name === "java")).toMatchObject({ + status: "failed", + code: "runtime_missing", + }); +});