fix(ghidra): bound the Java version probe output (#2361)

The Java version probe captured its output with maxBuffer set to Infinity, so a
chatty or hostile JVM selected as the Ghidra launcher could grow the captured
stdout/stderr without limit and exhaust REA's memory during installation
discovery. Bound the capture to 1 MiB, matching the budget already used for
other Java listings. A probe that exceeds the budget yields no observation and
is reported through the existing failed-probe path, the same one the Java check
already uses whenever the probe cannot produce an observation.

Co-authored-by: Claude Code <noreply@anthropic.com>
This commit is contained in:
Dery FerdikaandClaude Code authored and GitHub committed 2026-10-13 20:55:40 +08:00
1 parent 8e6f37e2a0
commit e0bd31c18c
2 files changed
+50 -1

No files matched your search

+10 -1
View File
@@ -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,
},
);
@@ -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",
});
});