Per-extent encrypted keys for fscrypt
Fscrypt got its start in 2015 as an ext4-specific encryption feature, but it was later generalized to be able to support other filesystems as well, with the second user being F2FS. To enable encryption, an administrator must start with an empty directory (which can be the root directory ) on a filesystem and set a "master key" for that directory, after which all files and subdirectories created below the top-level directory will be encrypted. To be able to access the contents of that directory, the master key must be stored in the kernel's keyring. One master key can be used with multiple directory hierarchies, or different keys can be used with different hierarchies as needed.
Each file within a filesystem has its own encryption key separate from the master key; this is needed to prevent two identical (plain-text) files from having the same encrypted representation. That key is stored with the file's inode data and can only be decrypted using the master key associated with the directory hierarchy. The result is secure encryption of the directory tree, which will be inaccessible in the absence of the master key. That should prevent access to the data in an offline attack, but will not prevent access from a compromised system where the master key is present.
Btrfs is, of course, a copy-on-write filesystem, and many of its features depend on that functionality. A filesystem snapshot, for example, is simply a second reference to the filesystem itself, sharing all data between both "copies". If a given file is written to after the snapshot is made, the affected extents of that file will be copied before modification, leaving the snapshot unchanged. Extents can also be explicitly shared between files using the Btrfs "reflink" functionality which, despite numerous attempts to upstream a reflink() system call, is currently implemented as a pair of ioctl() calls.
This sharing is efficient in terms of storage space, but it creates an interesting problem when fscrypt enters the picture: if an extent is shared between two files, which key should be used to encrypt its data? Those two files will have different keys, after all. One might consider adding another layer so that either key would work, but that runs into another difficulty; any solution to this problem will need to avoid adding data to the extent for each file that references it, since the number of such references can grow without bound. So approaches that add multiple keys are not going to work.
The solution that was chosen was to move the encryption key from a file's inode to each extent contained within the file. As any given file is read, the necessary decryption keys are obtained from each extent; the keys can vary from one extent to the next. As a result, a single file can contain data encrypted by multiple keys, and a given encrypted extent can appear in different (encrypted) files. Even different master keys could be used, as long as all of the required keys have been loaded into the kernel keyring.
This mechanism solves the data-sharing problem, and enables some additional
interesting use cases. For example, a directory's master key could be
changed after a snapshot is made without re-encrypting all of the data
contained underneath that directory. New data would, thereafter, be
written using the
new key. As a result, possession of the master key(s) needed to read the
snapshot would not give access to any data created after the snapshot is
made. Perhaps more usefully, a system in possession of (only) the current
key would be able to write to an encrypted filesystem without being able to
read any of the pre-snapshot data. According to this
design document for fscrypt support in Btrfs, "this is an important
use case for Meta
".
A scheme like this will bring some limitations of its own, of course. While it is theoretically possible to load all of the per-extent keys for a file prior to accessing the file, that would be problematic in practice. Files can be large and contain a huge number of extents, which would require the kernel to allocate memory for an equally large number of keys. An attacker who could create a large, highly fragmented file could thus run the kernel out of memory. So keys are loaded as extents are accessed; this works — as long as the master keys needed to obtain each per-extent key are all present. Otherwise, access to a file will fail partway through, which could be a surprising result.
There are also limitations on the sharing of encrypted extents. Perhaps most obviously, it is not possible to reflink an encrypted extent into an unencrypted file. Inline encryption, where the actual encryption and decryption work is offloaded to a suitably capable storage device is also not supported. That is not a fundamental limitation of this approach; Dorminy just hasn't figured out how to implement it yet.
The current patch set does not add fscrypt features to Btrfs; it is,
instead, a subset of a larger patch series (first posted
in August 2022 and most recently in
November) that is focused on the fscrypt changes. It makes sense to
put that work out separately, since it may affect filesystems beyond Btrfs.
Once this work clears the bar, though, the full Btrfs patch set seems
certain to follow quickly.
Index entries for this article | |
---|---|
Kernel | Filesystems/Btrfs |
Kernel | Security/Filesystem encryption |
Posted Jan 6, 2023 1:44 UTC (Fri)
by xecycle (subscriber, #140261)
[Link] (1 responses)
Posted Jan 6, 2023 6:07 UTC (Fri)
by hsiangkao (guest, #123981)
[Link]
Per-extent encrypted keys for fscrypt
Per-extent encrypted keys for fscrypt