Memory Hardening
While protecting against user-space memory attacks in the general case is not within the threat model for Bitwarden applications, the lock state must be protected, and it should not be possible to unlock a locked vault. Because of this, passwords or keys cannot be left behind in memory in an accessible state. Besides this requirement, some features such as SSH agent need process hardening where possible, as required by the protocol specification.
Zeroizing and process reload
To clear secrets on locking, Bitwarden clients use two techniques, zeroizing and process reload. For any memory that lives in Rust, memory is overwritten with zeroes, as soon as it becomes unused or gets dropped 1. This hardens the SDK, and the Rust desktop module (desktop native) against memory being left behind. Process reload wipes the entire process - on the web app by reloading the page, on browser extensions by reloading the extension, and on desktop by force-crashing the renderer process 2. The assumption here is that since the process dies, the memory gets wiped too. JavaScript does not provide mechanisms for reliably zeroizing memory. Secrets or partial secrets frequently remain in memory even after garbage collection cycles complete.
Process isolation and key protection on desktop apps
Next to process reload and zeroizing, desktop apps can use OS-level protections to harden memory. There are two mechanisms used here: Process isolation and key protection. Process isolation uses OS-level features to isolate the process from debugger access. Windows and desktop Linux by default allow user-space processes to debug other user-space processes and read memory. MacOS does not allow this by default and requires user consent to allow a process to debug another process. On Linux, some distributions such as Ubuntu use yama.ptrace_scope to limit ptrace access.
To harden against user-space memory attacks, Bitwarden desktop isolates the main process. On
Windows, DACL is used
to restrict access to the process, on Linux
PR_SET_DUMPABLE is used to
disable ptrace access and on MacOS the process is hardened using the Hardened Runtime entitlements,
and also by using PT_DENY_ATTACH to prevent debugger attachment. On Linux, a dynamic library that
sets PR_SET_DUMPABLE is also
injected into the renderer processes by injecting a shared object into the renderer processes
3,
so that these are isolated too. These mechanisms apply to all apps except for the Snap desktop app.
Snap does not support
PR_SET_DUMPABLE currently and
breaks file picker support, due to a bug
in the desktop portal.
Next to hardening the entire process, operating systems offer mechanisms to protect cryptographic
keys in memory. On Windows,
DPAPI can
be used to encrypt a key in memory, with a key bound to the process. On Linux,
memfd_secret and
keyctl are available, each of which can be
used to store keys in memory while preventing other processes from reading them. This is used to
hold the biometric unlock key in memory while the desktop app is locked. Access to this protected
memory is available via the
EncryptedMemoryStore
abstraction that automatically uses the correct memory protection.