Introducing cryptsetup-suspend
Today, we're introducing cryptsetup-suspend
, whose job is to protect the content of your harddrives while the system is sleeping.
TL;DR:
- You can lock your encrypted harddrives during suspend mode by installing
cryptsetup-suspend
- For
cryptsetup-suspend
to work properly, at least Linux kernel 5.6 is required - We hope that in a bright future, everything will be available out-of-the-box in Debian and it's derivatives
Before:
After:
Table of contents
What does this mean and why should you care about it?
If you don't use full-disk encryption, don't read any further. Instead, think about, what will happen if you lose your notebook on the train, a random person picks it up and browses through all your personal pictures, e-mails, and tax records. Then encrypt your system and come back.
If you believe full-disk encryption is necessary, you might know that it only works when your machine is powered off. Once you turn on the machine and decrypt your harddrive, your encryption key stays in RAM and can potentially be extracted by malicious software or physical access. Even if these attacks are non-trivial, it's enough to worry about. If an attacker is able to extract your disk encryption keys from memory, they're able to read the content of your disk in return.
Sadly, in 2020, we hardly power off our laptops anymore. The sleep mode, also known as "suspend mode", is just too convenient. Just close the lid to freeze the system state and lift it anytime later in order to continue. Well, convenience usually comes with a cost: during suspend mode, your system memory is kept powered, all your data - including your encryption keys - stays there, waiting to be extracted by a malicious person. Unfortunately, there are practical attacks to extract the data of your powered memory.
Cryptsetup-suspend expands the protection of your full-disk encryption to all those times when your computer sleeps in suspend mode. Cryptsetup-suspend utilizes the suspend feature of LUKS volumes and integrates it with your Debian system. Encryption keys are evicted from memory before suspend mode and the volumes have to be re-opened after resuming - potentially prompting for the required passphrases.
By now, we have a working prototype which we want to introduce today. We did quite some testing, both on virtualized and bare-metal Debian and Ubuntu systems, with and without graphical stack, so we dare to unseal and set free the project and ask you - the community - to test, review, criticize and give feedback.
Here's a screencast of cryptsetup-suspend
in action:
State of the implementation: where are we?
If you're interested in the technical details, here's how cryptsetup-suspend
works internally. It basically consists of three parts:
cryptsetup-suspend
: A C program that takes a list of LUKS devices as arguments, suspends them vialuksSuspend
and suspends the system afterwards. Also, it tries to reserve some memory for decryption, which we'll explain below.cryptsetup-suspend-wrapper
: A shell wrapper script which works the following way:- Extract the initramfs into a ramfs
- Run (systemd) pre-suspend scripts, stop udev, freeze almost all cgroups
- Chroot into the ramfs and run
cryptsetup-suspend
- Resume initramfs devices inside chroot after resume
- Resume non-initramfs devices outside chroot
- Thaw groups, start udev, run (systemd) post-suspend scripts
- Unmount the ramfs
- A systemd unit drop-in file overriding the
Exec
property ofsystemd-suspend.service
so that it invokes the scriptcryptsetup-suspend-wrapper
.
Reusing large parts of the existing cryptsetup-initramfs implementation has some positive side-effects: Out-of-the-box, we support all LUKS block device setups that have been supported by the Debian cryptsetup packages before.
Freezing most processes/cgroups is necessary to prevent possible race-conditions and dead-locks after the system resumes. Processes will try to access data on the locked/suspended block devices eventually leading to buffer overflows and data loss.
Technical challenges and caveats
- Dead-locks at suspend: In order to prevent possible dead-locks between suspending the encrypted LUKS disks and suspending the system, we have to tell the Linux kernel to not
sync()
before going to sleep. A corresponding patch got accepted upstream in Linux 5.6. See section What about the kernel patch? below for details. - Race conditions at resume: Likewise, there's a risk of race conditions between resuming the system and unlocking the encypted LUKS disks. We went with freezing as many processes as possible as a counter measurement. See last part of section State of the implementation: where are we? for details.
- Memory management: Memory management is definitely a challenge. Unlocking disks might require a lot of memory (if key derivation function is argon2i) and the swap device most likely is locked at that time. See section All that matters to me is the memories! below for details.
- systemd dependency: Our implementation depends on systemd. It uses a unit drop-in file for
systemd-suspend.service
for hooking into the system suspend process and depends on systemds cgroup management to freeze and thaw processes. If you're using a different init system, sorry, you're currently out of luck.
What about the kernel patch?
The problem is simple: the Linux kernel suspend implementation enforces a final filesystem sync() before the system goes to sleep in order to prevent potential data loss. While that's sensible in most scenarios, it may result in dead-locks in our situation, since the block device that holds the filesystem is already suspended. The fssync()
call will block forever as it waits for the block device to finish the sync()
operation. So we need a way to conditionally disable this sync()
call in the Linux kernel resume function. That's what our patch does, by introducing a run-time switch at /sys/power/sync_on_suspend
, but it only got accepted into the Linux kernel recently and was first released with Linux kernel 5.6.
Since release 4.3, the Linux kernel at least provides a build-time flag to disable the sync()
: CONFIG_SUSPEND_SKIP_SYNC
(that was called SUSPEND_SKIP_SYNC
first and renamed to CONFIG_SUSPEND_SKIP_SYNC
in kernel release 4.9). Enabling this flag at build-time protects you against the dead locks perfectly well. But while that works on an individual basis, it's a non-option for the distribution Linux kernel defaults. In most cases you still want the sync()
to happen, except if you have user-space code that takes care of the sync()
just before suspending your system - just like our cryptsetup-suspend
implementation does.
So in order to properly test cryptsetup-suspend
, you're strongly advised to run Linux kernel 5.6 or newer. Fortunately, Linux 5.6 is available in buster-backports
thanks to the Debian Kernel Team.
All that matters to me is the memories!
One of the tricky parts is memory management. Since version 2, LUKS uses argon2i as default key derivation function. Argon2i is a memory-hard hash function and LUKS2 assigns the minimum of half of your systems memory or 1 GB to unlocking your device. While this is usually unproblematic during system boot - there's not much in the system memory anyway - it can become problematic when suspending. When cryptsetup tries to unlock a device and wants 1 GB of memory for this, but everything is already occupied by your browser and video player, there's only two options what to do:
- Kill a process to free some memory
- Move some of the data from memory to swap space
The first option is certainly not what you expect when suspending your system. The second option is impossible, because swap is located on your harddrive which we have locked before. Our current solution is to allocate the memory after freezing the other processes, but before locking the disks. At this time, the system can still move data to swap, but it won't be accessed anymore. We then release the memory just in time for cryptsetup to claim it again. The implementation of this is still subject to change.
What's missing: A proper user interface
As mentioned before, we consider cryptsetup-suspend
usable, but it certainly still has bugs and shortcomings. The most obvious one is lack of a proper user interface. Currently, we switch over to a tty command-line interface to prompt for passphrases when unlocking the LUKS devices. It certainly would be better to replace this with a graphical user interface later, probably by using plymouth or something alike. Unfortunately, it seems rather impossible to spawn a real graphical environment for the passphrase prompt. That would imply to load the full graphical stack into the ramfs, raising the required amount of memory significantly. Lack of memory is currently our biggest concern and source of trouble.
We'd definitely appreciate to learn about your ideas how to improve the user experience here.
Let's get practical: how to use
TL;DR: On Debian Bullseye (Testing), all you need to do is to install the cryptsetup-suspend
package from experimental. It's not necessary to upgrade the other cryptsetup packages. On Debian Buster, cryptsetup packages from backports are required.
- First, be sure that you're running Linux kernel 5.6 or newer. For Buster systems, it's available in buster-backports.
- Second, if you're on Debian Buster, install the cryptsetup 2:2.3.3-2~bpo10+1 packages from buster-backports.
- Third, install the
cryptsetup-suspend
package from experimental. Beware thatcryptsetup-suspend
depends oncryptsetup-initramfs (>= 2:2.3.3-1~)
. Either you need the cryptsetup packages fromtesting/unstable
, or the backports frombuster-backports
. - Now that you have the
cryptsetup-suspend
package installed, everything should be in place: Just send your system to sleep. It should switch to a virtual text terminal before going to sleep, ask for a passphrase to unlock your encrypted disk(s) after resume and switch back to your former working environment (most likely your graphical desktop environment) afterwards.
Security considerations
Suspending LUKS devices basically means to remove the corresponding encryption keys from system memory. This protects against all sort of attacks trying to read them from there, e.g. cold-boot attacks. But, cryptsetup-suspend
only protects the encryption keys of your LUKS devices. Most likely there's more sensitive data in system memory, like all kinds of private keys (e.g. OpenPGP, OpenSSH) or documents with sensitive content.
We hope that the community will help improve this situation by providing useful pre-/post-suspend scripts. A positive example is KeepassXC, which is able to lock itself when going to suspend mode.
Related and similar projects
- Systemd Homed: systemd recently got a new feature to manage home directories. It brings support for encrypting your home directory within a LUKS2 container and for suspending the LUKS2 container during system sleep. It all is limited to systemd home directories though and doesn't help with other LUKS devices.
- There's several earlier
luks-suspend-*
implementations. Unfortunately, neither of them deal with all the Technical challenges and caveats we discovered. Still we'd like to mention some of them: - debian-luks-suspend
- go-luks-suspend
- arch-luks-suspend
Links
- Our Linux kernel patch: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c052bf82c6b00ca27aab0859addc4b3159dfd3a4
- The
debian/experimental
branch of our Debian cryptsetup packaging repository, containingcryptsetup-suspend
related changes: https://salsa.debian.org/cryptsetup-team/cryptsetup/-/tree/debian/experimental cryptsetup-suspend
manpage: https://salsa.debian.org/cryptsetup-team/cryptsetup/-/blob/debian/experimental/debian/doc/cryptsetup-suspend.xml
Feedback and Comments
We'd be more than happy to learn about your thoughts on cryptsetup-suspend. For specific issues, don't hesitate to open a bugreport against cryptsetup-suspend. You can also reach us via mail - see the next section for contact addresses. Last but not least, comments below the blogpost work as well.
Authors
- Tim (tim at systemli.org)
- Jonas (jonas at freesources.org)
Very interesting project - thanks a lot! Would sysv-init users have to customize the code you provide or can it be expected to work out-of-the-box (excepting its experimental status of course)?
systemd-suspend.service
. See Technical challenges and caveats.Dear Tim and Jonas
Although the third commentator IS right, all progress has its risks and I have not found anything, anywhere else, that comes near to solving this problem. Furthermore, methinks it short-sighted of those who implemented LUKS disk encryption not to think about this problem. Please carry on with the good work and I, for one, will use it and report back.
Thank you for your effort.
Andrew