From dec1c55e942f797aa6239a5b66fad16336d6c5f1 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Sun, 24 Aug 2025 04:47:16 +0200 Subject: [PATCH] Implement libmemory_security --- apps/desktop/.gitignore | 1 + apps/desktop/memory_security/Cargo.lock | 48 +++++++++++++++++++ apps/desktop/memory_security/Cargo.toml | 11 +++++ apps/desktop/memory_security/src/isolate.rs | 39 +++++++++++++++ apps/desktop/memory_security/src/lib.rs | 42 ++++++++++++++++ .../com.bitwarden.desktop.devel.yaml | 2 + apps/desktop/resources/linux-wrapper.sh | 13 +++-- apps/desktop/scripts/after-pack.js | 11 +++++ 8 files changed, 164 insertions(+), 3 deletions(-) create mode 100644 apps/desktop/memory_security/Cargo.lock create mode 100644 apps/desktop/memory_security/Cargo.toml create mode 100644 apps/desktop/memory_security/src/isolate.rs create mode 100644 apps/desktop/memory_security/src/lib.rs diff --git a/apps/desktop/.gitignore b/apps/desktop/.gitignore index 444c9a85100..dd720fb3d54 100644 --- a/apps/desktop/.gitignore +++ b/apps/desktop/.gitignore @@ -3,3 +3,4 @@ dist-safari/ *.env PlugIns/safari.appex/ xcuserdata/ +memory_security/target/ \ No newline at end of file diff --git a/apps/desktop/memory_security/Cargo.lock b/apps/desktop/memory_security/Cargo.lock new file mode 100644 index 00000000000..35de8a22214 --- /dev/null +++ b/apps/desktop/memory_security/Cargo.lock @@ -0,0 +1,48 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ctor" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67773048316103656a637612c4a62477603b777d91d9c62ff2290f9cde178fdb" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2931af7e13dc045d8e9d26afccc6fa115d64e115c9c84b1166288b46f6782c2" + +[[package]] +name = "dtor" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e58a0764cddb55ab28955347b45be00ade43d4d6f3ba4bf3dc354e4ec9432934" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" + +[[package]] +name = "libc" +version = "0.2.175" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" + +[[package]] +name = "memory-security" +version = "0.1.0" +dependencies = [ + "ctor", + "libc", +] diff --git a/apps/desktop/memory_security/Cargo.toml b/apps/desktop/memory_security/Cargo.toml new file mode 100644 index 00000000000..e348edfb1c4 --- /dev/null +++ b/apps/desktop/memory_security/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "memory-security" +version = "0.1.0" +edition = "2024" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +ctor = "0.5.0" +libc = "0.2.175" diff --git a/apps/desktop/memory_security/src/isolate.rs b/apps/desktop/memory_security/src/isolate.rs new file mode 100644 index 00000000000..521e681f3ce --- /dev/null +++ b/apps/desktop/memory_security/src/isolate.rs @@ -0,0 +1,39 @@ +#[cfg(target_env = "gnu")] +use libc::c_uint; +use libc::{self, c_int}; + +/// RLIMIT_CORE is the maximum size of a core dump file. Setting both to 0 disables core dumps, on crashes +/// https://github.com/torvalds/linux/blob/1613e604df0cd359cf2a7fbd9be7a0bcfacfabd0/include/uapi/asm-generic/resource.h#L20 +#[cfg(target_env = "musl")] +const RLIMIT_CORE: c_int = 4; +#[cfg(target_env = "gnu")] +const RLIMIT_CORE: c_uint = 4; + +/// PR_SET_DUMPABLE makes it so no other running process (root or same user) can dump the memory of this process +/// or attach a debugger to it. +/// https://github.com/torvalds/linux/blob/a38297e3fb012ddfa7ce0321a7e5a8daeb1872b6/include/uapi/linux/prctl.h#L14 +const PR_SET_DUMPABLE: c_int = 4; + +/// Prevents a process crash from creating a coredump on disk +pub(crate) fn disable_coredumps() -> () { + let rlimit = libc::rlimit { + rlim_cur: 0, + rlim_max: 0, + }; + + if unsafe { libc::setrlimit(RLIMIT_CORE, &rlimit) } != 0 { + let e = std::io::Error::last_os_error(); + eprintln!("[Process Isolation] Failed to disable core dumping: {}", e); + } +} + +/// Prevents other process from accessing env, memory, attaching debugger +pub(crate) fn isolate_process() -> () { + if unsafe { libc::prctl(PR_SET_DUMPABLE, 0) } != 0 { + let e = std::io::Error::last_os_error(); + eprintln!( + "[Process Isolation] Failed to disable memory dumping: {}", + e + ); + } +} diff --git a/apps/desktop/memory_security/src/lib.rs b/apps/desktop/memory_security/src/lib.rs new file mode 100644 index 00000000000..78e724a0f10 --- /dev/null +++ b/apps/desktop/memory_security/src/lib.rs @@ -0,0 +1,42 @@ +//! This library compiles to a pre-loadable shared object. When preloaded, it +//! immediately isolates the process using the methods available on the platform. +//! On Linux, this is PR_SET_DUMPABLE to prevent debuggers from attaching, the env +//! from being read and the memory from being stolen. + +use std::ffi::{c_char, c_void}; + +mod isolate; + +#[link(name = "dl")] +unsafe extern "C" { + unsafe fn dlsym(handle: *const c_void, symbol: *const c_char) -> *const c_void; +} + +/// Hooks unsetenv to fix a bug in zypak-wrapper. +/// Zypak unsets the env in Flatpak as a side-effect, which means that only the top level +/// processes would be hooked. With this work-around all processes in the tree are hooked +#[unsafe(no_mangle)] +unsafe extern "C" fn unsetenv(name: *const c_char) -> i32 { + let name_str = std::ffi::CStr::from_ptr(name).to_str().unwrap(); + let original_unsetenv: unsafe extern "C" fn(*const c_char) -> i32 = + std::mem::transmute(dlsym(libc::RTLD_NEXT, c"unsetenv".as_ptr())); + + if name_str == "LD_PRELOAD" { + // This env variable is provided by the flatpak configuration + let ld_preload = std::env::var("MEMORY_SECURITY_LD_PRELOAD").unwrap_or_default(); + std::env::set_var("LD_PRELOAD", ld_preload); + return 0; + } + + original_unsetenv(name) +} + +// Hooks the shared object being loaded into the process +#[ctor::ctor] +fn preload_init() { + unsafe { + println!("[memory-security] Enabling memory security for process {}", pid); + isolate::disable_memory_access(); + isolate::disable_coredumps(); + } +} diff --git a/apps/desktop/resources/com.bitwarden.desktop.devel.yaml b/apps/desktop/resources/com.bitwarden.desktop.devel.yaml index 858fb6e1af2..f6cd6ca985c 100644 --- a/apps/desktop/resources/com.bitwarden.desktop.devel.yaml +++ b/apps/desktop/resources/com.bitwarden.desktop.devel.yaml @@ -46,4 +46,6 @@ modules: commands: - ulimit -c 0 - export TMPDIR="$XDG_RUNTIME_DIR/app/$FLATPAK_ID" + - export ZYPAK_LD_PRELOAD="/app/bin/libmemory_security.so" + - export MEMORY_SECURITY_LD_PRELOAD="/app/bin/libmemory_security.so" - exec zypak-wrapper /app/bin/bitwarden-app "$@" diff --git a/apps/desktop/resources/linux-wrapper.sh b/apps/desktop/resources/linux-wrapper.sh index dd53eb9811c..99f1cfaaf7c 100644 --- a/apps/desktop/resources/linux-wrapper.sh +++ b/apps/desktop/resources/linux-wrapper.sh @@ -7,12 +7,19 @@ ulimit -c 0 RAW_PATH=$(readlink -f "$0") APP_PATH=$(dirname $RAW_PATH) -# force use of base image libdus in snap -if [ -e "/usr/lib/x86_64-linux-gnu/libdbus-1.so.3" ] -then +# force use of base image libdbus in snap +if [ -e "/usr/lib/x86_64-linux-gnu/libdbus-1.so.3" ]; then export LD_PRELOAD="/usr/lib/x86_64-linux-gnu/libdbus-1.so.3" fi +# If running in non-snap, add libmemory_security.so from app path to LD_PRELOAD +# This prevents debugger / memory dumping on all desktop processes +if [ -z "$SNAP" ] && [ -f "$APP_PATH/libmemory_security.so" ]; then + LIBMEMORY_SECURITY_SO="$APP_PATH/libmemory_security.so" + LD_PRELOAD="$LIBMEMORY_SECURITY_SO${LD_PRELOAD:+:$LD_PRELOAD}" + export LD_PRELOAD +fi + PARAMS="--enable-features=UseOzonePlatform,WaylandWindowDecorations --ozone-platform-hint=auto" if [ "$USE_X11" = "true" ]; then PARAMS="" diff --git a/apps/desktop/scripts/after-pack.js b/apps/desktop/scripts/after-pack.js index 5fc42f31ac3..b40ca79e602 100644 --- a/apps/desktop/scripts/after-pack.js +++ b/apps/desktop/scripts/after-pack.js @@ -30,6 +30,17 @@ async function run(context) { fse.copyFileSync(wrapperScript, wrapperBin); fse.chmodSync(wrapperBin, "755"); console.log("Copied memory-protection wrapper script"); + + const memorySecurityPath = path.join(__dirname, "../memory_security/"); + const memorySecurityLibPath = path.join( + memorySecurityPath, + "target", + "release", + "libmemory_security.so", + ); + const memorySecurityLibOutPath = path.join(appOutDir, "libmemory_security.so"); + child_process.execSync(`cargo build --release`, { cwd: memorySecurityPath }); + fse.copyFileSync(memorySecurityLibPath, memorySecurityLibOutPath); } if (["darwin", "mas"].includes(context.electronPlatformName)) {