[GH-ISSUE #7075] Windows: Ghidra provider fails to start - LaunchSupport cannot write java_home.save (access denied) #4264

Open
opened 2026-10-31 00:48:17 +00:00 by bsakaguchi · 11 comments
Owner

Originally created by @songtiancheng992-prog on GitHub (Oct 25, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/7075

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Setup, packaging, or upgrade

Observed behavior

On Windows x64, the Ghidra provider never reaches the bridge: Ghidra's own
LaunchSupport cannot write java_home.save inside REA's per-session config
directory, so it falls back to an interactive JDK prompt, reads EOF, and exits.

Provider: ghidra 12.1.4 · rea-agents@6.7.0 · Node v24.14.1 · Windows 11 x64.
Command: rea analyze <target>.exe (a 985 KB x86-64 PE).
Result: execution_failure / failure_kind: process /
failure_message: Ghidra exited before bridge startup.

rea doctor reports every Ghidra check healthy: configuration, platform (win32),
architecture (x64), installation, version (12.1.4), headless
(support/analyzeHeadless.bat), native_decompiler (decompile.exe reports pe x86_64), java (21.0.12.1; 64-bit; JDK; accepted), native_authority (Job Object,
private DACL verified).

Captured stderr from Ghidra's launch support:

java.io.FileNotFoundException:
  C:\Users\<user>\AppData\Local\Temp\rea-ghidra-<hash>\config\ghidra\ghidra_12.1.4_PUBLIC\java_home.save
  (Access is denied)
    at java.base/java.io.FileOutputStream.open0(Native Method)
    at java.base/java.io.FileOutputStream.open(FileOutputStream.java:289)
    at java.base/java.io.FileOutputStream.<init>(FileOutputStream.java:230)
    at java.base/java.io.FileWriter.<init>(FileWriter.java:98)
    at ghidra.launch.AppConfig.saveJavaHome(AppConfig.java:148)
    at ghidra.launch.LaunchSupport.findJavaHome(LaunchSupport.java:210)
    at ghidra.launch.LaunchSupport.handleJavaHome(LaunchSupport.java:153)
    at ghidra.launch.LaunchSupport.main(LaunchSupport.java:113)

stdout:

******************************************************************
JDK 21+ (64-bit) could not be found and must be manually chosen!
******************************************************************
Enter path to JDK home directory (ENTER for dialog):
ERROR: Failed to find a supported JDK.

The stdin is empty, so the prompt path also throws:

java.lang.NullPointerException: Cannot invoke "String.trim()"
  because the return value of "java.io.BufferedReader.readLine()" is null
    at ghidra.launch.LaunchSupport.askJavaHome(LaunchSupport.java:300)
    at ghidra.launch.LaunchSupport.handleJavaHome(LaunchSupport.java:151)

REA records exit_code: 1 and two launch_support entries (-envvars,
-vmargs) with the same stderr. Source of the diagnosis: the captured
LaunchSupport stack; I did not observe a successful bridge start.

Reproduction

  1. Windows 11 x64. Portable Temurin JDK 21.0.12.1+1 extracted to a short path,
    JAVA_HOME set to its root. Ghidra 12.1.4 from the official release zip,
    GHIDRA_INSTALL_DIR set to the extracted directory.
    rea-agents@6.7.0 installed via npm. Node v24.14.1.
  2. rea doctor -> all ghidra-* checks healthy.
  3. rea analyze <target>.exe on any PE (reproduced with a stock 985 KB jq.exe).
  4. Observe: Ghidra launch support fails saving java_home.save, the provider
    never starts, and rea returns Ghidra exited before bridge startup.

Reproduced 3/3. Also reproduced after:

  • adding %JAVA_HOME%\bin to PATH;
  • renaming the JDK directory to remove + from 21.0.12.1+1 (this fixed a
    different, earlier error; it does not fix this one);
  • setting APPDATA and XDG_CONFIG_HOME to a writable directory for the REA
    process — the config path still resolves under REA's own
    rea-ghidra-<hash>\config;
  • running the same command through the MCP server instead of the CLI (identical
    failure).

Control: the same Ghidra installation, invoked directly
(support/analyzeHeadless.bat <proj> <name> -import <target>), imports and
auto-analyzes the same PE successfully in about 61 s (REPORT: Import succeeded). So the installation, the JDK, and the target are all usable;
the failure is specific to how REA launches Ghidra.

Expected behavior

With GHIDRA_INSTALL_DIR and JAVA_HOME both set, valid, and reported healthy by
rea doctor, the Ghidra provider should start on Windows x64 without an
interactive JDK prompt, and rea analyze / binary_overview should return
analysis instead of Ghidra exited before bridge startup.

Impact and scope

The Ghidra provider is unusable on this Windows x64 host for any target, so all
native analysis (decompile, function analysis, cross references, call graph) is
blocked. Everything else works: inspect_artifact, static JS/Electron, managed
PE/CLI, Android package parsing, and the capture/pcap tooling all run locally,
and the MCP registration works.

Impact: P1 for this host, because the failure is not target-specific. Workaround:
drive support/analyzeHeadless.bat with a Ghidra post-script directly, which
works on the same host.

Scope: provider launch on Windows only. No change to analysis semantics is
requested.

Not a duplicate: searching the tracker for "Ghidra exited before bridge startup"
returns 12 hits and none reports a Windows java_home.save failure.

  • #3501 (closed) fails earlier with provider_unavailable / unsupported_host
    because a source install lacks native/windows/build/manifest.json. Here the
    npm install ships the native artifact, rea doctor reports
    ghidra-native_authority healthy, and the failure is inside Ghidra's own
    launch support.
  • #1026 (closed) is a Windows snapshot sharing violation; #4588 is a POSIX
    AF_UNIX denial. Both are different failure stages and platforms.

REA version

6.7.0 (npm rea-agents). Both the CLI path and the MCP path are affected.

Runtime and platform

Windows 11 x64; Node v24.14.1; Temurin JDK 21.0.12.1+1 (portable full JDK); Ghidra 12.1.4 from the official release zip; non-elevated user.

Target and artifact context

Any PE. Reproduced with a stock 985 KB x86-64 PE executable (sha256 7451fbbf37feffb9bf262bd97c54f0da558c63f0748e64152dd87b0a07b6d6ab). The same file imports and auto-analyzes with direct analyzeHeadless in about 61 s.

Interface

CLI

Earliest failure stage

Setup or provider admission

Relevant output or evidence

rea doctor (Ghidra section, trimmed)
ghidra                     ok  healthy  <ghidra root>
ghidra-platform            ok  healthy  win32
ghidra-architecture        ok  healthy  x64
ghidra-installation        ok  healthy  ...\Ghidra\application.properties
ghidra-version             ok  healthy  12.1.4
ghidra-headless            ok  healthy  ...\support\analyzeHeadless.bat
ghidra-native_decompiler   ok  healthy  ...\decompile.exe reports pe x86_64
ghidra-java                ok  healthy  21.0.12.1; 64-bit; JDK; accepted
ghidra-native_authority    ok  healthy  Job Object, private DACL verified
Full Ghidra launch stderr
NOTE: Picked up JDK_JAVA_OPTIONS: "-Duser.home=...\rea-ghidra-<hash>\home"
                                 "-Djava.io.tmpdir=...\rea-ghidra-<hash>\tmp"
java.io.FileNotFoundException:
  ...\rea-ghidra-<hash>\config\ghidra\ghidra_12.1.4_PUBLIC\java_home.save
  (Access is denied)
    at java.base/java.io.FileOutputStream.open0(Native Method)
    at ghidra.launch.AppConfig.saveJavaHome(AppConfig.java:148)
    at ghidra.launch.LaunchSupport.findJavaHome(LaunchSupport.java:210)
    at ghidra.launch.LaunchSupport.handleJavaHome(LaunchSupport.java:153)
Direct Ghidra control run (same install, works)
analyzeHeadless.bat <proj> Control -import <target>
...
Total Time   61 secs
INFO  REPORT: Analysis succeeded
INFO  REPORT: Import succeeded

Suspected cause (inferred from the stack, not confirmed by a maintainer): REA
creates the per-session runtime root with a hardened DACL, then points Ghidra's
config directory at <runtime_root>\config. Ghidra's LaunchSupport tries to
cache the resolved JDK in java_home.save under that directory, the write is
denied, and it falls back to the interactive JDK prompt.

Questions:

  1. Is a cached java_home.save supposed to be pre-seeded by REA on Windows?
  2. Is there a supported way to bypass LaunchSupport's JDK prompt (env var, or a
    launch config that points at java.exe directly)?
  3. Would pre-creating the config directory writable by the child be acceptable?

Analysis or suspected cause

Suspected cause (source-derived, not confirmed by a maintainer):

REA creates the per-session runtime root with a hardened DACL (see the
ghidra-native_authority doctor check) and points Ghidra's config directory at
<runtime_root>\config. On the first launch, Ghidra's
LaunchSupport.findJavaHome resolves the JDK and then caches it in
<config>\ghidra\ghidra_12.1.4_PUBLIC\java_home.save. That write raises
FileNotFoundException ... (Access is denied); LaunchSupport falls back to the
interactive prompt, reads EOF and exits 1, so the bridge never starts.

Evidence for this ordering:

  • The captured stderr stack is AppConfig.saveJavaHome ->
    LaunchSupport.findJavaHome -> LaunchSupport.handleJavaHome ->
    LaunchSupport.main, i.e. the save happens while resolving the JDK, not later.
  • JAVA_HOME is valid for the same REA process: the ghidra-java doctor check
    executes that JDK and accepts 21.0.12.1; 64-bit; JDK.
  • Redirecting APPDATA / XDG_CONFIG_HOME for the REA process does not move
    the config path; it stays under rea-ghidra-<hash>\config, so the path is
    chosen by REA rather than inherited from the environment.
  • The identical Ghidra install imports and auto-analyzes the same PE in ~61 s
    when launched directly, so the JDK, the native decompiler and the target are
    usable.

What would falsify this: a pre-seeded java_home.save, or a config directory
writable by the launched JVM, still failing with the same prompt. In that case
the blocked step is elsewhere in the launch handshake.

Not claimed: no maintainer confirmation, and no observed successful Windows
bridge start.

Additional context

No response

Originally created by @songtiancheng992-prog on GitHub (Oct 25, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7075 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Setup, packaging, or upgrade ### Observed behavior On Windows x64, the Ghidra provider never reaches the bridge: Ghidra's own LaunchSupport cannot write `java_home.save` inside REA's per-session config directory, so it falls back to an interactive JDK prompt, reads EOF, and exits. Provider: ghidra 12.1.4 · rea-agents@6.7.0 · Node v24.14.1 · Windows 11 x64. Command: `rea analyze <target>.exe` (a 985 KB x86-64 PE). Result: `execution_failure` / `failure_kind: process` / `failure_message: Ghidra exited before bridge startup`. `rea doctor` reports every Ghidra check healthy: configuration, platform (win32), architecture (x64), installation, version (12.1.4), headless (`support/analyzeHeadless.bat`), native_decompiler (`decompile.exe reports pe x86_64`), java (`21.0.12.1; 64-bit; JDK; accepted`), native_authority (Job Object, private DACL verified). Captured stderr from Ghidra's launch support: ``` java.io.FileNotFoundException: C:\Users\<user>\AppData\Local\Temp\rea-ghidra-<hash>\config\ghidra\ghidra_12.1.4_PUBLIC\java_home.save (Access is denied) at java.base/java.io.FileOutputStream.open0(Native Method) at java.base/java.io.FileOutputStream.open(FileOutputStream.java:289) at java.base/java.io.FileOutputStream.<init>(FileOutputStream.java:230) at java.base/java.io.FileWriter.<init>(FileWriter.java:98) at ghidra.launch.AppConfig.saveJavaHome(AppConfig.java:148) at ghidra.launch.LaunchSupport.findJavaHome(LaunchSupport.java:210) at ghidra.launch.LaunchSupport.handleJavaHome(LaunchSupport.java:153) at ghidra.launch.LaunchSupport.main(LaunchSupport.java:113) ``` stdout: ``` ****************************************************************** JDK 21+ (64-bit) could not be found and must be manually chosen! ****************************************************************** Enter path to JDK home directory (ENTER for dialog): ERROR: Failed to find a supported JDK. ``` The stdin is empty, so the prompt path also throws: ``` java.lang.NullPointerException: Cannot invoke "String.trim()" because the return value of "java.io.BufferedReader.readLine()" is null at ghidra.launch.LaunchSupport.askJavaHome(LaunchSupport.java:300) at ghidra.launch.LaunchSupport.handleJavaHome(LaunchSupport.java:151) ``` REA records `exit_code: 1` and two `launch_support` entries (`-envvars`, `-vmargs`) with the same stderr. Source of the diagnosis: the captured LaunchSupport stack; I did not observe a successful bridge start. ### Reproduction 1. Windows 11 x64. Portable Temurin JDK 21.0.12.1+1 extracted to a short path, `JAVA_HOME` set to its root. Ghidra 12.1.4 from the official release zip, `GHIDRA_INSTALL_DIR` set to the extracted directory. `rea-agents@6.7.0` installed via npm. Node v24.14.1. 2. `rea doctor` -> all ghidra-* checks healthy. 3. `rea analyze <target>.exe` on any PE (reproduced with a stock 985 KB jq.exe). 4. Observe: Ghidra launch support fails saving `java_home.save`, the provider never starts, and rea returns `Ghidra exited before bridge startup`. Reproduced 3/3. Also reproduced after: - adding `%JAVA_HOME%\bin` to `PATH`; - renaming the JDK directory to remove `+` from `21.0.12.1+1` (this fixed a different, earlier error; it does not fix this one); - setting `APPDATA` and `XDG_CONFIG_HOME` to a writable directory for the REA process — the config path still resolves under REA's own `rea-ghidra-<hash>\config`; - running the same command through the MCP server instead of the CLI (identical failure). Control: the same Ghidra installation, invoked directly (`support/analyzeHeadless.bat <proj> <name> -import <target>`), imports and auto-analyzes the same PE successfully in about 61 s (`REPORT: Import succeeded`). So the installation, the JDK, and the target are all usable; the failure is specific to how REA launches Ghidra. ### Expected behavior With `GHIDRA_INSTALL_DIR` and `JAVA_HOME` both set, valid, and reported healthy by `rea doctor`, the Ghidra provider should start on Windows x64 without an interactive JDK prompt, and `rea analyze` / `binary_overview` should return analysis instead of `Ghidra exited before bridge startup`. ### Impact and scope The Ghidra provider is unusable on this Windows x64 host for any target, so all native analysis (decompile, function analysis, cross references, call graph) is blocked. Everything else works: `inspect_artifact`, static JS/Electron, managed PE/CLI, Android package parsing, and the capture/pcap tooling all run locally, and the MCP registration works. Impact: P1 for this host, because the failure is not target-specific. Workaround: drive `support/analyzeHeadless.bat` with a Ghidra post-script directly, which works on the same host. Scope: provider launch on Windows only. No change to analysis semantics is requested. Not a duplicate: searching the tracker for "Ghidra exited before bridge startup" returns 12 hits and none reports a Windows `java_home.save` failure. - #3501 (closed) fails earlier with `provider_unavailable / unsupported_host` because a source install lacks `native/windows/build/manifest.json`. Here the npm install ships the native artifact, `rea doctor` reports `ghidra-native_authority` healthy, and the failure is inside Ghidra's own launch support. - #1026 (closed) is a Windows snapshot sharing violation; #4588 is a POSIX AF_UNIX denial. Both are different failure stages and platforms. ### REA version 6.7.0 (npm `rea-agents`). Both the CLI path and the MCP path are affected. ### Runtime and platform Windows 11 x64; Node v24.14.1; Temurin JDK 21.0.12.1+1 (portable full JDK); Ghidra 12.1.4 from the official release zip; non-elevated user. ### Target and artifact context Any PE. Reproduced with a stock 985 KB x86-64 PE executable (sha256 7451fbbf37feffb9bf262bd97c54f0da558c63f0748e64152dd87b0a07b6d6ab). The same file imports and auto-analyzes with direct `analyzeHeadless` in about 61 s. ### Interface CLI ### Earliest failure stage Setup or provider admission ### Relevant output or evidence <details><summary>rea doctor (Ghidra section, trimmed)</summary> ``` ghidra ok healthy <ghidra root> ghidra-platform ok healthy win32 ghidra-architecture ok healthy x64 ghidra-installation ok healthy ...\Ghidra\application.properties ghidra-version ok healthy 12.1.4 ghidra-headless ok healthy ...\support\analyzeHeadless.bat ghidra-native_decompiler ok healthy ...\decompile.exe reports pe x86_64 ghidra-java ok healthy 21.0.12.1; 64-bit; JDK; accepted ghidra-native_authority ok healthy Job Object, private DACL verified ``` </details> <details><summary>Full Ghidra launch stderr</summary> ``` NOTE: Picked up JDK_JAVA_OPTIONS: "-Duser.home=...\rea-ghidra-<hash>\home" "-Djava.io.tmpdir=...\rea-ghidra-<hash>\tmp" java.io.FileNotFoundException: ...\rea-ghidra-<hash>\config\ghidra\ghidra_12.1.4_PUBLIC\java_home.save (Access is denied) at java.base/java.io.FileOutputStream.open0(Native Method) at ghidra.launch.AppConfig.saveJavaHome(AppConfig.java:148) at ghidra.launch.LaunchSupport.findJavaHome(LaunchSupport.java:210) at ghidra.launch.LaunchSupport.handleJavaHome(LaunchSupport.java:153) ``` </details> <details><summary>Direct Ghidra control run (same install, works)</summary> ``` analyzeHeadless.bat <proj> Control -import <target> ... Total Time 61 secs INFO REPORT: Analysis succeeded INFO REPORT: Import succeeded ``` </details> Suspected cause (inferred from the stack, not confirmed by a maintainer): REA creates the per-session runtime root with a hardened DACL, then points Ghidra's config directory at `<runtime_root>\config`. Ghidra's `LaunchSupport` tries to cache the resolved JDK in `java_home.save` under that directory, the write is denied, and it falls back to the interactive JDK prompt. Questions: 1. Is a cached `java_home.save` supposed to be pre-seeded by REA on Windows? 2. Is there a supported way to bypass LaunchSupport's JDK prompt (env var, or a `launch` config that points at `java.exe` directly)? 3. Would pre-creating the config directory writable by the child be acceptable? ### Analysis or suspected cause Suspected cause (source-derived, not confirmed by a maintainer): REA creates the per-session runtime root with a hardened DACL (see the `ghidra-native_authority` doctor check) and points Ghidra's config directory at `<runtime_root>\config`. On the first launch, Ghidra's `LaunchSupport.findJavaHome` resolves the JDK and then caches it in `<config>\ghidra\ghidra_12.1.4_PUBLIC\java_home.save`. That write raises `FileNotFoundException ... (Access is denied)`; LaunchSupport falls back to the interactive prompt, reads EOF and exits 1, so the bridge never starts. Evidence for this ordering: - The captured stderr stack is `AppConfig.saveJavaHome` -> `LaunchSupport.findJavaHome` -> `LaunchSupport.handleJavaHome` -> `LaunchSupport.main`, i.e. the save happens while resolving the JDK, not later. - `JAVA_HOME` is valid for the same REA process: the `ghidra-java` doctor check executes that JDK and accepts `21.0.12.1; 64-bit; JDK`. - Redirecting `APPDATA` / `XDG_CONFIG_HOME` for the REA process does not move the config path; it stays under `rea-ghidra-<hash>\config`, so the path is chosen by REA rather than inherited from the environment. - The identical Ghidra install imports and auto-analyzes the same PE in ~61 s when launched directly, so the JDK, the native decompiler and the target are usable. What would falsify this: a pre-seeded `java_home.save`, or a config directory writable by the launched JVM, still failing with the same prompt. In that case the blocked step is elsewhere in the launch handshake. Not claimed: no maintainer confirmation, and no observed successful Windows bridge start. ### Additional context _No response_
Author
Owner

@XinwangTao commented on GitHub (Oct 25, 2026):

确认这是 Windows 特有路径问题:per-session 运行时目录的硬化 DACL 挡住了 Ghidra LaunchSupport 写 java_home.save,导致它回退到交互式 JDK prompt,stdin 为空即退出。既然控制组(直接跑 analyzeHeadless.bat)在同一 JDK/Ghidra 下成功,说明安装本身没问题,问题集中在 REA 的启动封装。对你的三个问题:(1) java_home.save 由 REA 预置是最直接的修复——launch 前把解析到的 JDK 路径写入该缓存文件,Ghidra 就不会走 prompt 分支;(2) 更稳妥的长期方案是让 REA 在 spawn 子进程前把 config 目录创建为子进程可写(最小权限放宽,只针对该 session 目录),避免依赖 LaunchSupport 内部行为;(3) 预创建可写 config 目录完全可接受,建议同时把它加进 doctor 的 Windows 检查项(探测 java_home.save 可写性),这样用户能在 analyze 之前就发现。另外建议在失败路径上保留当前 stderr 捕获,方便后续定位是 DACL 还是路径解析问题。

<!-- gh-comment-id:6393769316 --> @XinwangTao commented on GitHub (Oct 25, 2026): 确认这是 Windows 特有路径问题:per-session 运行时目录的硬化 DACL 挡住了 Ghidra LaunchSupport 写 java_home.save,导致它回退到交互式 JDK prompt,stdin 为空即退出。既然控制组(直接跑 analyzeHeadless.bat)在同一 JDK/Ghidra 下成功,说明安装本身没问题,问题集中在 REA 的启动封装。对你的三个问题:(1) java_home.save 由 REA 预置是最直接的修复——launch 前把解析到的 JDK 路径写入该缓存文件,Ghidra 就不会走 prompt 分支;(2) 更稳妥的长期方案是让 REA 在 spawn 子进程前把 config 目录创建为子进程可写(最小权限放宽,只针对该 session 目录),避免依赖 LaunchSupport 内部行为;(3) 预创建可写 config 目录完全可接受,建议同时把它加进 doctor 的 Windows 检查项(探测 java_home.save 可写性),这样用户能在 analyze 之前就发现。另外建议在失败路径上保留当前 stderr 捕获,方便后续定位是 DACL 还是路径解析问题。
Author
Owner

@songtiancheng992-prog commented on GitHub (Oct 25, 2026):

Thanks - that matches the evidence, and either (1) pre-seeding java_home.save
or (2) making the per-session config directory writable by the child would
unblock this host.

What I can verify once a build is on npm (next tag), on the same Windows 11 x64
host with Temurin 21.0.12.1 and Ghidra 12.1.4:

  • rea analyze <pe> and binary_overview start the provider instead of
    Ghidra exited before bridge startup;
  • the same three variants that fail today still start: PATH java,
    APPDATA/XDG_CONFIG_HOME redirected, and the MCP path rather than the CLI;
  • rea doctor on Windows reports a check for the config-write step if you add
    one, so a user sees it before analyze.

Two notes from this host that may help:

  • The control run (support/analyzeHeadless.bat -import) succeeds in ~61 s, so
    the JDK, the native decompiler and the target are all fine.
  • REA deletes the session runtime root when the request fails, so ghidra.log
    is gone before I can read it. Keeping stderr on the failure path (as you
    suggested) would let me attach the full log next time.

Happy to test a next build and report back.

<!-- gh-comment-id:6393820324 --> @songtiancheng992-prog commented on GitHub (Oct 25, 2026): Thanks - that matches the evidence, and either (1) pre-seeding `java_home.save` or (2) making the per-session config directory writable by the child would unblock this host. What I can verify once a build is on npm (`next` tag), on the same Windows 11 x64 host with Temurin 21.0.12.1 and Ghidra 12.1.4: - `rea analyze <pe>` and `binary_overview` start the provider instead of `Ghidra exited before bridge startup`; - the same three variants that fail today still start: `PATH` java, `APPDATA`/`XDG_CONFIG_HOME` redirected, and the MCP path rather than the CLI; - `rea doctor` on Windows reports a check for the config-write step if you add one, so a user sees it before `analyze`. Two notes from this host that may help: - The control run (`support/analyzeHeadless.bat -import`) succeeds in ~61 s, so the JDK, the native decompiler and the target are all fine. - REA deletes the session runtime root when the request fails, so `ghidra.log` is gone before I can read it. Keeping stderr on the failure path (as you suggested) would let me attach the full log next time. Happy to test a `next` build and report back.
Author
Owner

@dark13b commented on GitHub (Oct 25, 2026):

I'd like to work on this. #2470 already pre-creates <config>/ghidra/<release> (it is in 6.7.0), so the remaining question is why the launched JVM still cannot write java_home.save inside REA's Windows private runtime. I'll confirm that from source, fix it in the launcher without relying on LaunchSupport internals where possible, and add a regression test. @songtiancheng992-prog thanks for offering to test; I'll ping you here when there is a branch to try on your host.

<!-- gh-comment-id:6396986523 --> @dark13b commented on GitHub (Oct 25, 2026): I'd like to work on this. #2470 already pre-creates `<config>/ghidra/<release>` (it is in 6.7.0), so the remaining question is why the launched JVM still cannot write `java_home.save` inside REA's Windows private runtime. I'll confirm that from source, fix it in the launcher without relying on LaunchSupport internals where possible, and add a regression test. @songtiancheng992-prog thanks for offering to test; I'll ping you here when there is a branch to try on your host.
Author
Owner

@dark13b commented on GitHub (Oct 25, 2026):

I could not reproduce this on Windows 11 x64 with Ghidra 12.1.4 and Temurin 21.0.12.1: published 6.7.0 and 6.8.0 both start Ghidra and analyze a PE. From source, REA's runtime directory grants the current user full inherited control and the child runs with REA's own token, so a same-user write should succeed. Pre-seeding java_home.save would not help either: LaunchSupport.findJavaHome saves immediately after accepting JAVA_HOME (line 210), before it reads any saved file.

Could you run the probe below on the failing host? It recreates REA's directory DACL (current user + SYSTEM only) and runs Ghidra's own LaunchSupport -jdk_home -save against it, without REA. Please paste its output, and say whether REA runs elevated or as a different account, what antivirus or Defender Controlled Folder Access / ASR rules are active (and whether Protection history shows a blocked java.exe), and which drive and filesystem %TEMP% is on.

Probe (PowerShell; needs GHIDRA_INSTALL_DIR and JAVA_HOME; deletes only the directory it creates)
# Mimics REA's Windows private runtime for Ghidra's JDK cache, without REA.
# Requires GHIDRA_INSTALL_DIR and JAVA_HOME. Deletes only the probe directory it creates.
$ErrorActionPreference = 'Stop'
$root = Join-Path $env:TEMP ("rea-7075-probe-" + [guid]::NewGuid().ToString('N').Substring(0, 8))
$props = Get-Content (Join-Path $env:GHIDRA_INSTALL_DIR 'Ghidra\application.properties')
$ver = ($props | Select-String '^application.version=(.*)$').Matches[0].Groups[1].Value
$rel = ($props | Select-String '^application.release.name=(.*)$').Matches[0].Groups[1].Value
$release = Join-Path $root "config\ghidra\ghidra_${ver}_$rel"
New-Item -ItemType Directory -Force $release | Out-Null
$sid = (whoami /user /fo csv | ConvertFrom-Csv).SID
# Protected DACL: current user + SYSTEM, full control, inherited (as REA's native runtime).
icacls $root /inheritance:r /grant:r "*${sid}:(OI)(CI)F" '*S-1-5-18:(OI)(CI)F' /T /Q | Out-Null
try {
  "whoami: $(whoami)"
  whoami /groups | Select-String 'Mandatory Label'
  icacls $release
  $env:APPDATA = Join-Path $root 'config'
  $env:XDG_CONFIG_HOME = $env:APPDATA
  & "$env:JAVA_HOME\bin\java.exe" -cp "$env:GHIDRA_INSTALL_DIR\support\LaunchSupport.jar" LaunchSupport "$env:GHIDRA_INSTALL_DIR" -jdk_home -save
  "LaunchSupport exit: $LASTEXITCODE"
  $saved = Join-Path $release 'java_home.save'
  "java_home.save written: $(Test-Path $saved)"
} finally { Remove-Item -Recurse -Force $root }

Run with powershell -NoProfile -ExecutionPolicy Bypass -File rea-7075-probe.ps1. On my host it prints java_home.save written: True.

<!-- gh-comment-id:6397207213 --> @dark13b commented on GitHub (Oct 25, 2026): I could not reproduce this on Windows 11 x64 with Ghidra 12.1.4 and Temurin 21.0.12.1: published 6.7.0 and 6.8.0 both start Ghidra and analyze a PE. From source, REA's runtime directory grants the current user full inherited control and the child runs with REA's own token, so a same-user write should succeed. Pre-seeding `java_home.save` would not help either: `LaunchSupport.findJavaHome` saves immediately after accepting `JAVA_HOME` (line 210), before it reads any saved file. Could you run the probe below on the failing host? It recreates REA's directory DACL (current user + SYSTEM only) and runs Ghidra's own `LaunchSupport -jdk_home -save` against it, without REA. Please paste its output, and say whether REA runs elevated or as a different account, what antivirus or Defender Controlled Folder Access / ASR rules are active (and whether Protection history shows a blocked `java.exe`), and which drive and filesystem `%TEMP%` is on. <details><summary>Probe (PowerShell; needs GHIDRA_INSTALL_DIR and JAVA_HOME; deletes only the directory it creates)</summary> ```powershell # Mimics REA's Windows private runtime for Ghidra's JDK cache, without REA. # Requires GHIDRA_INSTALL_DIR and JAVA_HOME. Deletes only the probe directory it creates. $ErrorActionPreference = 'Stop' $root = Join-Path $env:TEMP ("rea-7075-probe-" + [guid]::NewGuid().ToString('N').Substring(0, 8)) $props = Get-Content (Join-Path $env:GHIDRA_INSTALL_DIR 'Ghidra\application.properties') $ver = ($props | Select-String '^application.version=(.*)$').Matches[0].Groups[1].Value $rel = ($props | Select-String '^application.release.name=(.*)$').Matches[0].Groups[1].Value $release = Join-Path $root "config\ghidra\ghidra_${ver}_$rel" New-Item -ItemType Directory -Force $release | Out-Null $sid = (whoami /user /fo csv | ConvertFrom-Csv).SID # Protected DACL: current user + SYSTEM, full control, inherited (as REA's native runtime). icacls $root /inheritance:r /grant:r "*${sid}:(OI)(CI)F" '*S-1-5-18:(OI)(CI)F' /T /Q | Out-Null try { "whoami: $(whoami)" whoami /groups | Select-String 'Mandatory Label' icacls $release $env:APPDATA = Join-Path $root 'config' $env:XDG_CONFIG_HOME = $env:APPDATA & "$env:JAVA_HOME\bin\java.exe" -cp "$env:GHIDRA_INSTALL_DIR\support\LaunchSupport.jar" LaunchSupport "$env:GHIDRA_INSTALL_DIR" -jdk_home -save "LaunchSupport exit: $LASTEXITCODE" $saved = Join-Path $release 'java_home.save' "java_home.save written: $(Test-Path $saved)" } finally { Remove-Item -Recurse -Force $root } ``` Run with `powershell -NoProfile -ExecutionPolicy Bypass -File rea-7075-probe.ps1`. On my host it prints `java_home.save written: True`. </details>
Author
Owner

@XinwangTao commented on GitHub (Oct 25, 2026):

This is a real Windows friction point: Ghidra's LaunchSupport writing java_home.save into REA's per-session config dir can fail when the session directory is created with restrictive ACLs (e.g. under a temp path with inherited deny rules or when the parent is read-only). Since Ghidra falls back to an interactive prompt and reads EOF, the failure is opaque. Suggestions: (1) pre-create the session config dir with the current user's write ACL before launching, or set GHIDRA_HOME's support dir as writable; (2) detect the launch-support exit path and surface a clear 'cannot write java_home.save (access denied)' hint with the offending directory; (3) as a workaround doc, let users point GHIDRA_JAVA_HOME/REA to a writable location.

<!-- gh-comment-id:6399837590 --> @XinwangTao commented on GitHub (Oct 25, 2026): This is a real Windows friction point: Ghidra's LaunchSupport writing java_home.save into REA's per-session config dir can fail when the session directory is created with restrictive ACLs (e.g. under a temp path with inherited deny rules or when the parent is read-only). Since Ghidra falls back to an interactive prompt and reads EOF, the failure is opaque. Suggestions: (1) pre-create the session config dir with the current user's write ACL before launching, or set GHIDRA_HOME's support dir as writable; (2) detect the launch-support exit path and surface a clear 'cannot write java_home.save (access denied)' hint with the offending directory; (3) as a workaround doc, let users point GHIDRA_JAVA_HOME/REA to a writable location.
Author
Owner

@morluto commented on GitHub (Oct 26, 2026):

Thank you for the CLI/MCP reproductions and the direct-Ghidra control. #2470 previously pre-created the LaunchSupport release directory and verified a Windows Ghidra run, but your 6.7.0 report is a later failure opening java_home.save itself. That earlier fix therefore does not establish this report is resolved. I am keeping it open for a current Windows reproduction and investigation of the file-write/ownership boundary; no Windows provider run was performed in this triage.

<!-- gh-comment-id:6428669498 --> @morluto commented on GitHub (Oct 26, 2026): Thank you for the CLI/MCP reproductions and the direct-Ghidra control. #2470 previously pre-created the LaunchSupport release directory and verified a Windows Ghidra run, but your 6.7.0 report is a later failure opening `java_home.save` itself. That earlier fix therefore does not establish this report is resolved. I am keeping it open for a current Windows reproduction and investigation of the file-write/ownership boundary; no Windows provider run was performed in this triage.
Author
Owner

@XinwangTao commented on GitHub (Oct 27, 2026):

支持保持开放——dark13b 无法复现但报告者确凿失败,差异很可能在 Windows 上下文而非版本:交互式控制台会话与计划任务/服务/提升 token 下的 DACL 继承规则不同,access denied 往往只在特定上下文复现。建议在失败路径上把 Ghidra 的 stderr 与 java_home.save 的 CreateFile 错误码(Win32 error)一并捕获进日志,并在 launch 前做一个显式的可写性 probe(对目标路径 CreateFile 试写再删除),把 probe 结果和错误码贴回本 issue,这样跨环境比对才能定位是 ACL 继承还是路径重定向问题。预置 java_home.save 可作临时 workaround,但不能作为修复依据,因为 LaunchSupport 在接受 JAVA_HOME 后立即重写该文件。

<!-- gh-comment-id:6431150794 --> @XinwangTao commented on GitHub (Oct 27, 2026): 支持保持开放——dark13b 无法复现但报告者确凿失败,差异很可能在 Windows 上下文而非版本:交互式控制台会话与计划任务/服务/提升 token 下的 DACL 继承规则不同,access denied 往往只在特定上下文复现。建议在失败路径上把 Ghidra 的 stderr 与 java_home.save 的 CreateFile 错误码(Win32 error)一并捕获进日志,并在 launch 前做一个显式的可写性 probe(对目标路径 CreateFile 试写再删除),把 probe 结果和错误码贴回本 issue,这样跨环境比对才能定位是 ACL 继承还是路径重定向问题。预置 java_home.save 可作临时 workaround,但不能作为修复依据,因为 LaunchSupport 在接受 JAVA_HOME 后立即重写该文件。
Author
Owner

@songtiancheng992-prog commented on GitHub (Oct 30, 2026):

Follow-up: the probe reproduces, but further isolation shows the blocker is not
REA's DACL and not antivirus. Please disregard my earlier AV guess.

Probe output (trimmed):

whoami: <user>
Mandatory Label\Medium Mandatory Level
...\rea-7075-probe-8980b0fb\config\ghidra\ghidra_12.1.4_PUBLIC
    NT AUTHORITY\SYSTEM:(OI)(CI)(F)
    <user>:(OI)(CI)(F)
LaunchSupport exit: 1
java_home.save written: False
java.io.FileNotFoundException: ...\java_home.save (Access is denied)

Minimal repro without REA or Ghidra - a 5-line FileWriter program:

target JVM cmd / .NET
%TEMP%\x.txt AccessDenied OK
%TEMP%\subdir\x.txt AccessDenied OK
%USERPROFILE%\Documents\x.txt AccessDenied OK
%USERPROFILE%\x.txt AccessDenied OK
D:\x.txt AccessDenied OK
%LOCALAPPDATA%\x.txt AccessDenied OK
a directory inside the agent workspace OK OK

Additional controls:

  • copying java.exe to javaX.exe in the same directory and rerunning: still
    denied, so it is not image-name based;
  • Kingsoft (kxescore, kxewsc) services are stopped, no kx kernel driver
    loaded; Defender EnableControlledFolderAccess = 0, ASR rule count = 0;
  • non-elevated, Medium integrity; %TEMP% on C: (NTFS).

So on this host REA runs inside a sandboxed agent that allows the shell and .NET
but denies arbitrary child executables any write outside the agent workspace. The
JVM is such a child, which is why Ghidra's LaunchSupport cannot create
java_home.save. It reproduces naturally outside any REA code path.

Workaround that makes the provider usable (verified on Ghidra 12.1.4 + Temurin
21.0.12.1, same host): point TMP/TEMP at a directory inside the allowed tree,
since REA resolves its session runtime root through os.tmpdir():

  • rea analyze <pe> -> success, 85 s
  • rea decompile <pe> --procedure 0x140001010 -> Ghidra pseudocode, 71 s
  • MCP binary_overview -> 12 segments, 1248 procedures, 1914 strings

Suggestions:

  1. Document that the session runtime root follows os.tmpdir(), and that hosts
    with restricted filesystem access must set TMP/TEMP to a writable path;
    or expose an explicit override such as REA_RUNTIME_ROOT.
  2. On the failure path, include the offending directory and the CreateFile
    error code in the message (today it is just <hash> (Access is denied)).
  3. Happy to test any branch on this host - the repro is deterministic.
<!-- gh-comment-id:6532665328 --> @songtiancheng992-prog commented on GitHub (Oct 30, 2026): Follow-up: the probe reproduces, but further isolation shows the blocker is **not** REA's DACL and **not** antivirus. Please disregard my earlier AV guess. Probe output (trimmed): ``` whoami: <user> Mandatory Label\Medium Mandatory Level ...\rea-7075-probe-8980b0fb\config\ghidra\ghidra_12.1.4_PUBLIC NT AUTHORITY\SYSTEM:(OI)(CI)(F) <user>:(OI)(CI)(F) LaunchSupport exit: 1 java_home.save written: False java.io.FileNotFoundException: ...\java_home.save (Access is denied) ``` Minimal repro without REA or Ghidra - a 5-line `FileWriter` program: | target | JVM | cmd / .NET | | --- | --- | --- | | `%TEMP%\x.txt` | AccessDenied | OK | | `%TEMP%\subdir\x.txt` | AccessDenied | OK | | `%USERPROFILE%\Documents\x.txt` | AccessDenied | OK | | `%USERPROFILE%\x.txt` | AccessDenied | OK | | `D:\x.txt` | AccessDenied | OK | | `%LOCALAPPDATA%\x.txt` | AccessDenied | OK | | a directory inside the agent workspace | OK | OK | Additional controls: - copying `java.exe` to `javaX.exe` in the same directory and rerunning: still denied, so it is not image-name based; - Kingsoft (`kxescore`, `kxewsc`) services are **stopped**, no kx kernel driver loaded; Defender `EnableControlledFolderAccess` = 0, ASR rule count = 0; - non-elevated, Medium integrity; `%TEMP%` on C: (NTFS). So on this host REA runs inside a sandboxed agent that allows the shell and .NET but denies arbitrary child executables any write outside the agent workspace. The JVM is such a child, which is why Ghidra's LaunchSupport cannot create `java_home.save`. It reproduces naturally outside any REA code path. Workaround that makes the provider usable (verified on Ghidra 12.1.4 + Temurin 21.0.12.1, same host): point `TMP`/`TEMP` at a directory inside the allowed tree, since REA resolves its session runtime root through `os.tmpdir()`: - `rea analyze <pe>` -> success, 85 s - `rea decompile <pe> --procedure 0x140001010` -> Ghidra pseudocode, 71 s - MCP `binary_overview` -> 12 segments, 1248 procedures, 1914 strings Suggestions: 1. Document that the session runtime root follows `os.tmpdir()`, and that hosts with restricted filesystem access must set `TMP`/`TEMP` to a writable path; or expose an explicit override such as `REA_RUNTIME_ROOT`. 2. On the failure path, include the offending directory and the `CreateFile` error code in the message (today it is just `<hash> (Access is denied)`). 3. Happy to test any branch on this host - the repro is deterministic.
Author
Owner

@XinwangTao commented on GitHub (Oct 31, 2026):

The isolation result is convincing — with the 5-line FileWriter repro failing across every location outside the agent workspace while .NET succeeds, this is clearly a sandbox write-restriction on child executables rather than anything REA or Ghidra controls. Pointing TMP/TEMP inside the allowed tree as the workaround is pragmatic and matches the os.tmpdir() resolution path. I'd support documenting that behavior and adding an explicit REA_RUNTIME_ROOT override, plus including the offending directory and CreateFile error code in the failure message — all three suggestions are low-cost and would save the next affected user a long debugging session.

<!-- gh-comment-id:6536910548 --> @XinwangTao commented on GitHub (Oct 31, 2026): The isolation result is convincing — with the 5-line FileWriter repro failing across every location outside the agent workspace while .NET succeeds, this is clearly a sandbox write-restriction on child executables rather than anything REA or Ghidra controls. Pointing TMP/TEMP inside the allowed tree as the workaround is pragmatic and matches the os.tmpdir() resolution path. I'd support documenting that behavior and adding an explicit REA_RUNTIME_ROOT override, plus including the offending directory and CreateFile error code in the failure message — all three suggestions are low-cost and would save the next affected user a long debugging session.
Author
Owner

@songtiancheng992-prog commented on GitHub (Oct 31, 2026):

Thanks - one data point, because it makes this a broader class than Ghidra.

The Win32 code behind both messages is 5 (ERROR_ACCESS_DENIED). On the same host a
second REA subsystem hits the same class of denial: analyze-javascript-application
fails with

EPERM: operation not permitted, open '...\rea-javascript-analysis-<hash>\<uuid>.source'

even when the temp root is already inside the allowed tree. So two independent
child processes (the JVM and the JS analyzer worker) are denied filesystem
operations on this host; the Ghidra path just surfaces it as the opaque
java_home.save failure.

That supports your three suggestions. I would add a fourth: when the launch or
transfer step returns ERROR_ACCESS_DENIED, print the path, code 5, and a hint
that restricted/sandboxed hosts need TMP/TEMP (and any future
REA_RUNTIME_ROOT) inside the writable tree - that alone would have saved me
this debugging session.

Still happy to test a branch; the repro is deterministic on 6.7.0 here.

<!-- gh-comment-id:6540398783 --> @songtiancheng992-prog commented on GitHub (Oct 31, 2026): Thanks - one data point, because it makes this a broader class than Ghidra. The Win32 code behind both messages is 5 (ERROR_ACCESS_DENIED). On the same host a second REA subsystem hits the same class of denial: `analyze-javascript-application` fails with ``` EPERM: operation not permitted, open '...\rea-javascript-analysis-<hash>\<uuid>.source' ``` even when the temp root is already inside the allowed tree. So two independent child processes (the JVM and the JS analyzer worker) are denied filesystem operations on this host; the Ghidra path just surfaces it as the opaque `java_home.save` failure. That supports your three suggestions. I would add a fourth: when the launch or transfer step returns ERROR_ACCESS_DENIED, print the path, code 5, and a hint that restricted/sandboxed hosts need `TMP`/`TEMP` (and any future `REA_RUNTIME_ROOT`) inside the writable tree - that alone would have saved me this debugging session. Still happy to test a branch; the repro is deterministic on 6.7.0 here.
Author
Owner

@XinwangTao commented on GitHub (Nov 4, 2026):

这个补充把问题从「Ghidra 专属」提升到「子进程 temp 写入被 sandbox 拒」这一通用类别了——JVM 和 JS analyzer worker 两条独立路径都撞上同一种 denial,说明修复应该放在运行时统一层而不是 provider 层。第四条建议(ERROR_ACCESS_DENIED 时打印 path + code 5 + 受限主机提示)很对,我再补一点:rea doctor 目前对 Ghidra 全绿反而误导排障,建议在 doctor(或运行时启动自检)里加一个轻量探针——在 os.tmpdir() 下尝试写入/删除一个临时文件并报告结果,这样受限主机在跑分析前就能直接看到「temp 不可写」的明确警告,而不是等 85 秒后从 opaque 错误里猜。

<!-- gh-comment-id:6622396067 --> @XinwangTao commented on GitHub (Nov 4, 2026): 这个补充把问题从「Ghidra 专属」提升到「子进程 temp 写入被 sandbox 拒」这一通用类别了——JVM 和 JS analyzer worker 两条独立路径都撞上同一种 denial,说明修复应该放在运行时统一层而不是 provider 层。第四条建议(ERROR_ACCESS_DENIED 时打印 path + code 5 + 受限主机提示)很对,我再补一点:rea doctor 目前对 Ghidra 全绿反而误导排障,建议在 doctor(或运行时启动自检)里加一个轻量探针——在 os.tmpdir() 下尝试写入/删除一个临时文件并报告结果,这样受限主机在跑分析前就能直接看到「temp 不可写」的明确警告,而不是等 85 秒后从 opaque 错误里猜。
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#4264
No description provided.