[PATCH] fscrypt: support passing a keyring key to FS_IOC_ADD_ENCRYPTION_KEY

Jarkko Sakkinen jarkko.sakkinen at linux.intel.com
Fri Nov 15 09:28:53 PST 2019


On Wed, Nov 06, 2019 at 04:12:59PM -0800, Eric Biggers wrote:
> From: Eric Biggers <ebiggers at google.com>
> 
> Extend the FS_IOC_ADD_ENCRYPTION_KEY ioctl to allow the raw key to be
> specified by a Linux keyring key, rather than specified directly.
> 
> This is useful because fscrypt keys belong to a particular filesystem
> instance, so they are destroyed when that filesystem is unmounted.
> Usually this is desired.  But in some cases, userspace may need to
> unmount and re-mount the filesystem while keeping the keys, e.g. during
> a system update.  This requires keeping the keys somewhere else too.
> 
> The keys could be kept in memory in a userspace daemon.  But depending
> on the security architecture and assumptions, it can be preferable to
> keep them only in kernel memory, where they are unreadable by userspace.
> 
> We also can't solve this by going back to the original fscrypt API
> (where for each file, the master key was looked up in the process's
> keyring hierarchy) because that caused lots of problems of its own.
> 
> Therefore, add the ability for FS_IOC_ADD_ENCRYPTION_KEY to accept a
> Linux keyring key.  This solves the problem by allowing userspace to (if
> needed) save the keys securely in a Linux keyring for re-provisioning,
> while still using the new fscrypt key management ioctls.
> 
> This is analogous to how dm-crypt accepts a Linux keyring key, but the
> key is then stored internally in the dm-crypt data structures rather
> than being looked up again each time the dm-crypt device is accessed.
> 
> Use a custom key type "fscrypt-provisioning" rather than one of the
> existing key types such as "logon".  This is strongly desired because it
> enforces that these keys are only usable for a particular purpose: for
> fscrypt as input to a particular KDF.  Otherwise, the keys could also be
> passed to any kernel API that accepts a "logon" key with any service
> prefix, e.g. dm-crypt, UBIFS, or (recently proposed) AF_ALG.  This would
> risk leaking information about the raw key despite it ostensibly being
> unreadable.  Of course, this mistake has already been made for multiple
> kernel APIs; but since this is a new API, let's do it right.
> 
> Signed-off-by: Eric Biggers <ebiggers at google.com>
> ---
>  Documentation/filesystems/fscrypt.rst |  35 ++++++-
>  fs/crypto/keyring.c                   | 126 ++++++++++++++++++++++++--
>  include/uapi/linux/fscrypt.h          |  13 ++-
>  3 files changed, 162 insertions(+), 12 deletions(-)
> 
> diff --git a/Documentation/filesystems/fscrypt.rst b/Documentation/filesystems/fscrypt.rst
> index 471a511c75088d..4d15dda36402e0 100644
> --- a/Documentation/filesystems/fscrypt.rst
> +++ b/Documentation/filesystems/fscrypt.rst
> @@ -638,7 +638,8 @@ follows::
>      struct fscrypt_add_key_arg {
>              struct fscrypt_key_specifier key_spec;
>              __u32 raw_size;
> -            __u32 __reserved[9];
> +            __u32 key_id;
> +            __u32 __reserved[8];
>              __u8 raw[];
>      };
>  
> @@ -655,6 +656,12 @@ follows::
>              } u;
>      };
>  
> +    struct fscrypt_key_provisioning_payload {
> +            __u32 type;
> +            __u32 __reserved;
> +            __u8 raw[];
> +    };
> +
>  :c:type:`struct fscrypt_add_key_arg` must be zeroed, then initialized
>  as follows:
>  
> @@ -677,9 +684,26 @@ as follows:
>    ``Documentation/security/keys/core.rst``).
>  
>  - ``raw_size`` must be the size of the ``raw`` key provided, in bytes.
> +  Alternatively, if ``key_id`` is nonzero, this field must be 0, since
> +  in that case the size is implied by the specified Linux keyring key.
> +
> +- ``key_id`` is 0 if the raw key is given directly in the ``raw``
> +  field.  Otherwise ``key_id`` is the ID of a Linux keyring key of
> +  type "fscrypt-provisioning" whose payload is a ``struct
> +  fscrypt_key_provisioning_payload`` whose ``raw`` field contains the
> +  raw key and whose ``type`` field matches ``key_spec.type``.  Since
> +  ``raw`` is variable-length, the total size of this key's payload
> +  must be ``sizeof(struct fscrypt_key_provisioning_payload)`` plus the
> +  raw key size.  The process must have Search permission on this key.
> +
> +  Most users should leave this 0 and specify the raw key directly.
> +  The support for specifying a Linux keyring key is intended mainly to
> +  allow re-adding keys after a filesystem is unmounted and re-mounted,
> +  without having to store the raw keys in userspace memory.
>  
>  - ``raw`` is a variable-length field which must contain the actual
> -  key, ``raw_size`` bytes long.
> +  key, ``raw_size`` bytes long.  Alternatively, if ``key_id`` is
> +  nonzero, then this field is unused.
>  
>  For v2 policy keys, the kernel keeps track of which user (identified
>  by effective user ID) added the key, and only allows the key to be
> @@ -701,11 +725,16 @@ FS_IOC_ADD_ENCRYPTION_KEY can fail with the following errors:
>  
>  - ``EACCES``: FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR was specified, but the
>    caller does not have the CAP_SYS_ADMIN capability in the initial
> -  user namespace
> +  user namespace; or the raw key was specified by Linux key ID but the
> +  process lacks Search permission on the key.
>  - ``EDQUOT``: the key quota for this user would be exceeded by adding
>    the key
>  - ``EINVAL``: invalid key size or key specifier type, or reserved bits
>    were set
> +- ``EKEYREJECTED``: the raw key was specified by Linux key ID, but the
> +  key has the wrong type
> +- ``ENOKEY``: the raw key was specified by Linux key ID, but no key
> +  exists with that ID
>  - ``ENOTTY``: this type of filesystem does not implement encryption
>  - ``EOPNOTSUPP``: the kernel was not configured with encryption
>    support for this filesystem, or the filesystem superblock has not
> diff --git a/fs/crypto/keyring.c b/fs/crypto/keyring.c
> index 040df1f5e1c8b1..ef5b171c0f1d64 100644
> --- a/fs/crypto/keyring.c
> +++ b/fs/crypto/keyring.c
> @@ -465,6 +465,103 @@ static int add_master_key(struct super_block *sb,
>  	return err;
>  }
>  
> +static int fscrypt_provisioning_key_preparse(struct key_preparsed_payload *prep)
> +{
> +	const struct fscrypt_key_provisioning_payload *payload = prep->data;
> +
> +	if (prep->datalen < sizeof(*payload) + FSCRYPT_MIN_KEY_SIZE ||
> +	    prep->datalen > sizeof(*payload) + FSCRYPT_MAX_KEY_SIZE)
> +		return -EINVAL;

<empty line>

> +	if (payload->type != FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR &&
> +	    payload->type != FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER)
> +		return -EINVAL;

<empty line>

> +	if (payload->__reserved)
> +		return -EINVAL;

<empty line>

> +	prep->payload.data[0] = kmemdup(payload, prep->datalen, GFP_KERNEL);
> +	if (!prep->payload.data[0])
> +		return -ENOMEM;

<empty line>

> +	prep->quotalen = prep->datalen;

<empty line>

> +	return 0;
> +}
> +
> +static void fscrypt_provisioning_key_free_preparse(
> +					struct key_preparsed_payload *prep)
> +{
> +	kzfree(prep->payload.data[0]);
> +}
> +
> +static void fscrypt_provisioning_key_describe(const struct key *key,
> +					      struct seq_file *m)
> +{
> +	seq_puts(m, key->description);
> +	if (key_is_positive(key)) {
> +		const struct fscrypt_key_provisioning_payload *payload =
> +			key->payload.data[0];
> +
> +		seq_printf(m, ": %u [%u]", key->datalen, payload->type);
> +	}
> +}
> +
> +static void fscrypt_provisioning_key_destroy(struct key *key)
> +{
> +	kzfree(key->payload.data[0]);
> +}
> +
> +static struct key_type key_type_fscrypt_provisioning = {
> +	.name			= "fscrypt-provisioning",
> +	.preparse		= fscrypt_provisioning_key_preparse,
> +	.free_preparse		= fscrypt_provisioning_key_free_preparse,
> +	.instantiate		= generic_key_instantiate,
> +	.describe		= fscrypt_provisioning_key_describe,
> +	.destroy		= fscrypt_provisioning_key_destroy,
> +};
> +
> +/*
> + * Retrieve the raw key from the Linux keyring key specified by 'key_id', and
> + * store it into 'secret'.
> + *
> + * The key must be of type "fscrypt-provisioning" and must have the field
> + * fscrypt_key_provisioning_payload::type set to 'type', indicating that it's
> + * only usable with fscrypt with the particular KDF version identified by
> + * 'type'.  We don't use the "logon" key type because there's no way to
> + * completely restrict the use of such keys; they can be used by any kernel API
> + * that accepts "logon" keys and doesn't require a specific service prefix.
> + *
> + * The ability to specify the key via Linux keyring key is intended for cases
> + * where userspace needs to re-add keys after the filesystem is unmounted and
> + * re-mounted.  Most users should just provide the raw key directly instead.
> + */
> +static int get_keyring_key(u32 key_id, u32 type,
> +			   struct fscrypt_master_key_secret *secret)
> +{
> +	key_ref_t ref;
> +	struct key *key;
> +	const struct fscrypt_key_provisioning_payload *payload;
> +	int err;
> +
> +	ref = lookup_user_key(key_id, 0, KEY_NEED_SEARCH);
> +	if (IS_ERR(ref))
> +		return PTR_ERR(ref);

<empty line>

> +	key = key_ref_to_ptr(ref);
> +	if (key->type != &key_type_fscrypt_provisioning)
> +		goto bad_key;

<empty line>

> +	payload = key->payload.data[0];
> +
> +	/* Don't allow fscrypt v1 keys to be used as v2 keys and vice versa. */
> +	if (payload->type != type)
> +		goto bad_key;
> +
> +	secret->size = key->datalen - sizeof(*payload);
> +	memcpy(secret->raw, payload->raw, secret->size);
> +	err = 0;
> +	goto out_put;

<empty line>

> +bad_key:
> +	err = -EKEYREJECTED;

<empty line>

> +out_put:
> +	key_ref_put(ref);
> +	return err;
> +}
> +
>  /*
>   * Add a master encryption key to the filesystem, causing all files which were
>   * encrypted with it to appear "unlocked" (decrypted) when accessed.
> @@ -503,18 +600,25 @@ int fscrypt_ioctl_add_key(struct file *filp, void __user *_uarg)
>  	if (!valid_key_spec(&arg.key_spec))
>  		return -EINVAL;
>  
> -	if (arg.raw_size < FSCRYPT_MIN_KEY_SIZE ||
> -	    arg.raw_size > FSCRYPT_MAX_KEY_SIZE)
> -		return -EINVAL;
> -
>  	if (memchr_inv(arg.__reserved, 0, sizeof(arg.__reserved)))
>  		return -EINVAL;
>  
>  	memset(&secret, 0, sizeof(secret));
> -	secret.size = arg.raw_size;
> -	err = -EFAULT;
> -	if (copy_from_user(secret.raw, uarg->raw, secret.size))
> -		goto out_wipe_secret;
> +	if (arg.key_id) {
> +		if (arg.raw_size != 0)
> +			return -EINVAL;
> +		err = get_keyring_key(arg.key_id, arg.key_spec.type, &secret);
> +		if (err)
> +			goto out_wipe_secret;
> +	} else {
> +		if (arg.raw_size < FSCRYPT_MIN_KEY_SIZE ||
> +		    arg.raw_size > FSCRYPT_MAX_KEY_SIZE)
> +			return -EINVAL;
> +		secret.size = arg.raw_size;
> +		err = -EFAULT;
> +		if (copy_from_user(secret.raw, uarg->raw, secret.size))
> +			goto out_wipe_secret;
> +	}
>  
>  	switch (arg.key_spec.type) {
>  	case FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR:
> @@ -978,8 +1082,14 @@ int __init fscrypt_init_keyring(void)
>  	if (err)
>  		goto err_unregister_fscrypt;
>  
> +	err = register_key_type(&key_type_fscrypt_provisioning);
> +	if (err)
> +		goto err_unregister_fscrypt_user;
> +
>  	return 0;
>  
> +err_unregister_fscrypt_user:
> +	unregister_key_type(&key_type_fscrypt_user);
>  err_unregister_fscrypt:
>  	unregister_key_type(&key_type_fscrypt);
>  	return err;
> diff --git a/include/uapi/linux/fscrypt.h b/include/uapi/linux/fscrypt.h
> index 1beb174ad95056..605dde7343a4e4 100644
> --- a/include/uapi/linux/fscrypt.h
> +++ b/include/uapi/linux/fscrypt.h
> @@ -109,11 +109,22 @@ struct fscrypt_key_specifier {
>  	} u;
>  };
>  
> +/*
> + * Payload for Linux keyring key of type "fscrypt-provisioning", referenced by
> + * fscrypt_add_key_arg::key_id as an alternative to fscrypt_add_key_arg::raw.
> + */
> +struct fscrypt_key_provisioning_payload {
> +	__u32 type;
> +	__u32 __reserved;
> +	__u8 raw[];
> +};
> +
>  /* Struct passed to FS_IOC_ADD_ENCRYPTION_KEY */
>  struct fscrypt_add_key_arg {
>  	struct fscrypt_key_specifier key_spec;
>  	__u32 raw_size;
> -	__u32 __reserved[9];
> +	__u32 key_id;
> +	__u32 __reserved[8];
>  	__u8 raw[];
>  };
>  
> -- 
> 2.24.0.rc1.363.gb1bccd3e3d-goog
> 

I don't see anything obviously wrong. Just would reformat it a bit.
How you tested it?

/Jarkko



More information about the linux-mtd mailing list