Encryption-at-Rest as a Gateway to Device-Mapper, Loop Devices in Linux

Navaz Alani

Last updated on 29 July, 2022 at 15:20 hrs.

Abstract

This post presents a demo using some cool Linux features (loop devices & the Device-Mapper kernel framework) to show how encryption-at-rest is actually achieved in practice (in Linux, of course). For reference, BitLocker and FireVault are the Windows and MacOS equivalents.



Introduction

Encryption-at-rest is a set-up in which the OS kernel (Linux in this case) will encrypt data before writing it to persistent memory, and decrypt it when reading from persistent memory. This is great as a privacy feature: if your laptop is ever stolen/lost, at least your data isn’t compromised. However, it does need to be implemented with great care to get any guarantee of security; the Arch Wiki on dm-crypt provides a lot of information that someone doing this seriously ought to understand – I suggest it for further reading if you’re interested.

This post does not actually go into how to perform an installation of Arch with disk-encryption, but it will surely help one understand how encryption-at-rest is implemented in Linux systems. To do this, the post provides a high-level overview of the kernel’s Device-Mapper abstraction framework and how it can be used, in conjunction with the Loop Devices, to build an encrypted backup setup.

Device-Mapper: dm-crypt

Device-Mapper is a framework provided by the Linux kernel which makes the creation and management of logical volumes, LVs, possible (Logical Volume Management, LVM, is the category for all things LV related). We differentiate between virtual and physical volumes as follows. A physical volume generally corresponds to hardware like an SSD/HDD, while a logical volume exists, and is managed, in software. Logical volumes are generally much more flexible and can be used to do cool things using the kernel’s Device-Mapper framework.

From Device-Mapper’s documentation

Device-Mapper’s “crypt” target provides transparent encryption of block devices using the kernel crypto API.

Recall: To the Linux kernel, a block device is anything that can provide random access to data, in fixed size blocks. RAM, SSDs, HDDs, USB flash drives, are all examples of block devices. Char(acter) devices, on the other hand, deal with data in a stream-oriented fashion; think of the webcam, microphone, speakers, keyboard and many more.

It would be nice to play around with dm-crypt in a low-stakes environment since it has the ability to render your system unusable, if not careful. A virtual machine is the best option, but it’s a bit too much for me (and I think loop devices are somewhat unappreciated).

Loop Devices

We’ve seen that the dm-crypt target provides encryption of devices which can provide random access to data in fixed size blocks. Well, a regular file provides can satisfy these requirements pretty trivially: it can be read and written in byte-sized blocks. But how can we get the kernel to treat a file like a block device (since that’s what we need to use dm-crypt)?

A loop device is a block device whose backing medium is not a physical volume, but rather another block device or a file in the file system (see man loop). This is exactly what we need to treat a file as a block device.

Project

Now that we have covered the basics, let’s combine them to solve some real problem. Most people have important digital files that (obviously) are backed up on, another device like an external HDD/SSD, USB flash drives, etc. It’s possible that some backed up files are confidential and so storing them in plain text can’t be good; encrypting each file individually can work, but it’s too much work (and not too elegant either), and it also revels (to a cryptographic adversary) too much information about what the files actually are; I probably don’t have to wonder what the contents of id_rsa are.

We’re going to implement a backup program which can be used by a system user to back up a list of files/directories.

Kernel Modules

We need to load the required kernel modules for Loop and dm-crypt to work. Run modprobe loop dm_crypt to load the modules into the kernel.

Creating a Loop Device from a Regular File

The size of the regular file can vary depending on one’s needs; I’ll create a 1G zeroed-out file called backup using

dd if=/dev/zero of=backup bs=4M count=250

NOTE: Always be careful when using dd as it can very easily destroy your disk (as evidenced by its nickname “disk destroyer”).

Since backup is a regular file, it can be moved/copied, using the normal file-system commands, to an external HDD or another backup device. So it doesn’t matter where one creates the backup file.

If need be, backup can also be simply resized later on. dd takes a seek argument which can be set to seek at the end of the file, so additional space is appended to the file. Then, the file system on the block device needs to be updated for this change to be interpreted properly. Different file systems have different commands, so one would have to look this up.

Setup: Loop & Dm-crypt

