Skip to content

Commit

Permalink
btrfs: add authentication support
Browse files Browse the repository at this point in the history
Add authentication support for a BTRFS file-system.

This works, because in BTRFS every meta-data block as well as every
data-block has a own checksum. For meta-data the checksum is in the
meta-data node itself. For data blocks, the checksums are stored in the
checksum tree.

When replacing the checksum algorithm with a keyed hash, like HMAC(SHA256),
a key is needed to mount a verified file-system. This key also needs to be
used at file-system creation time.

We have to used a keyed hash scheme, in contrast to doing a normal
cryptographic hash, to guarantee integrity of the file system, as a
potential attacker could just generate the corresponding cryptographic
hash for forged file-system operations and the changes would go unnoticed.

Having a keyed hash only on the topmost Node of a tree or even just in the
super-block and using cryptographic hashes on the normal meta-data nodes
and checksum tree entries doesn't work either, as the BTRFS B-Tree's Nodes
do not include the checksums of their respective child nodes, but only the
block pointers and offsets where to find them on disk.

Also note, we do not need a incompat R/O flag for this, because if an old
kernel tries to mount an authenticated file-system it will fail the
initial checksum type verification and thus refuses to mount.

The key has to be supplied by the kernel's keyring and the method of
getting the key securely into the kernel is not subject of this patch.

Example usage:
Create a file-system with authentication key 0123456
mkfs.btrfs --csum "hmac(sha256)" --auth-key 0123456 /dev/disk

Add the key to the kernel's keyring as keyid 'btrfs:foo'
keyctl add logon btrfs:foo 0123456 @U

Mount the fs using the 'btrfs:foo' key
mount -o auth_key=btrfs:foo,auth_hash_name="hmac(sha256)" /dev/disk /mnt/point

Signed-off-by: Johannes Thumshirn <jthumshirn@suse.de>
Signed-off-by: David Sterba <dsterba@suse.com>
  • Loading branch information
morbidrsa authored and kdave committed Feb 17, 2021
1 parent 39df2a4 commit 13be516
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 9 deletions.
2 changes: 2 additions & 0 deletions fs/btrfs/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ config BTRFS_FS
select CRYPTO_XXHASH
select CRYPTO_SHA256
select CRYPTO_BLAKE2B
select CRYPTO_HMAC
select KEYS
select ZLIB_INFLATE
select ZLIB_DEFLATE
select LZO_COMPRESS
Expand Down
22 changes: 20 additions & 2 deletions fs/btrfs/ctree.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,15 @@ static void del_ptr(struct btrfs_root *root, struct btrfs_path *path,

static const struct btrfs_csums {
u16 size;
const char name[10];
const char name[12];
const char driver[12];
} btrfs_csums[] = {
[BTRFS_CSUM_TYPE_CRC32] = { .size = 4, .name = "crc32c" },
[BTRFS_CSUM_TYPE_XXHASH] = { .size = 8, .name = "xxhash64" },
[BTRFS_CSUM_TYPE_SHA256] = { .size = 32, .name = "sha256" },
[BTRFS_CSUM_TYPE_BLAKE2] = { .size = 32, .name = "blake2b",
.driver = "blake2b-256" },
[BTRFS_CSUM_TYPE_HMAC_SHA256] = { .size = 32, .name = "hmac(sha256)" }
};

int btrfs_super_csum_size(const struct btrfs_super_block *s)
Expand All @@ -56,12 +57,29 @@ const char *btrfs_super_csum_name(u16 csum_type)
return btrfs_csums[csum_type].name;
}

static const char *btrfs_auth_csum_driver(struct btrfs_fs_info *fs_info)
{
int i;

for (i = 0; i < ARRAY_SIZE(btrfs_csums); i++) {
if (!strncmp(fs_info->auth_hash_name, btrfs_csums[i].name,
strlen(btrfs_csums[i].name)))
return btrfs_csums[i].driver[0] ?
btrfs_csums[i].driver : btrfs_csums[i].name;
}

return NULL;
}

