1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-10 05:13:29 +00:00

[BEEEP | PM-25358] Add process isolation on windows and mac desktop main process (#16156)

* Prevent memory dumping and debugger on windows and mac main process

* Fix clippy

* Only isolate process when isdev is false

* Clean up

* Add backticks around link
This commit is contained in:
Bernd Schoolmann
2025-09-04 21:40:25 +02:00
committed by GitHub
parent ca9b531571
commit ea1c3252e8
10 changed files with 101 additions and 27 deletions

View File

@@ -922,6 +922,7 @@ dependencies = [
"rsa", "rsa",
"russh-cryptovec", "russh-cryptovec",
"scopeguard", "scopeguard",
"secmem-proc",
"security-framework", "security-framework",
"security-framework-sys", "security-framework-sys",
"sha2", "sha2",
@@ -2769,6 +2770,16 @@ dependencies = [
"windows-sys 0.59.0", "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]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.20" version = "1.0.20"
@@ -2847,6 +2858,21 @@ dependencies = [
"zeroize", "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]] [[package]]
name = "security-framework" name = "security-framework"
version = "3.1.0" version = "3.1.0"

View File

@@ -48,6 +48,7 @@ rand = "=0.9.1"
rsa = "=0.9.6" rsa = "=0.9.6"
russh-cryptovec = "=0.7.3" russh-cryptovec = "=0.7.3"
scopeguard = "=1.2.0" scopeguard = "=1.2.0"
secmem-proc = "=0.3.7"
security-framework = "=3.1.0" security-framework = "=3.1.0"
security-framework-sys = "=2.13.0" security-framework-sys = "=2.13.0"
serde = "=1.0.209" serde = "=1.0.209"

View File

@@ -38,6 +38,7 @@ rand = { workspace = true }
rsa = { workspace = true } rsa = { workspace = true }
russh-cryptovec = { workspace = true } russh-cryptovec = { workspace = true }
scopeguard = { workspace = true } scopeguard = { workspace = true }
secmem-proc = { workspace = true }
sha2 = { workspace = true } sha2 = { workspace = true }
ssh-encoding = { workspace = true } ssh-encoding = { workspace = true }
ssh-key = { workspace = true, features = [ ssh-key = { workspace = true, features = [

View File

@@ -20,6 +20,8 @@ pub fn disable_coredumps() -> Result<()> {
rlim_cur: 0, rlim_cur: 0,
rlim_max: 0, rlim_max: 0,
}; };
println!("[Process Isolation] Disabling core dumps via setrlimit");
if unsafe { libc::setrlimit(RLIMIT_CORE, &rlimit) } != 0 { if unsafe { libc::setrlimit(RLIMIT_CORE, &rlimit) } != 0 {
let e = std::io::Error::last_os_error(); let e = std::io::Error::last_os_error();
return Err(anyhow::anyhow!( return Err(anyhow::anyhow!(
@@ -44,11 +46,17 @@ pub fn is_core_dumping_disabled() -> Result<bool> {
Ok(rlimit.rlim_cur == 0 && rlimit.rlim_max == 0) 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 { if unsafe { libc::prctl(PR_SET_DUMPABLE, 0) } != 0 {
let e = std::io::Error::last_os_error(); let e = std::io::Error::last_os_error();
return Err(anyhow::anyhow!( 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 e
)); ));
} }

View File

@@ -8,6 +8,17 @@ pub fn is_core_dumping_disabled() -> Result<bool> {
bail!("Not implemented on Mac") bail!("Not implemented on Mac")
} }
pub fn disable_memory_access() -> Result<()> { pub fn isolate_process() -> Result<()> {
bail!("Not implemented on Mac") 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
)
})
} }

View File

@@ -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)] #[allow(clippy::module_inception)]
#[cfg_attr(target_os = "linux", path = "linux.rs")] #[cfg_attr(target_os = "linux", path = "linux.rs")]
#[cfg_attr(target_os = "windows", path = "windows.rs")] #[cfg_attr(target_os = "windows", path = "windows.rs")]

View File

@@ -8,6 +8,17 @@ pub fn is_core_dumping_disabled() -> Result<bool> {
bail!("Not implemented on Windows") bail!("Not implemented on Windows")
} }
pub fn disable_memory_access() -> Result<()> { pub fn isolate_process() -> Result<()> {
bail!("Not implemented on Windows") 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
)
})
} }

View File

@@ -82,7 +82,7 @@ export declare namespace sshagent {
export declare namespace processisolations { export declare namespace processisolations {
export function disableCoredumps(): Promise<void> export function disableCoredumps(): Promise<void>
export function isCoreDumpingDisabled(): Promise<boolean> export function isCoreDumpingDisabled(): Promise<boolean>
export function disableMemoryAccess(): Promise<void> export function isolateProcess(): Promise<void>
} }
export declare namespace powermonitors { export declare namespace powermonitors {
export function onLock(callback: (err: Error | null, ) => any): Promise<void> export function onLock(callback: (err: Error | null, ) => any): Promise<void>

View File

@@ -337,8 +337,8 @@ pub mod processisolations {
#[allow(clippy::unused_async)] // FIXME: Remove unused async! #[allow(clippy::unused_async)] // FIXME: Remove unused async!
#[napi] #[napi]
pub async fn disable_memory_access() -> napi::Result<()> { pub async fn isolate_process() -> napi::Result<()> {
desktop_core::process_isolation::disable_memory_access() desktop_core::process_isolation::isolate_process()
.map_err(|e| napi::Error::from_reason(e.to_string())) .map_err(|e| napi::Error::from_reason(e.to_string()))
} }
} }

View File

@@ -36,7 +36,7 @@ export class WindowMain {
private windowStateChangeTimer: NodeJS.Timeout; private windowStateChangeTimer: NodeJS.Timeout;
private windowStates: { [key: string]: WindowState } = {}; private windowStates: { [key: string]: WindowState } = {};
private enableAlwaysOnTop = false; private enableAlwaysOnTop = false;
private enableRendererProcessForceCrashReload = false; private enableRendererProcessForceCrashReload = true;
session: Electron.Session; session: Electron.Session;
readonly defaultWidth = 950; readonly defaultWidth = 950;
@@ -149,28 +149,31 @@ export class WindowMain {
// initialization and is ready to create browser windows. // initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs. // Some APIs can only be used after this event occurs.
app.on("ready", async () => { app.on("ready", async () => {
if (isMac() || isWindows()) { if (!isDev()) {
this.enableRendererProcessForceCrashReload = true; // This currently breaks the file portal for snap https://github.com/flatpak/xdg-desktop-portal/issues/785
} else if (isLinux() && !isDev()) { if (!isSnapStore()) {
if (await processisolations.isCoreDumpingDisabled()) { this.logService.info(
this.logService.info("Coredumps are disabled in renderer process"); "[Process Isolation] Isolating process from debuggers and memory dumps",
this.enableRendererProcessForceCrashReload = true; );
} else {
this.logService.info("Disabling coredumps in main process");
try { try {
await processisolations.disableCoredumps(); await processisolations.isolateProcess();
} catch (e) { } 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 (isLinux()) {
if (!isSnapStore()) { if (await processisolations.isCoreDumpingDisabled()) {
this.logService.info("Disabling memory dumps in main process"); this.logService.info("Coredumps are disabled in renderer process");
try { } else {
await processisolations.disableMemoryAccess(); this.enableRendererProcessForceCrashReload = false;
} catch (e) { this.logService.info("Disabling coredumps in main process");
this.logService.error("Failed to disable memory dumps", e); try {
await processisolations.disableCoredumps();
this.enableRendererProcessForceCrashReload = true;
} catch (e) {
this.logService.error("Failed to disable coredumps", e);
}
} }
} }
} }