The following command will set up the regular file backup as a loop device.

sudo losetup --find --show --partscan backup

The --find flag asks losetup to find the first free loop device and use that and --partscan asks the kernel to scan the partition table for the device (see man losetup for more). If successful, it will print out a path like /dev/loop0 – our loop device has been created. Here’s the line that appears in my lsblk (the command which “lists block” devices) output

NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
loop0         7:0    0  1000M  0 loop

We can see that it has size 1G, and is not mounted. To mount it, we need a file system, but first a short discussion on the non-commutativity of logical volumes and encryption.

Device-Mapper allows us to set up and manage virtual volumes, and dm-crypt allows us to encrypt these virtual volumes (which satisfy the interface for block devices). The order of these actually does not matter to the tools themselves, but it does produce a different outcome. If dm-crypt encrypts the entire block device (backup) first, an adversary with a copy of backup, depending on how determined they are, won’t be able to tell that there’s a file-system on it, since dm-crypt will encrypt the partition table of the block device. If dm-crypt runs on a partition in a backup instead, an attacker can quickly determine that backup is a dm-crypt’ed loop device using a simple scan of the first few kilobytes of backup for a partition table. The former throws kind of a smoke-screen for the adversary, and we shall use that approach here.

Next, we have to prepare the device for use with dm-crypt using following command (where LOOP_DEVICE is the new block device added in the lsblk output; would be /dev/loop0 for me).

sudo cryptsetup luksFormat "${LOOP_DEVICE}"

If this completes successfully, the device has been set up by dm-crypt.

Aside: Security vs. Safety

TL;DR The data’s security can compromise a user’s safety.

The concern for safety comes when an adversary who possesses a copy of backup deduces that it is actually an encrypted file system. A scenario on the Wikipedia page for “deniable encryption” describes a situation in which a person, say Bob, has a kind of “onion-vault” which is encrypted by multiple layers of dm-crypt (so dm-crypt would encrypt a logical volume which is contained inside a dm-crypt encrypted logical volume and so on…) and the adversary becomes aware of this fact. The adversary then resorts to torturing the keys out of Bob, but the adversary can’t know whether Bob has provided the last key or not. And because of this fact, the adversary could indefinitely torture Bob. Deducing this, Bob actually figures that if the adversary can’t know when he’s given the last key, there’s no point in revealing any keys since he is going to be tortured either way.

It’s interesting to think about, and realize that cryptography, powerful as it is, can actually jeopardize the safety of the user in the most extreme situations.

Using a Dm-crypte’ed Block Device

Before using the dm-crypt’ed loop device, its Device-Mapper (dm-crypt) has to be set up in the kernel first (dm-crypt needs the key to perform encryption/decryption in its write/read implementations on the underlying block device).

sudo cryptsetup open "${LOOP_DEVICE}" "${MAPPER_NAME}"

The above command will set up the LOOP_DEVICE in the kernel (e.g. /dev/loop0) and expose it as an unencrypted block device at /dev/mapper/${MAPPER_NAME} for plain read/write operations. The first time that the device is used, it will have no file system, so we can create one on it, and make a mount point for this device.

# format the exposed block device
sudo mkfs.ext4 "/dev/mapper/${MAPPER_NAME}"
# create a mount point
sudo mkdir -p "/mnt/${MAPPER_NAME}"

Then to mount the formatted device to the created mount point, run

# mount the formatted exposed block device to mount point
sudo mount "/dev/mapper/${MAPPER_NAME}" "/mnt/${MAPPER_NAME}"

Now, I can back up my SSH keys in there using

rsync -aPrv ~/.ssh "/mnt/${MAPPER_NAME}"

In general, rsync can be used to make a great backup tool. The user can have a list of directories to back up in a ~/.config/backup_list.txt which; a script would then use rsync to mirror each directory in the list to /mnt/${MAPPER_NAME}.

Extension (for the Reader)

udev is another interesting part of the kernel which manages device events. If one can determine which udev event corresponds to the insertion of their backup media (HDD/SSD/USB flash), it’s possible to then run a script which will prompt the user for the backup file password, open backup, mount it and synchronize the user’s directories, then unmount and close backup.

Conclusion

This post has gone over the following

I hope you learned something, because I surely did when writing this!