/*
* Return driver name if defined, otherwise the name that's also a valid driver
* name
*/
const char *btrfs_super_csum_driver(u16 csum_type)
const char *btrfs_super_csum_driver(struct btrfs_fs_info *info, u16 csum_type)
{
if (btrfs_test_opt(info, AUTH_KEY))
return btrfs_auth_csum_driver(info);

/* csum type is validated at mount time */
return btrfs_csums[csum_type].driver[0] ?
btrfs_csums[csum_type].driver :
Expand Down
5 changes: 4 additions & 1 deletion fs/btrfs/ctree.h
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,8 @@ struct btrfs_fs_info {
struct rb_root swapfile_pins;

struct crypto_shash *csum_shash;
char *auth_key_name;
char *auth_hash_name;

/*
* Number of send operations in progress.
Expand Down Expand Up @@ -1382,6 +1384,7 @@ static inline u32 BTRFS_MAX_XATTR_SIZE(const struct btrfs_fs_info *info)
#define BTRFS_MOUNT_DISCARD_ASYNC (1 << 29)
#define BTRFS_MOUNT_IGNOREBADROOTS (1 << 30)
#define BTRFS_MOUNT_IGNOREDATACSUMS (1 << 31)
#define BTRFS_MOUNT_AUTH_KEY (1 << 32)

#define BTRFS_DEFAULT_COMMIT_INTERVAL (30)
#define BTRFS_DEFAULT_MAX_INLINE (2048)
Expand Down Expand Up @@ -2385,7 +2388,7 @@ BTRFS_SETGET_STACK_FUNCS(super_uuid_tree_generation, struct btrfs_super_block,

int btrfs_super_csum_size(const struct btrfs_super_block *s);
const char *btrfs_super_csum_name(u16 csum_type);
const char *btrfs_super_csum_driver(u16 csum_type);
const char *btrfs_super_csum_driver(struct btrfs_fs_info *info, u16 csum_type);
size_t __attribute_const__ btrfs_get_num_csums(void);


Expand Down
71 changes: 67 additions & 4 deletions fs/btrfs/disk-io.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <linux/error-injection.h>
#include <linux/crc32c.h>
#include <linux/sched/mm.h>
#include <keys/user-type.h>
#include <asm/unaligned.h>
#include <crypto/hash.h>
#include "ctree.h"
Expand Down Expand Up @@ -99,8 +100,10 @@ void __cold btrfs_end_io_wq_exit(void)

static void btrfs_free_csum_hash(struct btrfs_fs_info *fs_info)
{
if (fs_info->csum_shash)
if (fs_info->csum_shash) {
crypto_free_shash(fs_info->csum_shash);
fs_info->csum_shash = NULL;
}
}

/*
Expand Down Expand Up @@ -289,6 +292,7 @@ static bool btrfs_supported_super_csum(u16 csum_type)
case BTRFS_CSUM_TYPE_XXHASH:
case BTRFS_CSUM_TYPE_SHA256:
case BTRFS_CSUM_TYPE_BLAKE2:
case BTRFS_CSUM_TYPE_HMAC_SHA256:
return true;
default:
return false;
Expand Down Expand Up @@ -1560,6 +1564,8 @@ void btrfs_free_fs_info(struct btrfs_fs_info *fs_info)
percpu_counter_destroy(&fs_info->ordered_bytes);
percpu_counter_destroy(&fs_info->dev_replace.bio_counter);
btrfs_free_csum_hash(fs_info);
kfree(fs_info->auth_key_name);
kfree(fs_info->auth_hash_name);
btrfs_free_stripe_hash_table(fs_info);
btrfs_free_ref_cache(fs_info);
kfree(fs_info->balance_ctl);
Expand Down Expand Up @@ -2286,10 +2292,16 @@ static int btrfs_init_workqueues(struct btrfs_fs_info *fs_info,
static int btrfs_init_csum_hash(struct btrfs_fs_info *fs_info, u16 csum_type)
{
struct crypto_shash *csum_shash;
const char *csum_driver = btrfs_super_csum_driver(csum_type);
const char *csum_driver;
struct key *key;
const struct user_key_payload *ukp;
int err = -EINVAL;

csum_shash = crypto_alloc_shash(csum_driver, 0, 0);
csum_driver = btrfs_super_csum_driver(fs_info, csum_type);
if (!csum_driver)
return err;

csum_shash = crypto_alloc_shash(csum_driver, 0, 0);
if (IS_ERR(csum_shash)) {
btrfs_err(fs_info, "error allocating %s hash for checksum",
csum_driver);
Expand All @@ -2298,7 +2310,57 @@ static int btrfs_init_csum_hash(struct btrfs_fs_info *fs_info, u16 csum_type)

fs_info->csum_shash = csum_shash;

return 0;
/*
* If we're not doing authentication, we're done by now. If we use
* authentication and the auth_hash_name was bogus crypt_alloc_shash
* would have dropped out by now. Validation that both auth_hash_name
* and auth_key_name have been supplied is done in
* btrfs_parse_early_options(), so we should be good to go from here on
* and start authenticating the file-system.
*/
if (!btrfs_test_opt(fs_info, AUTH_KEY))
return 0;

if (strncmp(fs_info->auth_key_name, "btrfs:", 6)) {
btrfs_err(fs_info,
"authentication key must start with 'btrfs:'");
goto out_free_hash;
}

key = request_key(&key_type_logon, fs_info->auth_key_name, NULL);
if (IS_ERR(key)) {
err = PTR_ERR(key);
goto out_free_hash;
}

down_read(&key->sem);

ukp = user_key_payload_locked(key);
if (!ukp) {
btrfs_err(fs_info, "error getting payload for key %s",
fs_info->auth_key_name);
err = -EKEYREVOKED;
goto out;
}

err = crypto_shash_setkey(fs_info->csum_shash, ukp->data, ukp->datalen);
if (err)
btrfs_err(fs_info, "error setting key %s for verification",
fs_info->auth_key_name);

out:
if (err) {
btrfs_free_csum_hash(fs_info);
}

up_read(&key->sem);
key_put(key);

return err;

out_free_hash:
btrfs_free_csum_hash(fs_info);
return err;
}

static int btrfs_replay_log(struct btrfs_fs_info *fs_info,
Expand Down Expand Up @@ -3576,6 +3638,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device
btrfs_stop_all_workers(fs_info);
btrfs_free_block_groups(fs_info);
fail_alloc:
btrfs_free_csum_hash(fs_info);
btrfs_mapping_tree_free(&fs_info->mapping_tree);

iput(fs_info->btree_inode);
Expand Down
7 changes: 6 additions & 1 deletion fs/btrfs/ioctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,12 @@ static int btrfs_ioctl_setflags(struct file *file, void __user *arg)
else
binode_flags &= ~BTRFS_INODE_DIRSYNC;
if (fsflags & FS_NOCOW_FL) {
if (S_ISREG(inode->i_mode)) {
if (btrfs_test_opt(fs_info, AUTH_KEY)) {
btrfs_err(fs_info,
"Cannot set nodatacow or nodatasum on authenticated file-system");
ret = -EPERM;
goto out_unlock;
} else if (S_ISREG(inode->i_mode)) {
/*
* It's safe to turn csums off here, no extents exist.
* Otherwise we want the flag to reflect the real COW
Expand Down
51 changes: 50 additions & 1 deletion fs/btrfs/super.c
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,8 @@ enum {
Opt_thread_pool,
Opt_treelog, Opt_notreelog,
Opt_user_subvol_rm_allowed,
Opt_auth_key,
Opt_auth_hash_name,

/* Rescue options */
Opt_rescue,
Expand Down Expand Up @@ -431,6 +433,8 @@ static const match_table_t tokens = {
{Opt_treelog, "treelog"},
{Opt_notreelog, "notreelog"},
{Opt_user_subvol_rm_allowed, "user_subvol_rm_allowed"},
{Opt_auth_key, "auth_key=%s"},
{Opt_auth_hash_name, "auth_hash_name=%s"},

/* Rescue options */
{Opt_rescue, "rescue=%s"},
Expand Down Expand Up @@ -601,6 +605,11 @@ int btrfs_parse_options(struct btrfs_fs_info *info, char *options,
*/
break;
case Opt_nodatasum:
if (btrfs_test_opt(info, AUTH_KEY)) {
btrfs_info(info,
"nodatasum not supported on an authnticated file-system");
break;
}
btrfs_set_and_info(info, NODATASUM,
"setting nodatasum");
break;
Expand All @@ -616,6 +625,11 @@ int btrfs_parse_options(struct btrfs_fs_info *info, char *options,
btrfs_clear_opt(info->mount_opt, NODATASUM);
break;
case Opt_nodatacow:
if (btrfs_test_opt(info, AUTH_KEY)) {
btrfs_info(info,
"nodatacow not supported on an authnticated file-system");
break;
}
if (!btrfs_test_opt(info, NODATACOW)) {
if (!btrfs_test_opt(info, COMPRESS) ||
!btrfs_test_opt(info, FORCE_COMPRESS)) {
Expand Down Expand Up @@ -1070,7 +1084,8 @@ static int btrfs_parse_early_options(struct btrfs_fs_info *info,
continue;

token = match_token(p, tokens, args);
if (token == Opt_device) {
switch (token) {
case Opt_device:
device_name = match_strdup(&args[0]);
if (!device_name) {
error = -ENOMEM;
Expand All @@ -1083,9 +1098,40 @@ static int btrfs_parse_early_options(struct btrfs_fs_info *info,
error = PTR_ERR(device);
goto out;
}
break;
case Opt_auth_key:
info->auth_key_name = match_strdup(&args[0]);
if (!info->auth_key_name) {
error = -ENOMEM;
goto out;
}
break;
case Opt_auth_hash_name:
info->auth_hash_name = match_strdup(&args[0]);
if (!info->auth_hash_name) {
error = -ENOMEM;
goto out;
}
break;
default:
break;
}
}

/*
* Check that both auth_key_name and auth_hash_name are either present
* or absent and error out if only one of them is set.
*/
if (info->auth_key_name && info->auth_hash_name) {
btrfs_info(info, "doing authentication");
btrfs_set_opt(info->mount_opt, AUTH_KEY);
} else if ((info->auth_key_name && !info->auth_hash_name) ||
(!info->auth_key_name && info->auth_hash_name)) {
btrfs_err(info,
"auth_key and auth_hash_name must be supplied together");
error = -EINVAL;
}

out:
kfree(orig);
return error;
Expand Down Expand Up @@ -1525,6 +1571,8 @@ static int btrfs_show_options(struct seq_file *seq, struct dentry *dentry)
#endif
if (btrfs_test_opt(info, REF_VERIFY))
seq_puts(seq, ",ref_verify");
if (btrfs_test_opt(info, AUTH_KEY))
seq_printf(seq, ",auth_key=%s", info->auth_key_name);
seq_printf(seq, ",subvolid=%llu",
BTRFS_I(d_inode(dentry))->root->root_key.objectid);
subvol_name = btrfs_get_subvol_name_from_objectid(info,
Expand Down Expand Up @@ -2687,4 +2735,5 @@ MODULE_LICENSE("GPL");
MODULE_SOFTDEP("pre: crc32c");
MODULE_SOFTDEP("pre: xxhash64");
MODULE_SOFTDEP("pre: sha256");
MODULE_SOFTDEP("pre: hmac");
MODULE_SOFTDEP("pre: blake2b-256");
1 change: 1 addition & 0 deletions include/uapi/linux/btrfs_tree.h
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ enum btrfs_csum_type {
BTRFS_CSUM_TYPE_XXHASH = 1,
BTRFS_CSUM_TYPE_SHA256 = 2,
BTRFS_CSUM_TYPE_BLAKE2 = 3,
BTRFS_CSUM_TYPE_HMAC_SHA256 = 4,
};

/*
Expand Down

0 comments on commit 13be516

Please sign in to comment.