[lkml]   [2019]   [Jan]   [8]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
Messages in this thread
Patch in this message
Subject[RFC PATCH 01/19] btrfs: add encryption structs and constants
This is a series of patches to add per-extent AES encryption to Btrfs. Unlike
previous attempts, this isn't implemented by adding a new compression type,
meaning that extents that are both encrypted and compressed should work fine.

It's implemented by adding a 8-byte key number and a 16-byte
randomized initialization vector to the end of the BTRFS_EXTENT_DATA_KEY (which
for inline extents means immediately before the encrypted data). The key number
is an index into the new key root, which stores the password hashes, with the
key number doubling as the salt. This means that (unlike, AIUI, ext4), there's
no chance that a file by can be decrypted using the wrong key.

The ioctl BTRFS_IOC_GET_KEY_SALT provides the key number for a given password,
or if not present adds a new key with a randomized number.

We use AES in counter mode, so that we can do random reads without having to
read the whole extent.

Some notes:

* At the moment COW is forced for encrypted extents, meaning that preallocation
and the nocow option don't have any effect.

* Direct IO is disabled for encrypted extents. Direct reading should be
possible, as you'd do the decryption after the bio returns. I'm not sure direct
writing is feasible though, as you'd either have to copy the buffer or change
the process's memory.

* Once a key is added by keyctl, it persists until the FS is unmounted. Ideally
we'd like it to be removed when unlinked or revoked.

* We don't use fscrypt - I understand that the XTS mode that it operates in
means that sectors are keyed to their logical address, which would break

* Sending still dumps unencrypted data, and won't work on encrypted
files without the key. We could change this, but it raises the possibility that
the same password could correspond to multiple keys.

* These patches assume that the number of keys in the key tree will be small -
in particular, the ioctl walks the tree testing the hashes. There's no way to
remove keys from the key tree; I think we'd probably want btrfs-check to warn
about unused keys, something like that. We could use reference counting, but
that would mean writing to the key tree whenever an encrypted extent is created
or removed.

* Filenames aren't encrypted at present; I considered this a different enough
problem to save for another patchset, given that this one's already quite
hefty. I'm not sure if we could use fscrypt for this either, as btrfs allows
for multiple inodes with the same number, and it would be a security flaw to
duplicate IVs.

I've attached a quick-and-dirty userspace program demonstrating the ioctl:

#include <linux/btrfs.h>
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>
#include <keyutils.h>
#include <sys/xattr.h>

static int getpw(char **pw)
struct termios t, orig_t;
size_t sz;
int nread;

if (tcgetattr(fileno(stdin), &t) != 0)
return errno;

orig_t = t;
t.c_lflag &= ~ECHO;
if (tcsetattr(fileno(stdin), TCSAFLUSH, &t) != 0)
return errno;

nread = getline(pw, &sz, stdin);
if (nread > 0 && (*pw)[nread - 1] == '\n')
(*pw)[nread - 1] = 0; /* remove trailing newline */

tcsetattr(fileno(stdin), TCSAFLUSH, &orig_t);

return 0;

static int add_btrfs_key(__u64 salt, char *pw)
key_serial_t key;
char name[20];

sprintf(name, "btrfs:%016llx", salt);

* In pratice, you would want a type of "logon" here, so that
* the key can't be read from userspace.

key = add_key("user", name, pw, strlen(pw), KEY_SPEC_USER_KEYRING);
if (key == -1)
return errno;

printf("Added key %u to keyring\n", key);

return 0;

int main(int argc, char *argv[])
int fd, ret;
char *pw = NULL;
struct btrfs_ioctl_get_key_salt_args args;
char keyhex[17];

if (argc < 2) {
fprintf(stderr, "Usage: btrfscrypt <directory>\n");
return 0;

/* Prompt for password */

printf("Password: ");

ret = getpw(&pw);


if (ret) {
fprintf(stderr, "getpw returned %i\n", ret);
return ret;

fd = open(argv[1], 0);
if (fd < 0) {
fprintf(stderr, "open returned %i\n", fd);
goto end;

strcpy(args.password, pw);

/* Get salt */

ret = ioctl(fd, BTRFS_IOC_GET_KEY_SALT, &args);
if (ret) {
fprintf(stderr, "ioctl returned %i\n", ret);
goto end2;

printf("BTRFS_IOC_GET_KEY_SALT returned salt %llx\n", args.salt);

ret = add_btrfs_key(args.salt, pw);
if (ret) {
fprintf(stderr, "add_btrfs_key returned %i\n", ret);
goto end2;

/* Set xattr */

sprintf(keyhex, "%016llx", args.salt);

ret = setxattr(argv[1], "btrfs.key", keyhex, strlen(keyhex), 0);
if (ret) {
fprintf(stderr, "setxattr returned %i\n", ret);
goto end2;

ret = 0;



return ret;

Signed-off-by: Mark Harmstone <>
include/uapi/linux/btrfs.h | 1 +
include/uapi/linux/btrfs_tree.h | 21 +++++++++++++++++++++
2 files changed, 22 insertions(+)

diff --git a/include/uapi/linux/btrfs.h b/include/uapi/linux/btrfs.h
index 5ca1d21fc4a7..6c785d5cfb4b 100644
--- a/include/uapi/linux/btrfs.h
+++ b/include/uapi/linux/btrfs.h
@@ -269,6 +269,7 @@ struct btrfs_ioctl_fs_info_args {

struct btrfs_ioctl_feature_flags {
__u64 compat_flags;
diff --git a/include/uapi/linux/btrfs_tree.h b/include/uapi/linux/btrfs_tree.h
index aff1356c2bb8..40acca24c69f 100644
--- a/include/uapi/linux/btrfs_tree.h
+++ b/include/uapi/linux/btrfs_tree.h
@@ -48,6 +48,9 @@
/* tracks free space in block groups. */

+/* stores encryption keys */
/* device stats in the device tree */

@@ -141,6 +144,11 @@

+ * encryption key, consisting of the hashed password
+ */
* root backrefs tie subvols and snapshots to the directory entries that
* reference them
@@ -789,6 +797,19 @@ struct btrfs_file_extent_item {

} __attribute__ ((__packed__));

+struct btrfs_file_extent_item_enc {
+ struct btrfs_file_extent_item extent_item;
+ __le64 key_number;
+} __attribute__ ((__packed__));
+struct btrfs_file_extent_inline_enc {
+ __le64 key_number;
+} __attribute__ ((__packed__));
struct btrfs_csum_item {
__u8 csum;
} __attribute__ ((__packed__));
 \ /
  Last update: 2019-01-09 02:28    [W:0.082 / U:4.072 seconds]
©2003-2018 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site