[PATCH 4/6] fscrypt: verify that the correct master key was supplied
Michael Halcrow
mhalcrow at google.com
Fri Jul 14 09:40:54 PDT 2017
On Wed, Jul 12, 2017 at 02:00:33PM -0700, Eric Biggers wrote:
> From: Eric Biggers <ebiggers at google.com>
>
> Currently, while a fscrypt master key is required to have a certain
> description in the keyring, its payload is never verified to be correct.
> While sufficient for well-behaved userspace, this is insecure in a
> multi-user system where a user has been given only read-only access to
> an encrypted file or directory. Specifically, if an encrypted file or
> directory does not yet have its key cached by the kernel, the first user
> who accesses it can provide an arbitrary key in their own keyring, which
> the kernel will then associate with the inode and use for read(),
> write(), readdir(), etc. by other users as well.
>
> Consequently, it's trivial for a user with *read-only* access to an
> encrypted file or directory to make it appear as garbage to everyone.
> Creative users might be able to accomplish more sophisticated attacks by
> careful choice of the key, e.g. choosing a key causes certain bytes of
> file contents to have certain values or that causes filenames containing
> the '/' character to appear.
>
> Solve the problem for v2 encryption policies by storing a "hash" of the
> master encryption key in the encryption xattr and verifying it before
> accepting the user-provided key. We generate the "hash" using
> HKDF-SHA512 by passing a distinct application-specific info string.
> This produces a value which is cryptographically isolated and can be
> stored in the clear without leaking any information about the master key
> or any other derived keys (in a computational sense). Reusing HKDF is
> better than doing e.g. SHA-512(master_key) because it avoids passing the
> same key into different cryptographic primitives.
>
> We make the hash field 16 bytes long, as this should provide sufficient
> collision and preimage resistance while not wasting too much space for
> the encryption xattr.
>
> Signed-off-by: Eric Biggers <ebiggers at google.com>
Acked-by: Michael Halcrow <mhalcrow at google.com>
> ---
> fs/crypto/fscrypt_private.h | 4 ++++
> fs/crypto/keyinfo.c | 46 +++++++++++++++++++++++++++++++++++++
> fs/crypto/policy.c | 55 ++++++++++++++++++++++++++++++++++++---------
> 3 files changed, 95 insertions(+), 10 deletions(-)
>
> diff --git a/fs/crypto/fscrypt_private.h b/fs/crypto/fscrypt_private.h
> index 095e7c16483a..a7baeac92575 100644
> --- a/fs/crypto/fscrypt_private.h
> +++ b/fs/crypto/fscrypt_private.h
> @@ -92,6 +92,7 @@ fscrypt_valid_context_format(const struct fscrypt_context *ctx, int size)
> struct fscrypt_master_key {
> struct crypto_shash *mk_hmac;
> unsigned int mk_size;
> + u8 mk_hash[FSCRYPT_KEY_HASH_SIZE];
> };
>
> /*
> @@ -155,6 +156,9 @@ extern struct page *fscrypt_alloc_bounce_page(struct fscrypt_ctx *ctx,
> gfp_t gfp_flags);
>
> /* keyinfo.c */
> +extern int fscrypt_compute_key_hash(const struct inode *inode,
> + const struct fscrypt_policy *policy,
> + u8 hash[FSCRYPT_KEY_HASH_SIZE]);
> extern void __exit fscrypt_essiv_cleanup(void);
>
> #endif /* _FSCRYPT_PRIVATE_H */
> diff --git a/fs/crypto/keyinfo.c b/fs/crypto/keyinfo.c
> index 7ed1a7fb1308..12a60eacf819 100644
> --- a/fs/crypto/keyinfo.c
> +++ b/fs/crypto/keyinfo.c
> @@ -39,8 +39,11 @@ static struct crypto_shash *essiv_hash_tfm;
> *
> * Keys derived with different info strings are cryptographically isolated from
> * each other --- knowledge of one derived key doesn't reveal any others.
> + * (This property is particularly important for the derived key used as the
> + * "key hash", as that is stored in the clear.)
> */
> #define HKDF_CONTEXT_PER_FILE_KEY 1
> +#define HKDF_CONTEXT_KEY_HASH 2
>
> /*
> * HKDF consists of two steps:
> @@ -212,6 +215,12 @@ alloc_master_key(const struct fscrypt_key *payload)
> err = crypto_shash_setkey(k->mk_hmac, prk, sizeof(prk));
> if (err)
> goto fail;
> +
> + /* Calculate the "key hash" */
> + err = hkdf_expand(k->mk_hmac, HKDF_CONTEXT_KEY_HASH, NULL, 0,
> + k->mk_hash, FSCRYPT_KEY_HASH_SIZE);
> + if (err)
> + goto fail;
> out:
> memzero_explicit(prk, sizeof(prk));
> return k;
> @@ -537,6 +546,31 @@ void __exit fscrypt_essiv_cleanup(void)
> crypto_free_shash(essiv_hash_tfm);
> }
>
> +int fscrypt_compute_key_hash(const struct inode *inode,
> + const struct fscrypt_policy *policy,
> + u8 hash[FSCRYPT_KEY_HASH_SIZE])
> +{
> + struct fscrypt_master_key *k;
> + unsigned int min_keysize;
> +
> + /*
> + * Require that the master key be long enough for both the
> + * contents and filenames encryption modes.
> + */
> + min_keysize =
> + max(available_modes[policy->contents_encryption_mode].keysize,
> + available_modes[policy->filenames_encryption_mode].keysize);
> +
> + k = load_master_key_from_keyring(inode, policy->master_key_descriptor,
> + min_keysize);
> + if (IS_ERR(k))
> + return PTR_ERR(k);
> +
> + memcpy(hash, k->mk_hash, FSCRYPT_KEY_HASH_SIZE);
> + put_master_key(k);
> + return 0;
> +}
> +
> int fscrypt_get_encryption_info(struct inode *inode)
> {
> struct fscrypt_info *crypt_info;
> @@ -613,6 +647,18 @@ int fscrypt_get_encryption_info(struct inode *inode)
> goto out;
> }
>
> + /*
> + * Make sure the master key we found has the correct hash.
> + * Buggy or malicious userspace may provide the wrong key.
> + */
> + if (memcmp(crypt_info->ci_master_key->mk_hash, ctx.key_hash,
> + FSCRYPT_KEY_HASH_SIZE)) {
> + pr_warn_ratelimited("fscrypt: wrong encryption key supplied for inode %lu\n",
> + inode->i_ino);
> + res = -ENOKEY;
> + goto out;
> + }
> +
> res = derive_key_hkdf(crypt_info->ci_master_key, &ctx,
> derived_key, derived_keysize);
> }
> diff --git a/fs/crypto/policy.c b/fs/crypto/policy.c
> index 81c59f8e45c0..2934bc2bff4b 100644
> --- a/fs/crypto/policy.c
> +++ b/fs/crypto/policy.c
> @@ -40,7 +40,8 @@ static u8 context_version_for_policy(const struct fscrypt_policy *policy)
> */
> static bool is_encryption_context_consistent_with_policy(
> const struct fscrypt_context *ctx,
> - const struct fscrypt_policy *policy)
> + const struct fscrypt_policy *policy,
> + const u8 key_hash[FSCRYPT_KEY_HASH_SIZE])
> {
> return (ctx->version == context_version_for_policy(policy)) &&
> (memcmp(ctx->master_key_descriptor,
> @@ -50,11 +51,14 @@ static bool is_encryption_context_consistent_with_policy(
> (ctx->contents_encryption_mode ==
> policy->contents_encryption_mode) &&
> (ctx->filenames_encryption_mode ==
> - policy->filenames_encryption_mode);
> + policy->filenames_encryption_mode) &&
> + (ctx->version == FSCRYPT_CONTEXT_V1 ||
> + (memcmp(ctx->key_hash, key_hash, FSCRYPT_KEY_HASH_SIZE) == 0));
> }
>
> static int create_encryption_context_from_policy(struct inode *inode,
> - const struct fscrypt_policy *policy)
> + const struct fscrypt_policy *policy,
> + const u8 key_hash[FSCRYPT_KEY_HASH_SIZE])
> {
> struct fscrypt_context ctx;
>
> @@ -74,7 +78,7 @@ static int create_encryption_context_from_policy(struct inode *inode,
> BUILD_BUG_ON(sizeof(ctx.nonce) != FS_KEY_DERIVATION_NONCE_SIZE);
> get_random_bytes(ctx.nonce, FS_KEY_DERIVATION_NONCE_SIZE);
> if (ctx.version != FSCRYPT_CONTEXT_V1)
> - memset(ctx.key_hash, 0, FSCRYPT_KEY_HASH_SIZE);
> + memcpy(ctx.key_hash, key_hash, FSCRYPT_KEY_HASH_SIZE);
>
> return inode->i_sb->s_cop->set_context(inode, &ctx,
> fscrypt_context_size(&ctx),
> @@ -87,6 +91,7 @@ int fscrypt_ioctl_set_policy(struct file *filp, const void __user *arg)
> struct inode *inode = file_inode(filp);
> int ret;
> struct fscrypt_context ctx;
> + u8 key_hash[FSCRYPT_KEY_HASH_SIZE];
>
> if (copy_from_user(&policy, arg, sizeof(policy)))
> return -EFAULT;
> @@ -98,6 +103,25 @@ int fscrypt_ioctl_set_policy(struct file *filp, const void __user *arg)
> policy.version != FS_POLICY_VERSION_HKDF)
> return -EINVAL;
>
> + if (policy.version == FS_POLICY_VERSION_ORIGINAL) {
> + /*
> + * Originally no key verification was implemented, which was
> + * insufficient for scenarios where multiple users share
> + * encrypted files. The new encryption policy version fixes
> + * this and also implements an improved key derivation function.
> + * So as long as the key can be in the keyring at the time the
> + * policy is set and compatibility with old kernels isn't
> + * required, it's recommended to use the new policy version
> + * (fscrypt_policy.version = 2).
> + */
> + pr_warn_once("%s (pid %d) is setting less secure v0 encryption policy; recommend upgrading to v2.\n",
> + current->comm, current->pid);
> + } else {
> + ret = fscrypt_compute_key_hash(inode, &policy, key_hash);
> + if (ret)
> + return ret;
> + }
> +
> ret = mnt_want_write_file(filp);
> if (ret)
> return ret;
> @@ -112,10 +136,12 @@ int fscrypt_ioctl_set_policy(struct file *filp, const void __user *arg)
> ret = -ENOTEMPTY;
> else
> ret = create_encryption_context_from_policy(inode,
> - &policy);
> + &policy,
> + key_hash);
> } else if (ret >= 0 && fscrypt_valid_context_format(&ctx, ret) &&
> is_encryption_context_consistent_with_policy(&ctx,
> - &policy)) {
> + &policy,
> + key_hash)) {
> /* The file already uses the same encryption policy. */
> ret = 0;
> } else if (ret >= 0 || ret == -ERANGE) {
> @@ -232,7 +258,11 @@ int fscrypt_has_permitted_context(struct inode *parent, struct inode *child)
> (parent_ci->ci_data_mode == child_ci->ci_data_mode) &&
> (parent_ci->ci_filename_mode ==
> child_ci->ci_filename_mode) &&
> - (parent_ci->ci_flags == child_ci->ci_flags);
> + (parent_ci->ci_flags == child_ci->ci_flags) &&
> + (parent_ci->ci_context_version == FSCRYPT_CONTEXT_V1 ||
> + (memcmp(parent_ci->ci_master_key->mk_hash,
> + child_ci->ci_master_key->mk_hash,
> + FSCRYPT_KEY_HASH_SIZE) == 0));
> }
>
> res = cops->get_context(parent, &parent_ctx, sizeof(parent_ctx));
> @@ -251,7 +281,10 @@ int fscrypt_has_permitted_context(struct inode *parent, struct inode *child)
> child_ctx.contents_encryption_mode) &&
> (parent_ctx.filenames_encryption_mode ==
> child_ctx.filenames_encryption_mode) &&
> - (parent_ctx.flags == child_ctx.flags);
> + (parent_ctx.flags == child_ctx.flags) &&
> + (parent_ctx.version == FSCRYPT_CONTEXT_V1 ||
> + (memcmp(parent_ctx.key_hash, child_ctx.key_hash,
> + FSCRYPT_KEY_HASH_SIZE) == 0));
> }
> EXPORT_SYMBOL(fscrypt_has_permitted_context);
>
> @@ -286,8 +319,10 @@ int fscrypt_inherit_context(struct inode *parent, struct inode *child,
> memcpy(ctx.master_key_descriptor, ci->ci_master_key_descriptor,
> FS_KEY_DESCRIPTOR_SIZE);
> get_random_bytes(ctx.nonce, FS_KEY_DERIVATION_NONCE_SIZE);
> - if (ctx.version != FSCRYPT_CONTEXT_V1)
> - memset(ctx.key_hash, 0, FSCRYPT_KEY_HASH_SIZE);
> + if (ctx.version != FSCRYPT_CONTEXT_V1) {
> + memcpy(ctx.key_hash, ci->ci_master_key->mk_hash,
> + FSCRYPT_KEY_HASH_SIZE);
> + }
>
> BUILD_BUG_ON(sizeof(ctx) != FSCRYPT_SET_CONTEXT_MAX_SIZE);
> res = parent->i_sb->s_cop->set_context(child, &ctx,
> --
> 2.13.2.932.g7449e964c-goog
>
More information about the linux-mtd
mailing list