diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index bb7f7d9995b..23deda915ed 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -922,6 +922,7 @@ dependencies = [ "rsa", "russh-cryptovec", "scopeguard", + "secmem-proc", "security-framework", "security-framework-sys", "sha2", @@ -2769,6 +2770,16 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "rustix-linux-procfs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc84bf7e9aa16c4f2c758f27412dc9841341e16aa682d9c7ac308fe3ee12056" +dependencies = [ + "once_cell", + "rustix 1.0.7", +] + [[package]] name = "rustversion" version = "1.0.20" @@ -2847,6 +2858,21 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secmem-proc" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473559b1d28f530c3a9b5f91a2866053e2b1c528a0e43dae83048139c99490c2" +dependencies = [ + "anyhow", + "cfg-if", + "libc", + "rustix 1.0.7", + "rustix-linux-procfs", + "thiserror 2.0.12", + "windows 0.61.1", +] + [[package]] name = "security-framework" version = "3.1.0" diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index 84b835de35f..7e5ab43c6b9 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -48,6 +48,7 @@ rand = "=0.9.1" rsa = "=0.9.6" russh-cryptovec = "=0.7.3" scopeguard = "=1.2.0" +secmem-proc = "=0.3.7" security-framework = "=3.1.0" security-framework-sys = "=2.13.0" serde = "=1.0.209" diff --git a/apps/desktop/desktop_native/core/Cargo.toml b/apps/desktop/desktop_native/core/Cargo.toml index 97189e1d7cd..3aedf90613e 100644 --- a/apps/desktop/desktop_native/core/Cargo.toml +++ b/apps/desktop/desktop_native/core/Cargo.toml @@ -38,6 +38,7 @@ rand = { workspace = true } rsa = { workspace = true } russh-cryptovec = { workspace = true } scopeguard = { workspace = true } +secmem-proc = { workspace = true } sha2 = { workspace = true } ssh-encoding = { workspace = true } ssh-key = { workspace = true, features = [ diff --git a/apps/desktop/desktop_native/core/src/process_isolation/linux.rs b/apps/desktop/desktop_native/core/src/process_isolation/linux.rs index dc027e0b546..395d722ea01 100644 --- a/apps/desktop/desktop_native/core/src/process_isolation/linux.rs +++ b/apps/desktop/desktop_native/core/src/process_isolation/linux.rs @@ -20,6 +20,8 @@ pub fn disable_coredumps() -> Result<()> { rlim_cur: 0, rlim_max: 0, }; + println!("[Process Isolation] Disabling core dumps via setrlimit"); + if unsafe { libc::setrlimit(RLIMIT_CORE, &rlimit) } != 0 { let e = std::io::Error::last_os_error(); return Err(anyhow::anyhow!( @@ -44,11 +46,17 @@ pub fn is_core_dumping_disabled() -> Result { Ok(rlimit.rlim_cur == 0 && rlimit.rlim_max == 0) } -pub fn disable_memory_access() -> Result<()> { +pub fn isolate_process() -> Result<()> { + let pid = std::process::id(); + println!( + "[Process Isolation] Disabling ptrace and memory access for main ({}) via PR_SET_DUMPABLE", + pid + ); + if unsafe { libc::prctl(PR_SET_DUMPABLE, 0) } != 0 { let e = std::io::Error::last_os_error(); return Err(anyhow::anyhow!( - "failed to disable memory dumping, memory is dumpable by other processes {}", + "failed to disable memory dumping, memory may be accessible by other processes {}", e )); } diff --git a/apps/desktop/desktop_native/core/src/process_isolation/macos.rs b/apps/desktop/desktop_native/core/src/process_isolation/macos.rs index 04d8f7266c4..ce42e06a832 100644 --- a/apps/desktop/desktop_native/core/src/process_isolation/macos.rs +++ b/apps/desktop/desktop_native/core/src/process_isolation/macos.rs @@ -8,6 +8,17 @@ pub fn is_core_dumping_disabled() -> Result { bail!("Not implemented on Mac") } -pub fn disable_memory_access() -> Result<()> { - bail!("Not implemented on Mac") +pub fn isolate_process() -> Result<()> { + let pid: u32 = std::process::id(); + println!( + "[Process Isolation] Disabling ptrace on main process ({}) via PT_DENY_ATTACH", + pid + ); + + secmem_proc::harden_process().map_err(|e| { + anyhow::anyhow!( + "failed to disable memory dumping, memory may be accessible by other processes {}", + e + ) + }) } diff --git a/apps/desktop/desktop_native/core/src/process_isolation/mod.rs b/apps/desktop/desktop_native/core/src/process_isolation/mod.rs index 30f4dbf689a..b1872c8a423 100644 --- a/apps/desktop/desktop_native/core/src/process_isolation/mod.rs +++ b/apps/desktop/desktop_native/core/src/process_isolation/mod.rs @@ -1,3 +1,16 @@ +//! This module implements process isolation, which aims to protect +//! a process from dumping memory to disk when crashing, and from +//! userspace memory access. +//! +//! On Windows, by default most userspace apps can read the memory of all +//! other apps, and attach debuggers. On Mac, this is not possible, and only +//! after granting developer permissions can an app attach to processes via +//! ptrace / read memory. On Linux, this depends on the distro / configuration of yama +//! `https://linux-audit.com/protect-ptrace-processes-kernel-yama-ptrace_scope/` +//! For instance, ubuntu prevents ptrace of other processes by default. +//! On Fedora, there are change proposals but ptracing is still possible unless +//! otherwise configured. + #[allow(clippy::module_inception)] #[cfg_attr(target_os = "linux", path = "linux.rs")] #[cfg_attr(target_os = "windows", path = "windows.rs")] diff --git a/apps/desktop/desktop_native/core/src/process_isolation/windows.rs b/apps/desktop/desktop_native/core/src/process_isolation/windows.rs index 7c7864fbbd7..dc1092f9131 100644 --- a/apps/desktop/desktop_native/core/src/process_isolation/windows.rs +++ b/apps/desktop/desktop_native/core/src/process_isolation/windows.rs @@ -8,6 +8,17 @@ pub fn is_core_dumping_disabled() -> Result { bail!("Not implemented on Windows") } -pub fn disable_memory_access() -> Result<()> { - bail!("Not implemented on Windows") +pub fn isolate_process() -> Result<()> { + let pid: u32 = std::process::id(); + println!( + "[Process Isolation] Isolating main process via DACL {}", + pid + ); + + secmem_proc::harden_process().map_err(|e| { + anyhow::anyhow!( + "failed to isolate process, memory may be accessible by other processes {}", + e + ) + }) } diff --git a/apps/desktop/desktop_native/napi/index.d.ts b/apps/desktop/desktop_native/napi/index.d.ts index a920f0c00aa..281bfd5d69f 100644 --- a/apps/desktop/desktop_native/napi/index.d.ts +++ b/apps/desktop/desktop_native/napi/index.d.ts @@ -82,7 +82,7 @@ export declare namespace sshagent { export declare namespace processisolations { export function disableCoredumps(): Promise export function isCoreDumpingDisabled(): Promise - export function disableMemoryAccess(): Promise + export function isolateProcess(): Promise } export declare namespace powermonitors { export function onLock(callback: (err: Error | null, ) => any): Promise diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs index 1f99c1c3ed2..0e5cdc838d7 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -337,8 +337,8 @@ pub mod processisolations { #[allow(clippy::unused_async)] // FIXME: Remove unused async! #[napi] - pub async fn disable_memory_access() -> napi::Result<()> { - desktop_core::process_isolation::disable_memory_access() + pub async fn isolate_process() -> napi::Result<()> { + desktop_core::process_isolation::isolate_process() .map_err(|e| napi::Error::from_reason(e.to_string())) } } diff --git a/apps/desktop/src/main/window.main.ts b/apps/desktop/src/main/window.main.ts index 5b81cf8140b..1595252251b 100644 --- a/apps/desktop/src/main/window.main.ts +++ b/apps/desktop/src/main/window.main.ts @@ -36,7 +36,7 @@ export class WindowMain { private windowStateChangeTimer: NodeJS.Timeout; private windowStates: { [key: string]: WindowState } = {}; private enableAlwaysOnTop = false; - private enableRendererProcessForceCrashReload = false; + private enableRendererProcessForceCrashReload = true; session: Electron.Session; readonly defaultWidth = 950; @@ -149,28 +149,31 @@ export class WindowMain { // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. app.on("ready", async () => { - if (isMac() || isWindows()) { - this.enableRendererProcessForceCrashReload = true; - } else if (isLinux() && !isDev()) { - if (await processisolations.isCoreDumpingDisabled()) { - this.logService.info("Coredumps are disabled in renderer process"); - this.enableRendererProcessForceCrashReload = true; - } else { - this.logService.info("Disabling coredumps in main process"); + if (!isDev()) { + // This currently breaks the file portal for snap https://github.com/flatpak/xdg-desktop-portal/issues/785 + if (!isSnapStore()) { + this.logService.info( + "[Process Isolation] Isolating process from debuggers and memory dumps", + ); try { - await processisolations.disableCoredumps(); + await processisolations.isolateProcess(); } catch (e) { - this.logService.error("Failed to disable coredumps", e); + this.logService.error("[Process Isolation] Failed to isolate main process", e); } } - // this currently breaks the file portal for snap https://github.com/flatpak/xdg-desktop-portal/issues/785 - if (!isSnapStore()) { - this.logService.info("Disabling memory dumps in main process"); - try { - await processisolations.disableMemoryAccess(); - } catch (e) { - this.logService.error("Failed to disable memory dumps", e); + if (isLinux()) { + if (await processisolations.isCoreDumpingDisabled()) { + this.logService.info("Coredumps are disabled in renderer process"); + } else { + this.enableRendererProcessForceCrashReload = false; + this.logService.info("Disabling coredumps in main process"); + try { + await processisolations.disableCoredumps(); + this.enableRendererProcessForceCrashReload = true; + } catch (e) { + this.logService.error("Failed to disable coredumps", e); + } } } }