On memory security

Are passwords stored in memory safe?

The shortcut

Conclusion: yes, when you store a password in RAM, you trust the OS for keeping that confidential. Yes, the task is hard, even nigh impossible on modern systems. If some data is highly confidential, you really should not use the mainframe model, and not allow potentially hostile entities to run their code on your machine. [1]

What threats are we trying to defeat?

  1. adversaries with physical access to the device, including cold boot attacks (thieves, robbers, customs, or the local law enforcement have access to the user’s device).
  2. unprivileged adversarial processes on the same host (malware, applies especially for Desktop)
  3. Bugs in the JS/Nim engine or runtime environment
  4. a partial compromise of the host OS kernel

As a side note, Bitwarden was recently tested and criticized by a C’t magazine for not cleaning up their memory properly [2]
Node.js is currently meditating a solution: [3]

Beyond a partial compromise: Status Messenger Security Guarantees

We’ll describe the status app’s security guarantees that we should thrive to achieve.
We define three states of the app: not running, unlocked (and running), and locked (and running; this state assumes the app was previously opened). We define the three states below:

Not Running

We refer to “not running” where the app has been installed, configured, and interacted with by the user, but has not been launched since the last reboot.

In the “not running” state the app guarantees:

There should be no data stored on disk that would offer an attacker leverage toward compromising the database stored on drive (e.g., the seed phrase, passphrase, keycard PIN, or derived cryptographic keys) stored in a configuration or log file).
The encryption should be designed in such a way that, so long as the user used a reasonable secure password, the attacker cannot brute force the passphrase in a reasonable amount of time using commonly available computing resources.

Running: Unlocked State

We define running in an “unlocked state” as cases where the app is running, and where the user has typed in the seed phrase, the passphrase and/or the signing phrase to decrypt and access the messenger or wallet. The user may have displayed, copied to the clipboard, or otherwise accessed some of the app’s secret contents.

In this “running, unlocked state” the app guarantee:

It should not be possible to extract any secrets from memory, either directly or in any form that allows the original secret to be recovered without extraordinary computational effort.
For user data that has not been displayed/copied/accessed by the user since the app was unlocked, it should not be possible to access that data from memory.

Given usability constraints that affect the app, we acknowledge that:

It may be possible to extract user data from memory displayed/copied/accessed in the current unlocked session.
It may be possible to extract cryptographic data derived from the passphrase sufficient to decrypt other stored data, but not the seed phrase, passphrase, or keycard PIN itself.

Running: Locked State

We define “in locked state” as cases where (1) the app was launched, but the user has not entered the passphrase yet, or (2) the user previously entered the passphrase and used the app, but subsequently clicked the ‘Lock’ or a ‘lock-timeout’ locked the app.

In this “running, locked state,” the app should guarantee:

All the security guarantees of a not-running app should apply to an app that is in the locked state.
Since a locked app still exists as a process in virtual memory, this requires additional guarantees:

It should not be possible to extract the seed phrase, passphrase, or keycard PIN from memory, either directly or in any form that allows the original secrets to be recovered.
It should not be possible to extract from memory any cryptographic information derived from the passphrase that might allow sensitive user data to be decrypted without knowing the passphrase.
It should not be possible to extract any unencrypted messages or user data from memory that is stored in the app.

In addition to these explicit security guarantees, the Status app should incorporate additional hardening measures where possible, and to have these hardening measures enabled by default. For example, the app should attempt to block software keystroke loggers from accessing any secrets as they are typed, attempt to limit the exposure of unencrypted user data left in the clipboard, and take reasonable steps to detect and block modification or patching of the app and its supporting libraries that might expose passwords.

Potential next steps and best practices towards these guarantees

  • Feature: Memory safe (Auto-)App lock with opt-out [4] [5] [6]

  • Gradually implement explicit Memory Management; a static analyzer/linting (similar to [7] [8] helper that checks if a commit diff touched the secret set (Seedphrase, Private key, passphrase, Keycard pin). If so, suggest a) to apply the masked type to prevent log leaks b) apply explicit memory safety measures to the affected variables and references, for instance with libsodium.js memory_management [9] [10]

  • After the app has been locked, remove sensitive information by reloading the renderer process (similar to refreshing a web page) and apply memory safety measures to the secret set.

  • The inner state of external ciphers should be reset in memory as soon as possible. For instance, it should be verified that the algorithm that derives the private key from the seedphrase and the kdf that derives the .db key from the passphrase reset their internal state after use.

  • If the passphrase, seed phrase, or pin has to be kept in memory or handed over to third-party/libraries API, investigate if a KDF can be applied. For instance, SQL cipher allows to directly specify an encryption key instead of the library deriving one from the passphrase. If an external library is considered less trustworthy, we should derive a key from the passphrase in order to remain in control of the user provided secrets and their state in memory.

  • Limiting the traversal of secrets to OS provided APIs by implementing specialized/custom GUI elements, that are auditable, reusable and secure [11]

References:

[1] https://security.stackexchange.com/questions/29019/are-passwords-stored-in-memory-safe/29023#29023
[2] https://github.com/bitwarden/desktop/issues/476
[3] https://github.com/nodejs/node/issues/30956
[4] https://github.com/bitwarden/browser/issues/6
[5] https://github.com/bitwarden/browser/issues/29
[6] https://github.com/bitwarden/browser/pull/1194
[7] https://eslint.org
[8] https://github.com/jshint/jshint
[9] https://libsodium.gitbook.io/doc/memory_management
[10] https://github.com/jedisct1/libsodium.js/blob/59fcebeed8fb23130964f5583fc9d0c8254f5274/dist/modules-sumo/libsodium-wrappers.js#L1
[11] https://source.android.com/security/protected-confirmation
[12] https://docs.microsoft.com/en-us/windows-hardware/drivers/bringup/device-guard-requirements

8 Likes

I’d like to add the following resources on protecting the memory heap against debuggers and core dumps (an issue when running in a VM or in the “cloud”) and attacks that are practical against a memory allocator

In general, my recommendation is that high value secrets like keys and passwords should be in a separate process that only does one thing (signing, encrypting) and will have focused mitigations measures (including in-memory encryption).
So I would distinguish 3 kinds of security level for data:

  • High value secrets: keys/passwords
  • User messages
  • Public data
4 Likes

Thanks for your thoughts and resources on this @mamy. The status app was built with safety in mind from scratch, due to clojurescript and nim being the language of choice for the reference implementation, both being garbage collected, we do not have to implement memory safety from scratch. Even though, we thrive to further improve the safety of our users continuously by explicitly managing secrets in memory.

Could go one step further and have them stored in a separate application specific device like Keycard.

1 Like

That would become a barrier to entry.

For Android, having memory shared between different containers with read-only is possible: https://developer.android.com/ndk/reference/group/memory

So a virtual keycard that is replaced by hardware when present seems feasbile.