Skip to content

fix(desktop): hand helper-created runtime files to the GUI owner - #220

Merged
loss-and-quick merged 1 commit into
mainfrom
fix/helper-file-ownership
Jul 14, 2026
Merged

fix(desktop): hand helper-created runtime files to the GUI owner#220
loss-and-quick merged 1 commit into
mainfrom
fix/helper-file-ownership

Conversation

@loss-and-quick

Copy link
Copy Markdown
Owner

Summary

On Linux the privileged data-path helper (pkexec root, or the packaged caps wrapper) creates runtime files that end up root-owned: the ephemeral run-dir state (core.pid, service-state, tun-engine, …), the generated engine configs (hev.yml, tun2socks.yml, singbox-bridge.json), the core logs under datadir (xray.log, singbox.log, tun-engine.log), the helper's own log, and per-test-core logs. Hand their ownership to the unprivileged GUI user after every file-creating request.

This is standalone hardening (the GUI user can rotate/remove its own logs and state) and the precondition for the follow-up that skips the helper entirely in the non-tun proxy modes: an unprivileged in-process data-path owner must be able to read and replace these files without silent EACCES.

Mechanics:

  • A sweep, not per-write-site sprinkling: Server::dispatch classifies each request (Handoff::for_request) before consuming it, and after StartDataPath/StopDataPath chowns the fixed path list to the owner; after SpawnTestCore it chowns just that request's log (its port is dynamic).
  • The path list is a pure function on DesktopPaths (helper_owned_files) — every entry derived from a paths field, no hand-built strings; GUI-written inputs (engine, the built core configs, daemon.log) are excluded and the exclusion is test-pinned.
  • Directory inodes too, still non-recursive: chowning files alone grants content-write, but create/unlink live on the containing directory — and run_dir is created root-side at boot. helper_owned_dirs hands over exactly two inodes: run_dir itself and its kasumi-proxy namespace parent (only when it is ours by name). Nothing beneath them is swept; datadir's inode is never touched (the GUI creates it).
  • The sweep is a no-op when owner_uid is unset or equals the helper's own euid (hand_off_target) — the packaged caps-only wrapper already runs as the GUI user, so it never chowns needlessly.
  • The uid was already crossing the privilege boundary (--owner-uid, used to chown the control socket); no new plumbing.
  • Windows: untouched (files under the user's %LOCALAPPDATA% inherit ACLs from the parent; there is no service-path gating plan). All sweep machinery is cfg(target_os = "linux").

Affected layer

  • frontend/ — React Web UI
  • crates/ · src-tauri/ — Rust core / backend / Tauri desktop
  • module/ — Android installable zip (thin launcher over the Rust daemon)
  • scripts/ — build / release helpers
  • CI / .github/
  • Docs only

Verification

Rust (crates/ · src-tauri/):

  • cargo fmt --all --check
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo test --workspace — one failure: net::tests::tcp_ping_fails_on_unreachable_host, the known environment flake (an active VPN tun on the dev machine captures the TEST-NET-1 connect); fails identically on main
  • No frontend/codegen changes

New tests: the hand-off list contains exactly the helper-written names and none of the GUI-written ones, plus the directory list (run_dir + namespace parent, never datadir); hand_off_target no-ops for a same-user run; Handoff::for_request classifies the file-creating requests.

Checklist

  • Title is a scoped Conventional Commit; commits are logically split
  • No build artifacts committed (module/bin/<abi>/, geoip/geosite, built module/webroot/, src-tauri/gen/ — all gitignored on purpose)
  • Generated frontend/src/generated/ was regenerated from Rust, not hand-edited (untouched)
  • If user-visible strings changed: i18n/en.ts and every locale file updated (no user-visible strings changed)
  • Renames touching the project id were grepped in all case forms (kasumi-proxy, Kasumi Proxy, camelCase)

Notes for reviewers

  • An actual-chown test only means something as root, so the sweep body is kept trivial and the tested surface is the pure parts (path list, uid predicate, request classification).
  • hand_files_to_owner re-resolves DesktopPaths per Start/Stop (low frequency); the per-test-core path deliberately skips that resolve.
  • The helper-log leaf name moved into a shared HELPER_LOG_FILE const so the writer (hlog) and the sweep can't drift apart.

The privileged Linux data-path helper (root or caps-only) writes its
run-dir state, generated engine configs, core logs and its own log as
root. Sweep those known files back to the GUI owner uid after each
file-creating request so a future unprivileged in-process owner can
operate over the same paths without silent EACCES.

The hand-off is a fixed list derived from DesktopPaths/BackendPaths
fields (plus the test core's per-request log path), chowned only when an
owner uid is set and differs from the helper's own euid, so a caps-only
wrapper already running as the user stays a no-op. Ownership only, exact
regular-file paths, never a directory.
@github-actions github-actions Bot added platform:windows Windows desktop (Tauri) backend Rust core/backend/desktop (crates/, src-tauri/) labels Jul 14, 2026
@loss-and-quick
loss-and-quick merged commit e52c516 into main Jul 14, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend Rust core/backend/desktop (crates/, src-tauri/) platform:windows Windows desktop (Tauri)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant