From: Eric Biggers <hidden> Date: 2017-10-23 21:42:08
From: Eric Biggers <redacted>
Hello,
This patchset solves multiple interrelated problems with how filesystem
encryption keys are managed (for ext4, f2fs, and ubifs), including:
(1) There is a visibility mismatch between the filesystem/VFS "view" of
encrypted files (which is global) and the process-subscribed
keyrings (which are not global). Relying on process-subscribed
keyrings to provide the encryption keys on-demand makes it quite
difficult to support even simple things like running 'sudo', if
encrypted files need to be accessed.
(2) There is no API to securely remove an encryption key, which should
wipe all secret keys from memory and revert the encrypted files to
their ciphertext "view". Many users want this, even to the extent
that they're already working around it using the very bad hack of
'echo 2 > /proc/sys/vm/drop_caches', or alternatively hacking in an
ioctl to drop caches for a specific filesystem.
(3) The key derivation function (KDF) used to derive the per-file
encryption keys is nonstandard and has a number of problems, such as
being trivially reversible. We've wanted to replace it for some
time now.
(4) There is no verification that the correct master key was supplied.
This is actually a security vulnerability, as it allows malicious
local users to associate the wrong key with files to which they have
*read-only* access.
This patchset is based loosely on my earlier patchset "fscrypt: key
verification and KDF improvement". However, while the earlier patchset
solved problems (3) and (4) above, it ignored (1) and (2).
Consequently, it ended up with a solution which probably would have had
to be reworked when we also solved (1) and (2). For example, the
'key_hash' field was hacked on to the existing on-disk format just to
solve (4), but really we need it a different way to solve (1) and (2) as
well, at least for non-root users. There was also a filesystem-level
key cache hacked on for caching the HMAC transforms for HKDF, but really
it should be a real keyring which you can add and remove keys from, as
we need that anyway for (1) and (2).
By considering all the problems together we end up with a solution which
should be simpler in the end, notwithstanding the length of this
patchset.
This patchset is organized as follows:
- Patches 1-6 introduce a filesystem-level crypto keyring and a new
ioctl, FS_IOC_ADD_ENCRYPTION_KEY, which adds a master encryption key
to it. This solves problem (1) above, though initially only for use
cases where a privileged process sets up the keys. Patch 20 will make
it unprivileged in some cases.
- Patches 7-10 add a new ioctl, FS_IOC_REMOVE_ENCRYPTION_KEY, which
removes a master encryption key from the filesystem-level crypto
keyring. It also evicts the inodes which had been "unlocked" using
the key. This solves problem (2) above, though initially only for use
cases where a privileged process sets up the keys. Patch 20 will make
it unprivileged in some cases.
- Patch 11 adds an ioctl FS_IOC_GET_ENCRYPTION_KEY_STATUS which
retrieves the status of a key in the filesystem-level crypto keyring.
- Patches 12-14 wire up the above ioctls to ext4, f2fs, and ubifs.
- Patches 15-25 introduce a new encryption policy version ("v2") where
master_key_descriptor is replaced with master_key_identifier, which is
a cryptographic hash of the master key. This allows opening the
FS_IOC_ADD_ENCRYPTION_KEY and FS_IOC_REMOVE_ENCRYPTION_KEY ioctls up
to non-root users. In turn, this avoids any need to rely on the
process-subscribed keyrings and encounter their visibility problems,
and it allows non-root users to securely remove their encryption keys.
I also take the opportunity to replace the AES-ECB-based KDF with
HKDF-SHA512, which is also used to compute the master_key_identifier
so that we pass the master key into only a single cryptographic
primitive.
Note that patches 1-14 can be reviewed (and potentially even merged) on
their own, without patches 15-25. At just that point, the ioctls to
manage filesystem-level keys will be usable for existing encrypted
files, for privileged users only. However, to understand some of the
decisions made when designing the ioctls, it will be helpful to see how
the later patches extend the ioctls to also be usable for v2 encryption
policies and by unprivileged users.
Please review all API and on-disk format changes carefully, as we will
be locked into them once merged.
You can also get this patchset from git at:
Repository: https://github.com/ebiggers/linux.git
Branch: fscrypt-v2-policy-and-api_v1
It has received light testing. I've also made proof-of-concept changes
to the 'fscrypt' userspace program to make it support v2 encryption
policies and the filesystem-level keyring. You can find those userspace
changes in git at:
Repository: https://github.com/ebiggers/fscrypt.git
Branch: v2-policy-support
To make the 'fscrypt' userspace program use v2 policies for new
encrypted directories, add
"policy_version": "2"
to /etc/fscrypt.conf within the "options" section. (Again: for now
please consider the userspace changes proof-of-concept quality only!
So far I've been focusing on the kernel changes.)
It's intended that the other major users of filesystem-level encryption,
including the Android and Chromium OS key management systems, will
switch to the new API and encryption policy version as well.
Eric Biggers (25):
fs, fscrypt: move uapi definitions to new header <linux/fscrypt.h>
fscrypt: use FSCRYPT_ prefix for uapi constants
fscrypt: use FSCRYPT_* definitions, not FS_*
fscrypt: refactor finding and deriving key
fs: add ->s_master_keys to struct super_block
fscrypt: add FS_IOC_ADD_ENCRYPTION_KEY ioctl
fs/inode.c: export inode_lru_list_del()
fs/inode.c: rename and export dispose_list()
fs/dcache.c: add shrink_dcache_inode()
fscrypt: add FS_IOC_REMOVE_ENCRYPTION_KEY ioctl
fscrypt: add FS_IOC_GET_ENCRYPTION_KEY_STATUS ioctl
ext4 crypto: wire up new ioctls for managing encryption keys
f2fs crypto: wire up new ioctls for managing encryption keys
ubifs crypto: wire up new ioctls for managing encryption keys
fscrypt: add UAPI definitions to get/set v2 encryption policies
fscrypt: implement basic handling of v2 encryption policies
fscrypt: add an HKDF-SHA512 implementation
fscrypt: allow adding and removing keys for v2 encryption policies
fscrypt: use HKDF-SHA512 to derive the per-file keys for v2 policies
fscrypt: allow unprivileged users to add/remove keys for v2 policies
fscrypt: require that key be added when setting a v2 encryption policy
ext4 crypto: wire up FS_IOC_GET_ENCRYPTION_POLICY_EX
f2fs crypto: wire up FS_IOC_GET_ENCRYPTION_POLICY_EX
ubifs crypto: wire up FS_IOC_GET_ENCRYPTION_POLICY_EX
fscrypt: document the new ioctls and policy version
Documentation/filesystems/fscrypt.rst | 575 ++++++++++--
fs/crypto/Kconfig | 2 +
fs/crypto/crypto.c | 19 +-
fs/crypto/fname.c | 4 +-
fs/crypto/fscrypt_private.h | 196 +++-
fs/crypto/keyinfo.c | 1619 ++++++++++++++++++++++++++++++---
fs/crypto/policy.c | 373 +++++---
fs/dcache.c | 33 +
fs/ext4/ioctl.c | 22 +
fs/f2fs/file.c | 21 +-
fs/inode.c | 24 +-
fs/super.c | 3 +
fs/ubifs/ioctl.c | 24 +-
include/linux/dcache.h | 1 +
include/linux/fs.h | 6 +
include/linux/fscrypt.h | 12 +-
include/linux/fscrypt_notsupp.h | 23 +
include/linux/fscrypt_supp.h | 4 +
include/uapi/linux/fs.h | 50 +-
include/uapi/linux/fscrypt.h | 159 ++++
20 files changed, 2724 insertions(+), 446 deletions(-)
create mode 100644 include/uapi/linux/fscrypt.h
--
2.15.0.rc0.271.g36b669edcc-goog
From: Eric Biggers <hidden> Date: 2017-10-23 21:42:11
From: Eric Biggers <redacted>
There are going to be more filesystem encryption definitions added, and
we don't want to use a disproportionate amount of space in <linux/fs.h>
for filesystem encryption stuff. So move the fscrypt definitions to a
new header <linux/fscrypt.h>.
For compatibility with existing userspace programs which may be
including <linux/fs.h>, <linux/fs.h> still includes the new header.
(It's debatable whether we really need this, though; the filesystem
encryption API is new enough that most if not all programs that are
using it have to declare it themselves anyway.)
Signed-off-by: Eric Biggers <redacted>
---
include/linux/fscrypt.h | 2 +-
include/uapi/linux/fs.h | 50 +++--------------------------------------
include/uapi/linux/fscrypt.h | 53 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 57 insertions(+), 48 deletions(-)
create mode 100644 include/uapi/linux/fscrypt.h
@@ -0,0 +1,53 @@+#ifndef _UAPI_LINUX_FSCRYPT_H+#define _UAPI_LINUX_FSCRYPT_H++#include<linux/types.h>++/*+*Filesystemencryptionsupport+*/+/* Policy provided via an ioctl on the topmost directory */+#define FS_KEY_DESCRIPTOR_SIZE 8++#define FS_POLICY_FLAGS_PAD_4 0x00+#define FS_POLICY_FLAGS_PAD_8 0x01+#define FS_POLICY_FLAGS_PAD_16 0x02+#define FS_POLICY_FLAGS_PAD_32 0x03+#define FS_POLICY_FLAGS_PAD_MASK 0x03+#define FS_POLICY_FLAGS_VALID 0x03++/* Encryption algorithms */+#define FS_ENCRYPTION_MODE_INVALID 0+#define FS_ENCRYPTION_MODE_AES_256_XTS 1+#define FS_ENCRYPTION_MODE_AES_256_GCM 2+#define FS_ENCRYPTION_MODE_AES_256_CBC 3+#define FS_ENCRYPTION_MODE_AES_256_CTS 4+#define FS_ENCRYPTION_MODE_AES_128_CBC 5+#define FS_ENCRYPTION_MODE_AES_128_CTS 6++structfscrypt_policy{+__u8version;+__u8contents_encryption_mode;+__u8filenames_encryption_mode;+__u8flags;+__u8master_key_descriptor[FS_KEY_DESCRIPTOR_SIZE];+};++#define FS_IOC_SET_ENCRYPTION_POLICY _IOR('f', 19, struct fscrypt_policy)+#define FS_IOC_GET_ENCRYPTION_PWSALT _IOW('f', 20, __u8[16])+#define FS_IOC_GET_ENCRYPTION_POLICY _IOW('f', 21, struct fscrypt_policy)++/* Parameters for passing an encryption key into the kernel keyring */+#define FS_KEY_DESC_PREFIX "fscrypt:"+#define FS_KEY_DESC_PREFIX_SIZE 8++/* Structure that userspace passes to the kernel keyring */+#define FS_MAX_KEY_SIZE 64++structfscrypt_key{+__u32mode;+__u8raw[FS_MAX_KEY_SIZE];+__u32size;+};++#endif /* _UAPI_LINUX_FSCRYPT_H */
--
2.15.0.rc0.271.g36b669edcc-goog
------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
From: Eric Biggers <hidden> Date: 2017-10-23 21:42:14
From: Eric Biggers <redacted>
Prefix all filesystem encryption UAPI constants except the ioctl numbers
with "FSCRYPT_" rather than with "FS_". This namespaces the constants
more appropriately and makes it clear that they are related specifically
to the filesystem encryption feature, and to the 'fscrypt_*' structures.
With some of the old names like "FS_POLICY_FLAGS_VALID", it was not
immediately clear that the constant had anything to do with encryption.
This is also useful because we'll be adding more encryption-related
constants, e.g. for the policy version, and we'd otherwise have to
choose whether to use unclear names like FS_POLICY_VERSION_* or
inconsistent names like FS_ENCRYPTION_POLICY_VERSION_*.
For source compatibility with older userspace programs, keep the old
names defined as aliases to the new ones. (It's debatable whether we
really need this, though; the filesystem encryption API is new enough
that most if not all programs that are using it have to declare it
themselves anyway.)
Signed-off-by: Eric Biggers <redacted>
---
Documentation/filesystems/fscrypt.rst | 14 ++++-----
include/uapi/linux/fscrypt.h | 59 ++++++++++++++++++++++++-----------
2 files changed, 47 insertions(+), 26 deletions(-)
@@ -251,14 +251,14 @@ empty directory or verifies that a directory or regular file already has the specified encryption policy. It takes in a pointer to a:c:type:`struct fscrypt_policy`, defined as follows::- #define FS_KEY_DESCRIPTOR_SIZE 8+ #define FSCRYPT_KEY_DESCRIPTOR_SIZE 8 struct fscrypt_policy { __u8 version; __u8 contents_encryption_mode; __u8 filenames_encryption_mode; __u8 flags;- __u8 master_key_descriptor[FS_KEY_DESCRIPTOR_SIZE];+ __u8 master_key_descriptor[FSCRYPT_KEY_DESCRIPTOR_SIZE]; }; This structure must be initialized as follows:
@@ -274,7 +274,7 @@ This structure must be initialized as follows:-``flags`` must be set to a value from ``<linux/fs.h>`` which identifies the amount of NUL-padding to use when encrypting- filenames. If unsure, use FS_POLICY_FLAGS_PAD_32 (0x3).+ filenames. If unsure, use FSCRYPT_POLICY_FLAGS_PAD_32 (0x3).-``master_key_descriptor`` specifies how to find the master key in the keyring; see `Adding keys`_. It is up to userspace to choose a
@@ -374,11 +374,11 @@ followed by the 16-character lower case hex representation of the``master_key_descriptor`` that was set in the encryption policy. The key payload must conform to the following structure::- #define FS_MAX_KEY_SIZE 64+ #define FSCRYPT_MAX_KEY_SIZE 64 struct fscrypt_key { u32 mode;- u8 raw[FS_MAX_KEY_SIZE];+ u8 raw[FSCRYPT_MAX_KEY_SIZE]; u32 size; };
@@ -533,7 +533,7 @@ much confusion if an encryption policy were to be added to or removed from anything other than an empty directory.) The struct is defined as follows::- #define FS_KEY_DESCRIPTOR_SIZE 8+ #define FSCRYPT_KEY_DESCRIPTOR_SIZE 8 #define FS_KEY_DERIVATION_NONCE_SIZE 16 struct fscrypt_context {
@@ -38,16 +39,36 @@ struct fscrypt_policy {#define FS_IOC_GET_ENCRYPTION_POLICY _IOW('f', 21, struct fscrypt_policy)/* Parameters for passing an encryption key into the kernel keyring */-#define FS_KEY_DESC_PREFIX "fscrypt:"-#define FS_KEY_DESC_PREFIX_SIZE 8+#define FSCRYPT_KEY_DESC_PREFIX "fscrypt:"+#define FSCRYPT_KEY_DESC_PREFIX_SIZE 8/* Structure that userspace passes to the kernel keyring */-#define FS_MAX_KEY_SIZE 64+#define FSCRYPT_MAX_KEY_SIZE 64structfscrypt_key{__u32mode;-__u8raw[FS_MAX_KEY_SIZE];+__u8raw[FSCRYPT_MAX_KEY_SIZE];__u32size;};+/**********************************************************************/++/* old names; don't add anything new here! */+#define FS_POLICY_FLAGS_PAD_4 FSCRYPT_POLICY_FLAGS_PAD_4+#define FS_POLICY_FLAGS_PAD_8 FSCRYPT_POLICY_FLAGS_PAD_8+#define FS_POLICY_FLAGS_PAD_16 FSCRYPT_POLICY_FLAGS_PAD_16+#define FS_POLICY_FLAGS_PAD_32 FSCRYPT_POLICY_FLAGS_PAD_32+#define FS_POLICY_FLAGS_PAD_MASK FSCRYPT_POLICY_FLAGS_PAD_MASK+#define FS_POLICY_FLAGS_VALID FSCRYPT_POLICY_FLAGS_VALID+#define FS_KEY_DESCRIPTOR_SIZE FSCRYPT_KEY_DESCRIPTOR_SIZE+#define FS_ENCRYPTION_MODE_INVALID FSCRYPT_MODE_INVALID+#define FS_ENCRYPTION_MODE_AES_256_XTS FSCRYPT_MODE_AES_256_XTS+#define FS_ENCRYPTION_MODE_AES_256_GCM FSCRYPT_MODE_AES_256_GCM+#define FS_ENCRYPTION_MODE_AES_256_CBC FSCRYPT_MODE_AES_256_CBC+#define FS_ENCRYPTION_MODE_AES_256_CTS FSCRYPT_MODE_AES_256_CTS+#define FS_ENCRYPTION_MODE_AES_128_CBC FSCRYPT_MODE_AES_128_CBC+#define FS_ENCRYPTION_MODE_AES_128_CTS FSCRYPT_MODE_AES_128_CTS+#define FS_KEY_DESC_PREFIX FSCRYPT_KEY_DESC_PREFIX+#define FS_KEY_DESC_PREFIX_SIZE FSCRYPT_KEY_DESC_PREFIX_SIZE+#define FS_MAX_KEY_SIZE FSCRYPT_MAX_KEY_SIZE#endif /* _UAPI_LINUX_FSCRYPT_H */
--
2.15.0.rc0.271.g36b669edcc-goog
------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
From: Eric Biggers <hidden> Date: 2017-10-23 21:42:14
From: Eric Biggers <redacted>
Update the filesystem encryption kernel code to use the new names for
the UAPI constants rather than the old names.
Signed-off-by: Eric Biggers <redacted>
---
fs/crypto/fname.c | 4 ++--
fs/crypto/fscrypt_private.h | 4 ++--
fs/crypto/keyinfo.c | 36 +++++++++++++++++-------------------
fs/crypto/policy.c | 14 +++++++-------
include/linux/fscrypt.h | 8 ++++----
5 files changed, 32 insertions(+), 34 deletions(-)
@@ -278,9 +275,10 @@ int fscrypt_get_encryption_info(struct inode *inode)/* Fake up a context for an unencrypted directory */memset(&ctx,0,sizeof(ctx));ctx.format=FS_ENCRYPTION_CONTEXT_FORMAT_V1;-ctx.contents_encryption_mode=FS_ENCRYPTION_MODE_AES_256_XTS;-ctx.filenames_encryption_mode=FS_ENCRYPTION_MODE_AES_256_CTS;-memset(ctx.master_key_descriptor,0x42,FS_KEY_DESCRIPTOR_SIZE);+ctx.contents_encryption_mode=FSCRYPT_MODE_AES_256_XTS;+ctx.filenames_encryption_mode=FSCRYPT_MODE_AES_256_CTS;+memset(ctx.master_key_descriptor,0x42,+FSCRYPT_KEY_DESCRIPTOR_SIZE);}elseif(res!=sizeof(ctx)){return-EINVAL;}
@@ -288,7 +286,7 @@ int fscrypt_get_encryption_info(struct inode *inode)if(ctx.format!=FS_ENCRYPTION_CONTEXT_FORMAT_V1)return-EINVAL;-if(ctx.flags&~FS_POLICY_FLAGS_VALID)+if(ctx.flags&~FSCRYPT_POLICY_FLAGS_VALID)return-EINVAL;crypt_info=kmem_cache_alloc(fscrypt_info_cachep,GFP_NOFS);
@@ -312,12 +310,12 @@ int fscrypt_get_encryption_info(struct inode *inode)*cryptoAPIaspartofkeyderivation.*/res=-ENOMEM;-raw_key=kmalloc(FS_MAX_KEY_SIZE,GFP_NOFS);+raw_key=kmalloc(FSCRYPT_MAX_KEY_SIZE,GFP_NOFS);if(!raw_key)gotoout;-res=validate_user_key(crypt_info,&ctx,raw_key,FS_KEY_DESC_PREFIX,-keysize);+res=validate_user_key(crypt_info,&ctx,raw_key,+FSCRYPT_KEY_DESC_PREFIX,keysize);if(res&&inode->i_sb->s_cop->key_prefix){intres2=validate_user_key(crypt_info,&ctx,raw_key,inode->i_sb->s_cop->key_prefix,
From: Eric Biggers <hidden> Date: 2017-10-23 21:42:16
From: Eric Biggers <redacted>
In preparation for introducing a new way to find the master keys and
derive the per-file keys, clean up the current method. This includes:
- Introduce a helper function find_and_derive_key() so that we don't
have to add more code directly to fscrypt_get_encryption_info().
- Don't pass the 'struct fscrypt_key' directly into derive_key_aes().
This is in preparation for the case where we find the master key in a
filesystem-level keyring, where (for good reasons) the key payload
will *not* be formatted as the UAPI 'struct fscrypt_key'.
- Separate finding the key from key derivation. In particular, it
*only* makes sense to fall back to the alternate key description
prefix if searching for the "fscrypt:" prefix returns -ENOKEY. It
doesn't make sense to do so when derive_key_aes() fails, for example.
- Improve the error messages for when the fscrypt_key is invalid.
- Rename 'raw_key' to 'derived_key' for clarity.
Signed-off-by: Eric Biggers <redacted>
---
fs/crypto/keyinfo.c | 205 ++++++++++++++++++++++++++++------------------------
1 file changed, 109 insertions(+), 96 deletions(-)
@@ -28,113 +28,138 @@ static void derive_crypt_complete(struct crypto_async_request *req, int rc)complete(&ecr->completion);}-/**-*derive_key_aes()-DeriveakeyusingAES-128-ECB-*@deriving_key:Encryptionkeyusedforderivation.-*@source_key:Sourcekeytowhichtoapplyderivation.-*@derived_raw_key:Derivedrawkey.+/*+*Keyderivationfunction.Thisgeneratesthederivedkeybyencryptingthe+*masterkeywithAES-128-ECBusingthenonceastheAESkey.*-*Return:Zeroonsuccess;non-zerootherwise.+*Themasterkeymustbeatleastaslongasthederivedkey.Ifthemaster+*keyislonger,thenonlythefirst'derived_keysize'bytesareused.*/-staticintderive_key_aes(u8deriving_key[FS_AES_128_ECB_KEY_SIZE],-conststructfscrypt_key*source_key,-u8derived_raw_key[FSCRYPT_MAX_KEY_SIZE])+staticintderive_key_aes(constu8*master_key,+conststructfscrypt_context*ctx,+u8*derived_key,unsignedintderived_keysize){-intres=0;+interr;structskcipher_request*req=NULL;DECLARE_FS_COMPLETION_RESULT(ecr);structscatterlistsrc_sg,dst_sg;-structcrypto_skcipher*tfm=crypto_alloc_skcipher("ecb(aes)",0,0);+structcrypto_skcipher*tfm;++tfm=crypto_alloc_skcipher("ecb(aes)",0,0);+if(IS_ERR(tfm))+returnPTR_ERR(tfm);-if(IS_ERR(tfm)){-res=PTR_ERR(tfm);-tfm=NULL;-gotoout;-}crypto_skcipher_set_flags(tfm,CRYPTO_TFM_REQ_WEAK_KEY);req=skcipher_request_alloc(tfm,GFP_NOFS);if(!req){-res=-ENOMEM;+err=-ENOMEM;gotoout;}skcipher_request_set_callback(req,CRYPTO_TFM_REQ_MAY_BACKLOG|CRYPTO_TFM_REQ_MAY_SLEEP,derive_crypt_complete,&ecr);-res=crypto_skcipher_setkey(tfm,deriving_key,-FS_AES_128_ECB_KEY_SIZE);-if(res<0)++BUILD_BUG_ON(sizeof(ctx->nonce)!=FS_AES_128_ECB_KEY_SIZE);+err=crypto_skcipher_setkey(tfm,ctx->nonce,sizeof(ctx->nonce));+if(err)gotoout;-sg_init_one(&src_sg,source_key->raw,source_key->size);-sg_init_one(&dst_sg,derived_raw_key,source_key->size);-skcipher_request_set_crypt(req,&src_sg,&dst_sg,source_key->size,+sg_init_one(&src_sg,master_key,derived_keysize);+sg_init_one(&dst_sg,derived_key,derived_keysize);+skcipher_request_set_crypt(req,&src_sg,&dst_sg,derived_keysize,NULL);-res=crypto_skcipher_encrypt(req);-if(res==-EINPROGRESS||res==-EBUSY){+err=crypto_skcipher_encrypt(req);+if(err==-EINPROGRESS||err==-EBUSY){wait_for_completion(&ecr.completion);-res=ecr.res;+err=ecr.res;}out:skcipher_request_free(req);crypto_free_skcipher(tfm);-returnres;+returnerr;}-staticintvalidate_user_key(structfscrypt_info*crypt_info,-structfscrypt_context*ctx,u8*raw_key,-constchar*prefix,intmin_keysize)+/*+*Searchthecurrenttask'ssubscribedkeyringsfora"logon"keywith+*descriptionprefix:descriptor,andiffoundacquireareadlockonitand+*returnapointertoitsvalidatedpayloadin*payload_ret.+*/+staticstructkey*+find_and_lock_process_key(constchar*prefix,+constu8descriptor[FSCRYPT_KEY_DESCRIPTOR_SIZE],+unsignedintmin_keysize,+conststructfscrypt_key**payload_ret){char*description;-structkey*keyring_key;-structfscrypt_key*master_key;+structkey*key;conststructuser_key_payload*ukp;-intres;+conststructfscrypt_key*payload;description=kasprintf(GFP_NOFS,"%s%*phN",prefix,-FSCRYPT_KEY_DESCRIPTOR_SIZE,-ctx->master_key_descriptor);+FSCRYPT_KEY_DESCRIPTOR_SIZE,descriptor);if(!description)-return-ENOMEM;+returnERR_PTR(-ENOMEM);-keyring_key=request_key(&key_type_logon,description,NULL);+key=request_key(&key_type_logon,description,NULL);kfree(description);-if(IS_ERR(keyring_key))-returnPTR_ERR(keyring_key);-down_read(&keyring_key->sem);--if(keyring_key->type!=&key_type_logon){-printk_once(KERN_WARNING-"%s: key type must be logon\n",__func__);-res=-ENOKEY;-gotoout;-}-ukp=user_key_payload_locked(keyring_key);-if(!ukp){-/* key was revoked before we acquired its semaphore */-res=-EKEYREVOKED;-gotoout;+if(IS_ERR(key))+returnkey;++down_read(&key->sem);+ukp=user_key_payload_locked(key);++if(!ukp)/* was the key revoked before we acquired its semaphore? */+gotoinvalid;++payload=(conststructfscrypt_key*)ukp->data;++if(ukp->datalen!=sizeof(structfscrypt_key)||+payload->size<1||payload->size>FSCRYPT_MAX_KEY_SIZE){+pr_warn_ratelimited("fscrypt: key with description '%s' has invalid payload\n",+key->description);+gotoinvalid;}-if(ukp->datalen!=sizeof(structfscrypt_key)){-res=-EINVAL;-gotoout;++if(payload->size<min_keysize){+pr_warn_ratelimited("fscrypt: key with description '%s' is too short "+"(got %u bytes, need %u+ bytes)\n",+key->description,+payload->size,min_keysize);+gotoinvalid;}-master_key=(structfscrypt_key*)ukp->data;-BUILD_BUG_ON(FS_AES_128_ECB_KEY_SIZE!=FS_KEY_DERIVATION_NONCE_SIZE);--if(master_key->size<min_keysize||-master_key->size>FSCRYPT_MAX_KEY_SIZE-||master_key->size%AES_BLOCK_SIZE!=0){-printk_once(KERN_WARNING-"%s: key size incorrect: %d\n",-__func__,master_key->size);-res=-ENOKEY;-gotoout;++*payload_ret=payload;+returnkey;++invalid:+up_read(&key->sem);+key_put(key);+returnERR_PTR(-ENOKEY);+}++/* Find the master key, then derive the inode's actual encryption key */+staticintfind_and_derive_key(conststructinode*inode,+conststructfscrypt_context*ctx,+u8*derived_key,unsignedintderived_keysize)+{+structkey*key;+conststructfscrypt_key*payload;+interr;++key=find_and_lock_process_key(FSCRYPT_KEY_DESC_PREFIX,+ctx->master_key_descriptor,+derived_keysize,&payload);+if(key==ERR_PTR(-ENOKEY)&&inode->i_sb->s_cop->key_prefix){+key=find_and_lock_process_key(inode->i_sb->s_cop->key_prefix,+ctx->master_key_descriptor,+derived_keysize,&payload);}-res=derive_key_aes(ctx->nonce,master_key,raw_key);-out:-up_read(&keyring_key->sem);-key_put(keyring_key);-returnres;+if(IS_ERR(key))+returnPTR_ERR(key);+err=derive_key_aes(payload->raw,ctx,derived_key,derived_keysize);+up_read(&key->sem);+key_put(key);+returnerr;}staticconststruct{
@@ -256,8 +281,8 @@ int fscrypt_get_encryption_info(struct inode *inode)structfscrypt_contextctx;structcrypto_skcipher*ctfm;constchar*cipher_str;-intkeysize;-u8*raw_key=NULL;+unsignedintderived_keysize;+u8*derived_key=NULL;intres;if(inode->i_crypt_info)
@@ -301,7 +326,8 @@ int fscrypt_get_encryption_info(struct inode *inode)memcpy(crypt_info->ci_master_key,ctx.master_key_descriptor,sizeof(crypt_info->ci_master_key));-res=determine_cipher_type(crypt_info,inode,&cipher_str,&keysize);+res=determine_cipher_type(crypt_info,inode,+&cipher_str,&derived_keysize);if(res)gotoout;
@@ -310,24 +336,14 @@ int fscrypt_get_encryption_info(struct inode *inode)*cryptoAPIaspartofkeyderivation.*/res=-ENOMEM;-raw_key=kmalloc(FSCRYPT_MAX_KEY_SIZE,GFP_NOFS);-if(!raw_key)+derived_key=kmalloc(FS_MAX_KEY_SIZE,GFP_NOFS);+if(!derived_key)gotoout;-res=validate_user_key(crypt_info,&ctx,raw_key,-FSCRYPT_KEY_DESC_PREFIX,keysize);-if(res&&inode->i_sb->s_cop->key_prefix){-intres2=validate_user_key(crypt_info,&ctx,raw_key,-inode->i_sb->s_cop->key_prefix,-keysize);-if(res2){-if(res2==-ENOKEY)-res=-ENOKEY;-gotoout;-}-}elseif(res){+res=find_and_derive_key(inode,&ctx,derived_key,derived_keysize);+if(res)gotoout;-}+ctfm=crypto_alloc_skcipher(cipher_str,0,0);if(!ctfm||IS_ERR(ctfm)){res=ctfm?PTR_ERR(ctfm):-ENOMEM;
@@ -361,7 +374,7 @@ int fscrypt_get_encryption_info(struct inode *inode)if(res==-ENOKEY)res=0;put_crypt_info(crypt_info);-kzfree(raw_key);+kzfree(derived_key);returnres;}EXPORT_SYMBOL(fscrypt_get_encryption_info);
--
2.15.0.rc0.271.g36b669edcc-goog
------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
From: Eric Biggers <hidden> Date: 2017-10-23 21:42:17
From: Eric Biggers <redacted>
Add an ->s_master_keys keyring to 'struct super_block' for holding
encryption keys which have been added to the filesystem. This keyring
will be populated using a new fscrypt ioctl.
This is needed for several reasons, including:
- To solve the visibility problems of having filesystem encryption keys
stored in process-subscribed keyrings, while the VFS state of the
filesystem is actually global.
- To implement a proper API for removing keys, which among other things
will require maintaining the list of inodes that are using each master
key so that we can evict the inodes when the key is removed.
- To allow caching a crypto transform for each master key so that we
don't have to repeatedly allocate one over and over.
See later patches for full details, including why it wouldn't be enough
to add the concept of a "global keyring" to the keyrings API instead.
->s_master_keys will only be allocated when someone tries to add a key
for the first time. Otherwise it will stay NULL.
Note that this could go in the filesystem-specific superblocks instead.
However, we already have three filesystems using fs/crypto/, so it's
useful to have it in the VFS.
Signed-off-by: Eric Biggers <redacted>
---
fs/super.c | 3 +++
include/linux/fs.h | 4 ++++
2 files changed, 7 insertions(+)
@@ -1440,6 +1440,10 @@ struct super_block {spinlock_ts_inode_wblist_lock;structlist_heads_inodes_wb;/* writeback inodes */++#if IS_ENABLED(CONFIG_FS_ENCRYPTION)+structkey*s_master_keys;/* master crypto keys in use */+#endif}__randomize_layout;/* Helper functions so that in most cases filesystems will
--
2.15.0.rc0.271.g36b669edcc-goog
------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
From: Eric Biggers <hidden> Date: 2017-10-23 21:42:19
From: Eric Biggers <redacted>
Add a new filesystem encryption ioctl, FS_IOC_ADD_ENCRYPTION_KEY. This
ioctl adds a master encryption key to the filesystem encryption keyring
->s_master_keys.
When a process tries to access an encrypted file that has not yet been
"unlocked" (set up with an ->i_crypt_info containing a crypto transform
keyed by the file's derived key), fscrypt_get_encryption_info() will
search for the master key in ->s_master_keys before falling back to the
process-subscribed keyrings.
For now this ioctl is root-only, which is necessary in part because the
keys are identified by master_key_descriptor, which is not
cryptographically tied to the actual key payload. However, a later
patch will introduce a new encryption policy version where the key is
identified by a cryptographic hash. This will, in combination with
other protections, make it possible for non-root users to use this ioctl
in some situations.
Why we need this
~~~~~~~~~~~~~~~~
The main problem is that the "locked/unlocked" (ciphertext/plaintext)
status of encrypted files is global, but currently the master keys are
not. We only look for master keys in the process-subscribed keyrings;
that is, the current thread keyring, process keyring, and session
keyring, where the session keyring usually contains the user keyring.
This means we have to put the master keys in the keyrings for individual
users or for individual sessions. This causes much confusion as soon as
a process with a different UID, such as a 'sudo' command, tries to
access encrypted files. In such a situation, whether each individual
inode appears "locked" or "unlocked" will depend on whether it was
previously accessed and happens to still be in the inode cache, which is
more or less nondeterministic.
It may seem that we should indeed provide each process its own "view" of
the filesystem depending on whether it "has the key" or not. However
that would be extremely difficult to do without a separate mount, due to
the way the VFS caches work. Furthermore, it is actually missing the
point of encryption because it would *not* be encryption that would
provide the different "views", but rather kernel *code*. Thus, it would
simply be an access control mechanism largely redundant with the many
existing access control mechanisms such as UNIX file permissions and
LSMs. The reality is that the confidentially of encrypted files *after
the kernel already has the encryption key in memory* is only protected
by the correctness of the kernel, not by the mathematical properties of
encryption.
And at the end of the day, almost all users of filesystem encryption we
are aware of do really need the global view, because they need encrypted
files to be accessible to processes running under different UIDs. This
can be as simple as needing to be able to run 'sudo', or it can be
something more complex like Android's key management system where
applications running under different UIDs as well as system processes
need access to the same encrypted files.
As a result, some very ugly hacks have been added to try to emulate
globally visible keys. The Android and Chromium OS key management
systems simply create a "session" keyring in PID 1 and put all the keys
in it, which abuses the "session" keyring to have nothing to do with a
"session", but rather be a global keyring. This is fragile, as it means
that the "session" keyring must never be changed. There have also been
bugs involving processes that were forked before the "session" keyring
was created, causing them to miss out on the keys.
Meanwhile, filesystem encryption tools written for general-purpose Linux
distributions have no such ability to abuse the "session" keyring. They
instead must implement "interesting" workarounds such as linking all the
user keyrings into root's user keyring, as is done by the fscrypt
userspace tool (see the design document at https://goo.gl/55cCrI). This
raises security concerns, to say the least.
By having an API to add a key to the *filesystem* we'll be able to
eliminate all the above hacks and better express the intended semantics:
the "locked/unlocked" status of an encrypted directory is global. And
orthogonally to encryption, existing mechanisms such as file permissions
and LSMs can and should continue to be used for the purpose of *access
control*.
Why use a custom key type
~~~~~~~~~~~~~~~~~~~~~~~~~
The keys the new ioctl adds to ->s_master_keys are still "keys" in the
sense of the keyrings service, but they have a custom key type rather
than the "logon" key type we currently require when userspace provides a
key via a process-subscribed keyring.
Judging just from this patch alone, the "logon" key type would be
sufficient. However, later patches will be solving problems such as the
nonstandard KDF and the lack of a key removal API. The solutions for
these problems will require tracking information on a per-master-key
basis. Therefore, we'll need a custom structure associated with each
master key anyway. A custom key type lets us do that easily.
Why not use add_key()
~~~~~~~~~~~~~~~~~~~~~
Instead of adding a new ioctl() to add a key, we could have userspace
use the add_key() system call. In combination with an ioctl which
retrieves the key ID of ->s_master_keys, add_key() could be used to add
a key to ->s_master_keys. Alternatively, we could add the concept of a
"global keyring" or "namespace keyring" to the keyrings service, where
that keyring would be searched in addition to the process-subscribed
keyrings. Then, add_key() could add a key to that.
This actually makes sense given only the present patch. However,
unfortunately it falls apart once we consider the follow-on changes.
First, we also need to add the ability to remove an encryption key, and
it will need to have more specialized semantics than keyctl_unlink() or
keyctl_revoke() can provide. For example, we must not only wipe the
master key *secret* from memory, but we must also try to evict all the
inodes which had been "unlocked" using the key. And it's possible that
even though the master key secret was wiped, some inodes could not be
evicted, since they may be busy. In that case, we still want to wipe
the master key *secret* so that no more encrypted files can be
"unlocked". But we also want to allow userspace to retry the request
later, so that evicting the remaining inodes can be re-attempted.
Alternatively, we want the same list of inodes to be picked up again if
the secret happens to be added again. Trying to shoehorn these specific
semantics into the keyrings API would be very difficult.
Later we also want to open up the add/remove key operations to non-root
users by taking advantage of a new encryption policy version which
includes a cryptographic hash of the master key. This is needed because
otherwise we wouldn't be able to fully replace the process-subscribed
keyrings and avoid all its problems mentioned earlier. But to actually
make non-root use secure, we'll need to do some extra accounting where
we keep track of all users who have added a given key, then only really
remove a key after all users have removed it. Non-root users also
cannot simply be given write permission to a global keyring. So again,
it seems that trying to shoehorn the needed semantics into the keyrings
API would just create problems.
Nevertheless, we do still use the keyrings service internally so that we
reuse some code and get some "free" functionality such as having the
keys show up in /proc/keys for debugging purposes.
Signed-off-by: Eric Biggers <redacted>
---
fs/crypto/crypto.c | 12 +-
fs/crypto/fscrypt_private.h | 3 +
fs/crypto/keyinfo.c | 351 +++++++++++++++++++++++++++++++++++++++-
include/linux/fscrypt_notsupp.h | 5 +
include/linux/fscrypt_supp.h | 1 +
include/uapi/linux/fscrypt.h | 41 +++--
6 files changed, 397 insertions(+), 16 deletions(-)
@@ -449,6 +450,8 @@ int fscrypt_initialize(unsigned int cop_flags)*/staticint__initfscrypt_init(void){+interr=-ENOMEM;+fscrypt_read_workqueue=alloc_workqueue("fscrypt_read_queue",WQ_HIGHPRI,0);if(!fscrypt_read_workqueue)
@@ -462,14 +465,20 @@ static int __init fscrypt_init(void)if(!fscrypt_info_cachep)gotofail_free_ctx;+err=register_key_type(&key_type_fscrypt_mk);+if(err)+gotofail_free_info;+return0;+fail_free_info:+kmem_cache_destroy(fscrypt_info_cachep);fail_free_ctx:kmem_cache_destroy(fscrypt_ctx_cachep);fail_free_queue:destroy_workqueue(fscrypt_read_workqueue);fail:-return-ENOMEM;+returnerr;}module_init(fscrypt_init)
@@ -9,14 +9,307 @@*/#include<keys/user-type.h>-#include<linux/scatterlist.h>+#include<linux/key-type.h>#include<linux/ratelimit.h>+#include<linux/scatterlist.h>+#include<linux/seq_file.h>#include<crypto/aes.h>#include<crypto/sha.h>#include"fscrypt_private.h"staticstructcrypto_shash*essiv_hash_tfm;+/*+*fscrypt_master_key_secret-secretkeymaterialofanin-usemasterkey+*/+structfscrypt_master_key_secret{++/* Size of the raw key in bytes */+u32size;++/* The raw key */+u8raw[FSCRYPT_MAX_KEY_SIZE];+};++/*+*fscrypt_master_key-anin-usemasterkey+*+*Thisrepresentsamasterencryptionkeywhichhasbeenaddedtothe+*filesystemandcanbeusedto"unlock"theencryptedfileswhichwere+*encryptedwithit.+*/+structfscrypt_master_key{++/* The secret key material */+structfscrypt_master_key_secretmk_secret;++/* Arbitrary key descriptor which was assigned by userspace */+structfscrypt_key_specifiermk_spec;+};++staticinlineintmaster_key_spec_len(conststructfscrypt_key_specifier*spec)+{+switch(spec->type){+caseFSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR:+returnFSCRYPT_KEY_DESCRIPTOR_SIZE;+}+return0;+}++staticinlineboolvalid_key_spec(conststructfscrypt_key_specifier*spec)+{+if(spec->reserved)+returnfalse;+returnmaster_key_spec_len(spec)!=0;+}++staticvoidwipe_master_key_secret(structfscrypt_master_key_secret*secret)+{+memzero_explicit(secret,sizeof(*secret));+}++staticvoidmove_master_key_secret(structfscrypt_master_key_secret*dst,+structfscrypt_master_key_secret*src)+{+memcpy(dst,src,sizeof(*dst));+memzero_explicit(src,sizeof(*src));+}++staticvoidfree_master_key(structfscrypt_master_key*mk)+{+wipe_master_key_secret(&mk->mk_secret);+kzfree(mk);+}++staticintfscrypt_key_instantiate(structkey*key,+structkey_preparsed_payload*prep)+{+key->payload.data[0]=(structfscrypt_master_key*)prep->data;+return0;+}++staticvoidfscrypt_key_destroy(structkey*key)+{+free_master_key(key->payload.data[0]);+}++staticvoidfscrypt_key_describe(conststructkey*key,structseq_file*m)+{+seq_puts(m,key->description);+}++/*+*Typeofkeyin->s_master_keys.Eachkeyofthistyperepresentsamaster+*keywhichhasbeenaddedtothefilesystem.Itspayloadisa+*'structfscrypt_master_key'.+*/+structkey_typekey_type_fscrypt_mk={+.name="._fscrypt",+.instantiate=fscrypt_key_instantiate,+.destroy=fscrypt_key_destroy,+.describe=fscrypt_key_describe,+};++/*+*Search->s_master_keys.Notethatwemarkthekeyringreferenceas+*"possessed"sothatwecanusetheKEY_POS_SEARCHpermission.+*/+staticstructkey*search_fscrypt_keyring(structkey*keyring,+structkey_type*type,+constchar*description)+{+key_ref_tkeyref;++keyref=keyring_search(make_key_ref(keyring,1),type,description);+if(IS_ERR(keyref)){+if(PTR_ERR(keyref)==-EAGAIN)+keyref=ERR_PTR(-ENOKEY);+returnERR_CAST(keyref);+}+returnkey_ref_to_ptr(keyref);+}++#define FSCRYPT_FS_KEYRING_DESCRIPTION_SIZE \+(sizeof("fscrypt-")-1+sizeof(((structsuper_block*)0)->s_id)+1)++#define FSCRYPT_MK_DESCRIPTION_SIZE (2 * FSCRYPT_KEY_DESCRIPTOR_SIZE + 1)++staticvoidformat_fs_keyring_description(+chardescription[FSCRYPT_FS_KEYRING_DESCRIPTION_SIZE],+conststructsuper_block*sb)+{+sprintf(description,"fscrypt-%s",sb->s_id);+}++staticvoidformat_mk_description(+chardescription[FSCRYPT_MK_DESCRIPTION_SIZE],+conststructfscrypt_key_specifier*mk_spec)+{+sprintf(description,"%*phN",+master_key_spec_len(mk_spec),mk_spec->max_specifier);+}++/*+*Findthespecifiedmasterkeyin->s_master_keys.+*ReturnsERR_PTR(-ENOKEY)ifnotfound.+*/+staticstructkey*find_master_key(structsuper_block*sb,+conststructfscrypt_key_specifier*mk_spec)+{+structkey*keyring;+chardescription[FSCRYPT_MK_DESCRIPTION_SIZE];++/* pairs with smp_store_release() in add_to_filesystem_keyring() */+keyring=smp_load_acquire(&sb->s_master_keys);+if(keyring==NULL)+returnERR_PTR(-ENOKEY);++format_mk_description(description,mk_spec);+returnsearch_fscrypt_keyring(keyring,&key_type_fscrypt_mk,+description);+}++staticstructkey*+allocate_master_key(structfscrypt_master_key_secret*secret,+conststructfscrypt_key_specifier*mk_spec)+{+structfscrypt_master_key*mk;+structkey*key;+chardescription[FSCRYPT_MK_DESCRIPTION_SIZE];+interr;++mk=kzalloc(sizeof(*mk),GFP_NOFS);+if(!mk)+returnERR_PTR(-ENOMEM);++mk->mk_spec=*mk_spec;++move_master_key_secret(&mk->mk_secret,secret);++format_mk_description(description,mk_spec);+key=key_alloc(&key_type_fscrypt_mk,description,+GLOBAL_ROOT_UID,GLOBAL_ROOT_GID,current_cred(),+KEY_POS_SEARCH|KEY_USR_SEARCH|+KEY_USR_READ|KEY_USR_VIEW,0,NULL);+if(IS_ERR(key))+gotoout_free_mk;++err=key_instantiate_and_link(key,mk,sizeof(*mk),NULL,NULL);+if(err){+key_put(key);+key=ERR_PTR(err);+gotoout_free_mk;+}+returnkey;++out_free_mk:+free_master_key(mk);+returnkey;+}++/*+*Addthegivenkeyto->s_master_keys,creating->s_master_keysifitdoesn't+*alreadyexist.Synchronizedbyfscrypt_add_key_mutex.+*/+staticintadd_to_filesystem_keyring(structsuper_block*sb,structkey*key)+{+structkey*keyring=sb->s_master_keys;++if(keyring==NULL){+chardescription[FSCRYPT_FS_KEYRING_DESCRIPTION_SIZE];++format_fs_keyring_description(description,sb);+keyring=keyring_alloc(description,GLOBAL_ROOT_UID,+GLOBAL_ROOT_GID,current_cred(),+KEY_POS_SEARCH|KEY_USR_SEARCH|+KEY_USR_READ|KEY_USR_VIEW,+KEY_ALLOC_NOT_IN_QUOTA,NULL,NULL);+if(IS_ERR(keyring))+returnPTR_ERR(keyring);++/* Pairs with smp_load_acquire() in find_master_key() */+smp_store_release(&sb->s_master_keys,keyring);+}++returnkey_link(keyring,key);+}++staticintadd_master_key(structsuper_block*sb,+structfscrypt_master_key_secret*secret,+conststructfscrypt_key_specifier*mk_spec)+{+structkey*key;+interr;+staticDEFINE_MUTEX(fscrypt_add_key_mutex);++mutex_lock(&fscrypt_add_key_mutex);/* serialize find + link */+key=find_master_key(sb,mk_spec);+if(IS_ERR(key)){+if(key!=ERR_PTR(-ENOKEY)){+err=PTR_ERR(key);+gotoout_unlock;+}+/* Didn't find the key in ->s_master_keys; add it. */++key=allocate_master_key(secret,mk_spec);+if(IS_ERR(key)){+err=PTR_ERR(key);+gotoout_unlock;+}+err=add_to_filesystem_keyring(sb,key);+if(err)+gotoout_put_key;+}+err=0;+out_put_key:+key_put(key);+out_unlock:+mutex_unlock(&fscrypt_add_key_mutex);+returnerr;+}++/*+*Addamasterencryptionkeytothefilesystem,causingallfileswhichwere+*encryptedwithittoappear"unlocked"(decrypted)whenaccessed.+*/+intfscrypt_ioctl_add_key(structfile*filp,void__user*_uarg)+{+structsuper_block*sb=file_inode(filp)->i_sb;+structfscrypt_add_key_args__user*uarg=_uarg;+structfscrypt_add_key_argsarg;+structfscrypt_master_key_secretsecret;+interr;++if(copy_from_user(&arg,uarg,sizeof(arg)))+return-EFAULT;++if(arg.raw_size<FSCRYPT_MIN_KEY_SIZE||+arg.raw_size>FSCRYPT_MAX_KEY_SIZE)+return-EINVAL;++if(arg.reserved1||+memchr_inv(arg.reserved2,0,sizeof(arg.reserved2)))+return-EINVAL;++if(!valid_key_spec(&arg.key_spec))+return-EINVAL;++if(!capable(CAP_SYS_ADMIN))+return-EACCES;++memset(&secret,0,sizeof(secret));+secret.size=arg.raw_size;+err=-EFAULT;+if(copy_from_user(secret.raw,uarg->raw,secret.size))+gotoout_wipe_secret;++err=add_master_key(sb,&secret,&arg.key_spec);+out_wipe_secret:+wipe_master_key_secret(&secret);+returnerr;+}+EXPORT_SYMBOL_GPL(fscrypt_ioctl_add_key);+staticvoidderive_crypt_complete(structcrypto_async_request*req,intrc){structfscrypt_completion_result*ecr=req->data;
@@ -137,10 +430,10 @@ find_and_lock_process_key(const char *prefix,returnERR_PTR(-ENOKEY);}-/* Find the master key, then derive the inode's actual encryption key */-staticintfind_and_derive_key(conststructinode*inode,-conststructfscrypt_context*ctx,-u8*derived_key,unsignedintderived_keysize)+staticintfind_and_derive_key_legacy(conststructinode*inode,+conststructfscrypt_context*ctx,+u8*derived_key,+unsignedintderived_keysize){structkey*key;conststructfscrypt_key*payload;
@@ -162,6 +455,54 @@ static int find_and_derive_key(const struct inode *inode,returnerr;}+/* Find the master key, then derive the inode's actual encryption key */+staticintfind_and_derive_key(conststructinode*inode,+conststructfscrypt_context*ctx,+u8*derived_key,unsignedintderived_keysize)+{+structkey*key;+structfscrypt_master_key*mk;+structfscrypt_key_specifiermk_spec;+interr;++mk_spec.type=FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR;+memcpy(mk_spec.descriptor,ctx->master_key_descriptor,+FSCRYPT_KEY_DESCRIPTOR_SIZE);++key=find_master_key(inode->i_sb,&mk_spec);+if(IS_ERR(key)){+if(key!=ERR_PTR(-ENOKEY))+returnPTR_ERR(key);+/*+*Asalegacyfallback,wesearchthecurrenttask'ssubscribed+*keyringsinadditionto->s_master_keys.+*/+returnfind_and_derive_key_legacy(inode,ctx,derived_key,+derived_keysize);+}+mk=key->payload.data[0];++/*+*Requirethatthemasterkeybeatleastaslongasthederivedkey.+*Otherwise,thederivedkeycannotpossiblycontainasmuchentropyas+*thatrequiredbytheencryptionmodeitwillbeusedfor.+*/+if(mk->mk_secret.size<derived_keysize){+pr_warn_ratelimited("fscrypt: key with description '%s' is too short "+"(got %u bytes, need %u+ bytes)\n",+key->description,+mk->mk_secret.size,derived_keysize);+err=-ENOKEY;+gotoout_put_key;+}++err=derive_key_aes(mk->mk_secret.raw,ctx,+derived_key,derived_keysize);+out_put_key:+key_put(key);+returnerr;+}+staticconststruct{constchar*cipher_str;intkeysize;
@@ -34,22 +34,43 @@ struct fscrypt_policy {__u8master_key_descriptor[FSCRYPT_KEY_DESCRIPTOR_SIZE];};-#define FS_IOC_SET_ENCRYPTION_POLICY _IOR('f', 19, struct fscrypt_policy)-#define FS_IOC_GET_ENCRYPTION_PWSALT _IOW('f', 20, __u8[16])-#define FS_IOC_GET_ENCRYPTION_POLICY _IOW('f', 21, struct fscrypt_policy)--/* Parameters for passing an encryption key into the kernel keyring */+/*+*Process-subscribed"logon"keydescriptionprefixandpayloadformat.+*Deprecated;preferFS_IOC_ADD_ENCRYPTION_KEYinstead.+*/#define FSCRYPT_KEY_DESC_PREFIX "fscrypt:"-#define FSCRYPT_KEY_DESC_PREFIX_SIZE 8--/* Structure that userspace passes to the kernel keyring */-#define FSCRYPT_MAX_KEY_SIZE 64-+#define FSCRYPT_KEY_DESC_PREFIX_SIZE 8+#define FSCRYPT_MAX_KEY_SIZE 64structfscrypt_key{__u32mode;__u8raw[FSCRYPT_MAX_KEY_SIZE];__u32size;};++structfscrypt_key_specifier{+__u32type;+#define FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR 1+__u32reserved;+union{+__u8max_specifier[32];+__u8descriptor[FSCRYPT_KEY_DESCRIPTOR_SIZE];+};+};++/* Struct passed to FS_IOC_ADD_ENCRYPTION_KEY */+structfscrypt_add_key_args{+__u32raw_size;+__u32reserved1;+__u64reserved2[2];+structfscrypt_key_specifierkey_spec;+__u8raw[];+};++#define FS_IOC_SET_ENCRYPTION_POLICY _IOR( 'f', 19, struct fscrypt_policy)+#define FS_IOC_GET_ENCRYPTION_PWSALT _IOW( 'f', 20, __u8[16])+#define FS_IOC_GET_ENCRYPTION_POLICY _IOW( 'f', 21, struct fscrypt_policy)+#define FS_IOC_ADD_ENCRYPTION_KEY _IOWR('f', 22, struct fscrypt_add_key_args)+/**********************************************************************//* old names; don't add anything new here! */
--
2.15.0.rc0.271.g36b669edcc-goog
------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
From: Eric Biggers <hidden> Date: 2017-10-23 21:42:20
From: Eric Biggers <redacted>
When a filesystem encryption key is removed, we need all files which had
been "unlocked" (had ->i_crypt_info set up) with it to appear "locked"
again. This is most easily done by evicting the inodes. This can
currently be done using 'echo 2 > /proc/sys/vm/drop_caches'; however,
that is overkill and not usable by non-root users. In preparation for
allowing fs/crypto/ to evict just the needed inodes, export
inode_lru_list_del() to modules.
Signed-off-by: Eric Biggers <redacted>
---
fs/inode.c | 5 ++---
include/linux/fs.h | 1 +
2 files changed, 3 insertions(+), 3 deletions(-)
--
2.15.0.rc0.271.g36b669edcc-goog
------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
From: Eric Biggers <hidden> Date: 2017-10-23 21:42:21
From: Eric Biggers <redacted>
When a filesystem encryption key is removed, we need all files which had
been "unlocked" (had ->i_crypt_info set up) with it to appear "locked"
again. This is most easily done by evicting the inodes. This can
currently be done using 'echo 2 > /proc/sys/vm/drop_caches'; however,
that is overkill and not usable by non-root users. In preparation for
allowing fs/crypto/ to evict just the needed inodes, export
dispose_list() to modules. For clarity also rename it to
evict_inode_list().
Signed-off-by: Eric Biggers <redacted>
---
fs/inode.c | 19 ++++++++++---------
include/linux/fs.h | 1 +
2 files changed, 11 insertions(+), 9 deletions(-)
--
2.15.0.rc0.271.g36b669edcc-goog
------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
From: Eric Biggers <hidden> Date: 2017-10-23 21:42:22
From: Eric Biggers <redacted>
When a filesystem encryption key is removed, we need all files which had
been "unlocked" (had ->i_crypt_info set up) with it to appear "locked"
again. This is most easily done by evicting the inodes. This can
currently be done using 'echo 2 > /proc/sys/vm/drop_caches'; however,
that is overkill and not usable by non-root users.
To evict just the needed inodes we also need the ability to evict those
inodes' dentries, since an inode is pinned by its dentries. Therefore,
add a function shrink_dcache_inode() which iterates through an inode's
dentries and evicts any unused ones as well as any unused descendants
(since there may be negative dentries pinning the inode's dentries).
Signed-off-by: Eric Biggers <redacted>
---
fs/dcache.c | 33 +++++++++++++++++++++++++++++++++
include/linux/dcache.h | 1 +
2 files changed, 34 insertions(+)
@@ -1456,6 +1456,39 @@ void shrink_dcache_parent(struct dentry *parent)}EXPORT_SYMBOL(shrink_dcache_parent);+/**+*shrink_dcache_inode-prunedcacheforinode+*@inode:inodetoprune+*+*Evictallunusedaliasesofthespecifiedinodefromthedcache.Thisis+*intendedtobeusedwhentryingtoevictaspecificinode,sinceinodesare+*pinnedbytheirdentries.Wealsohavetodescendto->d_subdirsforeach+*alias,sincealiasesmaybepinnedbynegativechilddentries.+*/+voidshrink_dcache_inode(structinode*inode)+{+for(;;){+structselect_datadata;+structdentry*dentry;++INIT_LIST_HEAD(&data.dispose);+data.start=NULL;+data.found=0;++spin_lock(&inode->i_lock);+hlist_for_each_entry(dentry,&inode->i_dentry,d_u.d_alias)+d_walk(dentry,&data,select_collect,NULL);+spin_unlock(&inode->i_lock);++if(!data.found)+break;++shrink_dentry_list(&data.dispose);+cond_resched();+}+}+EXPORT_SYMBOL(shrink_dcache_inode);+staticenumd_walk_retumount_check(void*_data,structdentry*dentry){/* it has busy descendents; complain about those instead */
--
2.15.0.rc0.271.g36b669edcc-goog
------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
From: Eric Biggers <hidden> Date: 2017-10-23 21:42:25
From: Eric Biggers <redacted>
Problem
~~~~~~~
Many filesystem encryption users want the ability to remove encryption
keys, causing the corresponding encrypted directories to appear "locked"
(presented in ciphertext form) again. Moreover, users want removing an
encryption key to *really* remove it, in the sense that the removed keys
cannot be recovered even if kernel memory is compromised, e.g. by the
exploit of a kernel security vulnerability or by a physical attack.
This is desirable after a user logs out of the system, for example. In
many cases users even already assume this to be the case and are
surprised to hear when it's not.
It is *not* sufficient to simply unlink the master key from the keyring
(or to revoke or invalidate it), since files are encrypted with per-file
keys instead of with the master keys directly. Therefore, to really
remove a key we must also remove the per-file keys, e.g. by evicting the
corresponding inodes from the inode cache. This also would have the
benefit of making encrypted files appear "locked" again.
Currently the workaround is to run:
sync
echo 2 > /proc/sys/vm/drop_caches
This is a very bad solution because it evicts all not-in-use inodes in
the system rather than just the inodes associated with the key being
removed. Moreover, it requires root privileges, so non-root users
cannot lock their encrypted directories. Finally, the drop_caches
sysctl was originally meant for debugging purposes only.
Nevertheless, the largest users of filesystem encryption (Android and
Chromium OS) actually want this capability badly enough that they are
actually using the drop_caches workaround. Similarly, the drop_caches
workaround is also used in the PAM module provided by the fscrypt
userspace tool (https://github.com/google/fscrypt). Needless to say,
this is causing significant performance problems due to inodes for
unencrypted system files being evicted. So a real solution is needed.
Solution
~~~~~~~~
To properly solve this problem, we need an API which removes and wipes
the given master key, *and* removes and and wipes the corresponding
per-file keys. This requires tracking which inodes have been "unlocked"
using each master key. Originally that was not possible because the
kernel didn't actually have a centralized notion of what a master key
even was. But now that we have the filesystem-level keyring
->s_master_keys it is finally possible.
Add this API as a new ioctl, FS_IOC_REMOVE_ENCRYPTION_KEY. It is the
counterpart of FS_IOC_ADD_ENCRYPTION_KEY.
FS_IOC_REMOVE_ENCRYPTION_KEY first wipes the master key's secret from
memory. Then, it syncs the filesystem and tries to evict the list of
inodes that had been "unlocked" with the key. Evicting the inodes has
several effects, including:
- The actual keys used to encrypt the data (in ->i_crypt_info->ci_ctfm)
are wiped from memory. Thus, they can no longer be recovered, even if
kernel memory is later compromised.
- The encrypted files and directories once again appear "locked", i.e.
in ciphertext or in "encrypted" form. This is highly desirable from a
user interface perspective. It can also be desirable from a security
perspective (although sometimes for the wrong reasons!).
- The pagecache pages are freed, which allows the plaintext file
contents to be overwritten in memory later as the system continues
running. Currently we do not actually wipe the pages on free, nor
does the kernel more generally wipe memory on free either. Thus, for
now we tolerate that an attacker who later gains access to kernel
memory may be able to see portions of file contents and file names in
plaintext in unallocated memory. Security-conscious users who do not
mind a performance hit may ameliorate this by enabling page poisoning.
Of course, some inodes may still be in use when a master key is removed,
and we cannot simply revoke random file descriptors, mmap's, etc. The
approach we take is to skip in-use inodes, and notify userspace by
returning -EBUSY if any inodes could not be evicted. Still, even in
this case the master key secret is removed, so no more files can be
unlocked with it. Moreover, most of the inodes should still be evicted
as well. Userspace can then retry the ioctl later to evict the
remaining inodes. Alternatively, if userspace adds the key again, then
the refreshed secret will be associated with the existing list of inodes
so that they are correctly tracked for future key removals.
For now, FS_IOC_REMOVE_ENCRYPTION_KEY has to be restricted to privileged
users only, just like FS_IOC_ADD_ENCRYPTION_KEY. This is sufficient for
use cases where all encryption keys are managed by a privileged process,
e.g. as is the case on Android and Chromium OS.
But in the more general case, non-root users need to be able to both
unlock *and* lock their own encrypted directories. As it turns out, we
will indeed be able to support this through these ioctls, but non-root
use will need to be tied to the use of a new encryption policy version
which identifies the master key using a cryptographic hash. (See later
patches.)
Signed-off-by: Eric Biggers <redacted>
---
fs/crypto/fscrypt_private.h | 18 ++-
fs/crypto/keyinfo.c | 347 ++++++++++++++++++++++++++++++++++++++--
fs/crypto/policy.c | 5 +-
include/linux/fscrypt_notsupp.h | 6 +
include/linux/fscrypt_supp.h | 1 +
include/uapi/linux/fscrypt.h | 7 +
6 files changed, 367 insertions(+), 17 deletions(-)
@@ -61,7 +61,23 @@ struct fscrypt_info {u8ci_flags;structcrypto_skcipher*ci_ctfm;structcrypto_cipher*ci_essiv_tfm;-u8ci_master_key[FSCRYPT_KEY_DESCRIPTOR_SIZE];+u8ci_master_key_descriptor[FSCRYPT_KEY_DESCRIPTOR_SIZE];++/*+*Themasterkeywithwhichthisinodewasunlocked(decrypted).This+*willbeNULLifthemasterkeywasfoundinaprocess-subscribed+*keyringratherthaninthefilesystem-levelkeyring.+*/+structkey*ci_master_key;++/* Link in list of inodes that were unlocked with the master key */+structlist_headci_master_key_link;++/*+*Back-pointertotheinode,neededduringkeyremoval.Onlysetwhen+*->ci_master_keyisset.+*/+structinode*ci_inode;};typedefenum{
@@ -40,11 +40,35 @@ struct fscrypt_master_key_secret {*/structfscrypt_master_key{-/* The secret key material */+/*+*Thesecretkeymaterial.AfterFS_IOC_REMOVE_ENCRYPTION_KEYis+*executed,thisiswipedandnonewinodescanbeunlockedwiththis+*key;however,theremaystillbeinodesin->mk_decrypted_inodes+*whichcouldnotbeevicted.Aslongassomeinodesstillremain,+*FS_IOC_REMOVE_ENCRYPTION_KEYcanberetried,or+*FS_IOC_ADD_ENCRYPTION_KEYcanaddthesecretagain.+*+*Locking:protectedbykey->sem.+*/structfscrypt_master_key_secretmk_secret;/* Arbitrary key descriptor which was assigned by userspace */structfscrypt_key_specifiermk_spec;++/*+*Lengthof->mk_decrypted_inodes,plusoneifmk_secretispresent.+*Oncethisgoesto0,themasterkeyisremovedfrom->s_master_keys.+*Thisstructwillcontinuetoliveaslongasthe'structkey'whose+*payloaditis,butwewon'tletthisreferencecountriseagain.+*/+refcount_tmk_refcount;++/*+*Listofinodesthatwereunlockedusingthiskey.Thisallowsthe+*inodestobeevictedefficientlyifthekeyisremoved.+*/+structlist_headmk_decrypted_inodes;+spinlock_tmk_decrypted_inodes_lock;};staticinlineintmaster_key_spec_len(conststructfscrypt_key_specifier*spec)
@@ -243,6 +285,7 @@ static int add_master_key(struct super_block *sb,staticDEFINE_MUTEX(fscrypt_add_key_mutex);mutex_lock(&fscrypt_add_key_mutex);/* serialize find + link */+retry:key=find_master_key(sb,mk_spec);if(IS_ERR(key)){if(key!=ERR_PTR(-ENOKEY)){
@@ -259,6 +302,33 @@ static int add_master_key(struct super_block *sb,err=add_to_filesystem_keyring(sb,key);if(err)gotoout_put_key;+}else{+structfscrypt_master_key*mk=key->payload.data[0];+boolrekey;++/* Found the key in ->s_master_keys */++down_write(&key->sem);++/*+*Takeareferenceifwe'llbere-adding->mk_secret.Ifwe+*couldn'ttakeareference,thenthekeyisbeingremovedfrom+*->s_master_keysandcannolongerbeused.Soinvalidatethe+*key(someoneelseisdoingthattoo,buttheymightbe+*slower)andretrysearching->s_master_keys.+*/+rekey=!is_master_key_secret_present(&mk->mk_secret);+if(rekey&&!refcount_inc_not_zero(&mk->mk_refcount)){+up_write(&key->sem);+key_invalidate(key);+key_put(key);+gotoretry;+}++/* Re-add the secret key material if needed */+if(rekey)+move_master_key_secret(&mk->mk_secret,secret);+up_write(&key->sem);}err=0;out_put_key:
@@ -270,7 +340,8 @@ static int add_master_key(struct super_block *sb,/**Addamasterencryptionkeytothefilesystem,causingallfileswhichwere-*encryptedwithittoappear"unlocked"(decrypted)whenaccessed.+*encryptedwithittoappear"unlocked"(decrypted)whenaccessed.Thekey+*canberemovedlaterbyFS_IOC_REMOVE_ENCRYPTION_KEY.*/intfscrypt_ioctl_add_key(structfile*filp,void__user*_uarg){
@@ -310,6 +381,191 @@ int fscrypt_ioctl_add_key(struct file *filp, void __user *_uarg)}EXPORT_SYMBOL_GPL(fscrypt_ioctl_add_key);+staticvoidevict_dentries_for_decrypted_inodes(structfscrypt_master_key*mk)+{+structfscrypt_info*ci;+structinode*inode;+structinode*toput_inode=NULL;++spin_lock(&mk->mk_decrypted_inodes_lock);++list_for_each_entry(ci,&mk->mk_decrypted_inodes,ci_master_key_link){+inode=ci->ci_inode;+spin_lock(&inode->i_lock);+if(inode->i_state&(I_FREEING|I_WILL_FREE|I_NEW)){+spin_unlock(&inode->i_lock);+continue;+}+__iget(inode);+spin_unlock(&inode->i_lock);+spin_unlock(&mk->mk_decrypted_inodes_lock);++shrink_dcache_inode(inode);+iput(toput_inode);+toput_inode=inode;++spin_lock(&mk->mk_decrypted_inodes_lock);+}++spin_unlock(&mk->mk_decrypted_inodes_lock);+iput(toput_inode);+}++staticintevict_decrypted_inodes(structfscrypt_master_key*mk)+{+structfscrypt_info*ci;+structinode*inode;+LIST_HEAD(dispose);+unsignedlongnum_busy=0;+unsignedlongbusy_ino;++spin_lock(&mk->mk_decrypted_inodes_lock);++list_for_each_entry(ci,&mk->mk_decrypted_inodes,ci_master_key_link){+inode=ci->ci_inode;+spin_lock(&inode->i_lock);++if(inode->i_state&(I_FREEING|I_WILL_FREE))+gotonext;++if(atomic_read(&inode->i_count)||+(inode->i_state&~I_REFERENCED)){+num_busy++;+busy_ino=inode->i_ino;+gotonext;+}++inode->i_state|=I_FREEING;+inode_lru_list_del(inode);+list_add(&inode->i_lru,&dispose);+next:+spin_unlock(&inode->i_lock);+}++spin_unlock(&mk->mk_decrypted_inodes_lock);++evict_inode_list(&dispose);++if(unlikely(num_busy)){+pr_warn_ratelimited("fscrypt: %lu inodes still busy after removing key with description %*phN (%sino: %lu)\n",+num_busy,master_key_spec_len(&mk->mk_spec),+mk->mk_spec.max_specifier,+(num_busy>1?"example ":""),busy_ino);+return-EBUSY;+}++return0;+}++staticinttry_to_lock_encrypted_files(structsuper_block*sb,+structfscrypt_master_key*mk)+{+interr1;+interr2;++/*+*Aninodecan'tbeevictedwhileitstillhasdirtypages,orwhile+*theinodeitselfisstilldirty.Thus,wefirsthavetocleanall+*theinodesin->mk_decrypted_inodes.+*+*Justdoittheeasyway:callsync_filesystem().It'soverkill,but+*itworks,andit'smoreimportanttominimizetheamountofcacheswe+*dropthantheamountofdatawesync.Also,unprivilegeduserscan+*alreadycallsync_filesystem()viasys_syncfs()orsys_sync().+*/+down_read(&sb->s_umount);+err1=sync_filesystem(sb);+up_read(&sb->s_umount);++/*+*Inodesarepinnedbytheirdentries,sowehavetoevictthedentries+*first.Wecouldpotentiallyjustcallshrink_dcache_sb()here,but+*thatwouldbeoverkill,andanunprivilegedusershouldn'tbeableto+*evictalldentriesfortheentirefilesystem.Instead,gothrough+*theinodes'aliaslistsandtrytoevicteachdentry.+*/+evict_dentries_for_decrypted_inodes(mk);++/*+*Finally,iteratethrough->mk_decrypted_inodesandevictasmany+*inodesaswecan.Similarly,wecouldpotentiallyjustcall+*invalidate_inodes()here,butthatwouldbeoverkill,andan+*unprivilegedusershouldn'tbeabletoevictallinodesforthe+*entirefilesystem.+*+*Notethatideally,wewouldn'treallyevicttheinodes,butrather+*justfreetheir->i_crypt_infoandpagecache.Butevictionis*much*+*easiertocorrectlyimplementwithoutcausinguse-after-freebugs.+*/+err2=evict_decrypted_inodes(mk);++returnerr1?:err2;+}++/*+*Trytoremoveanfscryptmasterencryptionkey.+*+*Firstwewipetheactualmasterkeysecretfrommemory,sothatnomore+*inodescanbeunlockedwithit.Then,wetrytoevictallcachedinodesthat+*hadbeenunlockedusingthekey.Sincethiscanfailforin-useinodes,this+*isexpectedtobeusedincooperationwithuserspaceensuringthatnoneof+*thefilesarestillopen.+*+*If,nevertheless,someinodescouldnotbeevicted,wereturn-EBUSY+*(althoughwestillevictedasmanyinodesaspossible)andkeepthe'struct+*key'andthe'structfscrypt_master_key'aroundtokeeptrackofthelistof+*remaininginodes.Userspacecanthenretrytheioctllatertoretryevicting+*theremaininginodes,oralternativelycanaddthesecretkeyagain.+*+*Notethateventhoughwewipetheencryption*keys*frommemory,decrypted+*datacanlikelystillbefoundinmemory,e.g.inpagecachepagesthathave+*beenfreed.Wipingsuchdataiscurrentlyoutofscope,shortofuserswho+*maychoosetoenablepageandslabpoisoningsystemwide.+*/+intfscrypt_ioctl_remove_key(structfile*filp,constvoid__user*uarg)+{+structsuper_block*sb=file_inode(filp)->i_sb;+structfscrypt_remove_key_argsarg;+structkey*key;+structfscrypt_master_key*mk;+interr;+booldead;++if(copy_from_user(&arg,uarg,sizeof(arg)))+return-EFAULT;++if(memchr_inv(arg.reserved,0,sizeof(arg.reserved)))+return-EINVAL;++if(!valid_key_spec(&arg.key_spec))+return-EINVAL;++if(!capable(CAP_SYS_ADMIN))+return-EACCES;++key=find_master_key(sb,&arg.key_spec);+if(IS_ERR(key))+returnPTR_ERR(key);+mk=key->payload.data[0];++down_write(&key->sem);+dead=false;+if(is_master_key_secret_present(&mk->mk_secret)){+wipe_master_key_secret(&mk->mk_secret);+dead=refcount_dec_and_test(&mk->mk_refcount);+}+up_write(&key->sem);+if(dead){+key_invalidate(key);+err=0;+}else{+err=try_to_lock_encrypted_files(sb,mk);+}+key_put(key);+returnerr;+}+EXPORT_SYMBOL_GPL(fscrypt_ioctl_remove_key);+staticvoidderive_crypt_complete(structcrypto_async_request*req,intrc){structfscrypt_completion_result*ecr=req->data;
@@ -455,10 +711,20 @@ static int find_and_derive_key_legacy(const struct inode *inode,returnerr;}-/* Find the master key, then derive the inode's actual encryption key */+/*+*Findthemasterkey,thenderivetheinode'sactualencryptionkey.+*+*Ifthemasterkeyisfoundinthefilesystem-levelkeyring,thenthe+*corresponding'structkey'isreturnedread-lockedin*master_key_ret.This+*isneededbecauseweneedtoholdthesemaphoreuntilwelinkthenew+*fscrypt_infointo->mk_decrypted_inodes,butinthecasewheremultipletasks+*areracingtosetupaninode's->i_crypt_info,onlythewinnershouldlink+*itsfscrypt_infointo->mk_decrypted_inodes.+*/staticintfind_and_derive_key(conststructinode*inode,conststructfscrypt_context*ctx,-u8*derived_key,unsignedintderived_keysize)+u8*derived_key,unsignedintderived_keysize,+structkey**master_key_ret){structkey*key;structfscrypt_master_key*mk;
@@ -481,6 +747,13 @@ static int find_and_derive_key(const struct inode *inode,derived_keysize);}mk=key->payload.data[0];+down_read(&key->sem);++/* Has the secret been removed using FS_IOC_REMOVE_ENCRYPTION_KEY? */+if(!is_master_key_secret_present(&mk->mk_secret)){+err=-ENOKEY;+gotoout_release_key;+}/**Requirethatthemasterkeybeatleastaslongasthederivedkey.
@@ -493,12 +766,19 @@ static int find_and_derive_key(const struct inode *inode,key->description,mk->mk_secret.size,derived_keysize);err=-ENOKEY;-gotoout_put_key;+gotoout_release_key;}err=derive_key_aes(mk->mk_secret.raw,ctx,derived_key,derived_keysize);-out_put_key:+if(err)+gotoout_release_key;++*master_key_ret=key;+return0;++out_release_key:+up_read(&key->sem);key_put(key);returnerr;}
--
2.15.0.rc0.271.g36b669edcc-goog
------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
From: Eric Biggers <hidden> Date: 2017-10-23 21:42:25
From: Eric Biggers <redacted>
Add a new ioctl, FS_IOC_GET_ENCRYPTION_KEY_STATUS. Given a key
specified by 'struct fscrypt_key_specifier' (the same way a key is
specified for the ioctls which add and remove keys), it returns status
information in a 'struct fscrypt_get_key_status_args'.
The main motivation for this is that applications need to be able to
check whether an encrypted directory is "unlocked" or not, so that they
can add the key if it is not, and avoid adding the key (which may
involve prompting the user for a passphrase) if it already is. It's
possible to use some workarounds such as checking whether opening a
regular file fails with ENOKEY, or checking whether the filenames "look
like gibberish" or not. However, no workaround is usable in all cases.
It's also not a simple matter of locked/unlocked anymore because we also
have a partially locked state, where FS_IOC_REMOVE_ENCRYPTION_KEY has
removed the secret but some encrypted files are still in use. This
difference can be important for applications. Moreover, after later
patches some applications will also need a way to determine whether a
key was added by the current user vs. by some other user.
Ideally we'd have been able to use keyctl_search() to check whether a
key is present or not, rather than introducing a new ioctl. However,
even if the keyrings permission system was fixed to allow granting
read-only access to a keyring (currently the "Search" permission allows
keyctl_invalidate()), it still wouldn't work out because the fscrypt
master keys can be in states other than just present/absent, as
described above. Moreover, we'd still have to at least add an ioctl
which retrieves the ID of ->s_master_keys.
/proc/keys cannot really be the API either, since reading /proc/keys
involves iterating through all keys on the system and is primarily meant
as a debugging interface. We also don't necessarily want to grant
everyone VIEW access to all the fscrypt keys as that would imply
everyone being able to list them as well.
Therefore, a new ioctl to get an fscrypt key's status seems like the
best solution. It is also consistent with the ioctls to add and remove
keys.
Signed-off-by: Eric Biggers <redacted>
---
fs/crypto/keyinfo.c | 64 +++++++++++++++++++++++++++++++++++++++++
include/linux/fscrypt_notsupp.h | 6 ++++
include/linux/fscrypt_supp.h | 1 +
include/uapi/linux/fscrypt.h | 17 +++++++++++
4 files changed, 88 insertions(+)
@@ -978,6 +978,21 @@ long ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)caseEXT4_IOC_GET_ENCRYPTION_POLICY:returnfscrypt_ioctl_get_policy(filp,(void__user*)arg);+caseFS_IOC_ADD_ENCRYPTION_KEY:+if(!ext4_has_feature_encrypt(sb))+return-EOPNOTSUPP;+returnfscrypt_ioctl_add_key(filp,(void__user*)arg);++caseFS_IOC_REMOVE_ENCRYPTION_KEY:+if(!ext4_has_feature_encrypt(sb))+return-EOPNOTSUPP;+returnfscrypt_ioctl_remove_key(filp,(constvoid__user*)arg);++caseFS_IOC_GET_ENCRYPTION_KEY_STATUS:+if(!ext4_has_feature_encrypt(sb))+return-EOPNOTSUPP;+returnfscrypt_ioctl_get_key_status(filp,(void__user*)arg);+caseEXT4_IOC_FSGETXATTR:{structfsxattrfa;
@@ -1102,6 +1117,9 @@ long ext4_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)caseEXT4_IOC_SET_ENCRYPTION_POLICY:caseEXT4_IOC_GET_ENCRYPTION_PWSALT:caseEXT4_IOC_GET_ENCRYPTION_POLICY:+caseFS_IOC_ADD_ENCRYPTION_KEY:+caseFS_IOC_REMOVE_ENCRYPTION_KEY:+caseFS_IOC_GET_ENCRYPTION_KEY_STATUS:caseEXT4_IOC_SHUTDOWN:caseFS_IOC_GETFSMAP:break;
@@ -2651,6 +2651,12 @@ long f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)returnf2fs_ioc_get_encryption_policy(filp,arg);caseF2FS_IOC_GET_ENCRYPTION_PWSALT:returnf2fs_ioc_get_encryption_pwsalt(filp,arg);+caseFS_IOC_ADD_ENCRYPTION_KEY:+returnfscrypt_ioctl_add_key(filp,(void__user*)arg);+caseFS_IOC_REMOVE_ENCRYPTION_KEY:+returnfscrypt_ioctl_remove_key(filp,(constvoid__user*)arg);+caseFS_IOC_GET_ENCRYPTION_KEY_STATUS:+returnfscrypt_ioctl_get_key_status(filp,(void__user*)arg);caseF2FS_IOC_GARBAGE_COLLECT:returnf2fs_ioc_gc(filp,arg);caseF2FS_IOC_GARBAGE_COLLECT_RANGE:
@@ -2731,6 +2737,9 @@ long f2fs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)caseF2FS_IOC_SET_ENCRYPTION_POLICY:caseF2FS_IOC_GET_ENCRYPTION_PWSALT:caseF2FS_IOC_GET_ENCRYPTION_POLICY:+caseFS_IOC_ADD_ENCRYPTION_KEY:+caseFS_IOC_REMOVE_ENCRYPTION_KEY:+caseFS_IOC_GET_ENCRYPTION_KEY_STATUS:caseF2FS_IOC_GARBAGE_COLLECT:caseF2FS_IOC_GARBAGE_COLLECT_RANGE:caseF2FS_IOC_WRITE_CHECKPOINT:
@@ -205,6 +205,15 @@ long ubifs_ioctl(struct file *file, unsigned int cmd, unsigned long arg)#endif}+caseFS_IOC_ADD_ENCRYPTION_KEY:+returnfscrypt_ioctl_add_key(file,(void__user*)arg);++caseFS_IOC_REMOVE_ENCRYPTION_KEY:+returnfscrypt_ioctl_remove_key(file,(constvoid__user*)arg);++caseFS_IOC_GET_ENCRYPTION_KEY_STATUS:+returnfscrypt_ioctl_get_key_status(file,(void__user*)arg);+default:return-ENOTTY;}
@@ -222,6 +231,9 @@ long ubifs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)break;caseFS_IOC_SET_ENCRYPTION_POLICY:caseFS_IOC_GET_ENCRYPTION_POLICY:+caseFS_IOC_ADD_ENCRYPTION_KEY:+caseFS_IOC_REMOVE_ENCRYPTION_KEY:+caseFS_IOC_GET_ENCRYPTION_KEY_STATUS:break;default:return-ENOIOCTLCMD;
--
2.15.0.rc0.271.g36b669edcc-goog
------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
From: Eric Biggers <hidden> Date: 2017-10-23 21:42:30
From: Eric Biggers <redacted>
Currently, fscrypt policies and xattrs identify the master key by
master_key_descriptor, which is an arbitrary 8-byte value used to form
the description of the keyring key. However, there is no verification
that the key descriptor in any way corresponds with the key payload.
Since ->i_crypt_info by necessity gets set up on a "first come, first
serve" basis, this flaw allows a process with only read-only access to
an encrypted file or directory to provide the wrong key, causing the
file contents or directory listing to be corrupted. This is a bug with
security implications which must be fixed.
To fix this bug without simply locking down adding keys to root, we must
replace master_key_descriptor with a cryptographic hash of the key. We
name the replacement master_key_identifier and make it 16 bytes long,
which should provide enough collision resistance, and more importantly
preimage resistance, without bloating the size of the encryption xattr
too much.
This will be both an on-disk format and API change, since we'll need to
define new versions of both the fscrypt_context and fscrypt_policy.
This patch begins this process by defining the UAPI changes to manage v2
policies. (Note: we jump to version 2 even though the previous policy
version number was 0 because the fscrypt_context was actually already
using version 1, not version 0. It would be really confusing to have
them always be 1 off from each other.)
The existing FS_IOC_SET_ENCRYPTION_POLICY will be used to set a v2
policy, as the kernel will be able to examine the 'version' field.
However, a new ioctl FS_IOC_GET_ENCRYPTION_POLICY_EX is needed to get a
v2 policy, since the returned struct needs to be larger. This ioctl
includes a size field as input, so that it can be used for both v1 and
v2 policies as well as any new policy versions that may get added in the
future.
Signed-off-by: Eric Biggers <redacted>
---
include/uapi/linux/fscrypt.h | 37 +++++++++++++++++++++++++++++++++++--
1 file changed, 35 insertions(+), 2 deletions(-)
--
2.15.0.rc0.271.g36b669edcc-goog
------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
From: Eric Biggers <hidden> Date: 2017-10-23 21:42:32
From: Eric Biggers <redacted>
Update the fscrypt internals to handle v2 encryption policies. This
includes supporting getting and setting them, translating them to/from
the on-disk fscrypt_context. It also includes storing either a v1 or v2
policy struct in the fscrypt_info for use by fscrypt_inherit_context()
and fscrypt_has_permitted_context(). (Previously we were storing the
individual fields, but it is a bit easier to store a policy struct.)
An fscrypt_policy_v1 (previously 'fscrypt_policy') maps to/from an
fscrypt_context_v1 (previously 'fscrypt_context'), while an
fscrypt_policy_v2 maps to/from an fscrypt_context_v2.
Key management for v2 policies will be implemented by later patches.
For now, attempting to set up an inode's key just fails with EOPNOTSUPP.
Signed-off-by: Eric Biggers <redacted>
---
fs/crypto/fname.c | 4 +-
fs/crypto/fscrypt_private.h | 172 ++++++++++++++++---
fs/crypto/keyinfo.c | 70 ++++----
fs/crypto/policy.c | 368 ++++++++++++++++++++++++++++------------
include/linux/fscrypt.h | 2 +-
include/linux/fscrypt_notsupp.h | 6 +
include/linux/fscrypt_supp.h | 1 +
7 files changed, 452 insertions(+), 171 deletions(-)
@@ -29,39 +29,159 @@#define FSCRYPT_MIN_KEY_SIZE 16-/**-*Encryptioncontextforinode-*-*Protectorformat:-*1byte:Protectorformat(1=thisversion)-*1byte:Filecontentsencryptionmode-*1byte:Filenamesencryptionmode-*1byte:Flags-*8bytes:MasterKeydescriptor-*16bytes:EncryptionKeyderivationnonce-*/-structfscrypt_context{-u8format;+structfscrypt_context_v1{++u8version;/* FSCRYPT_CONTEXT_V1 */++/* Same meaning as in v2 context --- see below */u8contents_encryption_mode;u8filenames_encryption_mode;u8flags;++/*+*Descriptorforthisfile'smasterkeyinaprocess-subscribedkeyring+*---typicallyasessionkeyring,orauserkeyringlinkedintoa+*sessionorusersessionkeyring.Thisisanarbitraryvalue,chosen+*byuserspacewhenitsettheencryptionpolicy.Itis*not*+*necessarilytiedtotheactualkeypayload.+*/u8master_key_descriptor[FSCRYPT_KEY_DESCRIPTOR_SIZE];++/*+*Auniquevalueusedincombinationwiththemasterkeytoderivethe+*file'sactualencryptionkey+*/u8nonce[FS_KEY_DERIVATION_NONCE_SIZE];-}__packed;+};++structfscrypt_context_v2{++u8version;/* FSCRYPT_CONTEXT_V2 */++/* Encryption mode for the contents of regular files */+u8contents_encryption_mode;-#define FS_ENCRYPTION_CONTEXT_FORMAT_V1 1+/* Encryption mode for filenames in directories and symlink targets */+u8filenames_encryption_mode;++/* Options that affect how encryption is done (e.g. padding amount) */+u8flags;++/* Reserved, must be 0 */+u8reserved[4];++/*+*Acryptographichashofthemasterkeywithwhichthisfileis+*encrypted+*/+u8master_key_identifier[FSCRYPT_KEY_IDENTIFIER_SIZE];++/*+*Auniquevalueusedincombinationwiththemasterkeytoderivethe+*file'sactualencryptionkey+*/+u8nonce[FS_KEY_DERIVATION_NONCE_SIZE];+};++/**+*fscrypt_context-theencryptioncontextforaninode+*+*Filesystemsusuallystorethisinanextendedattribute.Itidentifiesthe+*encryptionalgorithmandkeywithwhichthefileisencrypted.+*+*Sincethisisstoredon-disk,becarefulnottoreorderfieldsoraddany+*implicitpaddingbytes!+*/+unionfscrypt_context{+structfscrypt_context_v1v1;+structfscrypt_context_v2v2;+};++#define FSCRYPT_CONTEXT_V1 1+#define FSCRYPT_CONTEXT_V2 2++staticinlineintfscrypt_context_size(constunionfscrypt_context*ctx)+{+switch(ctx->v1.version){+caseFSCRYPT_CONTEXT_V1:+returnsizeof(ctx->v1);+caseFSCRYPT_CONTEXT_V2:+returnsizeof(ctx->v2);+}+return0;+}++staticinlinebool+fscrypt_valid_context_format(constunionfscrypt_context*ctx,intsize)+{+returnsize>=1&&size==fscrypt_context_size(ctx);+}++#undef fscrypt_policy+unionfscrypt_policy{+structfscrypt_policy_v1v1;+structfscrypt_policy_v2v2;+};++staticinlineintfscrypt_policy_size(constunionfscrypt_policy*policy)+{+switch(policy->v1.version){+caseFSCRYPT_POLICY_VERSION_LEGACY:+returnsizeof(policy->v1);+caseFSCRYPT_POLICY_VERSION_2:+returnsizeof(policy->v2);+}+return0;+}++staticinlineu8+fscrypt_policy_contents_mode(constunionfscrypt_policy*policy)+{+switch(policy->v1.version){+caseFSCRYPT_POLICY_VERSION_LEGACY:+returnpolicy->v1.contents_encryption_mode;+caseFSCRYPT_POLICY_VERSION_2:+returnpolicy->v2.contents_encryption_mode;+}+BUG();+}++staticinlineu8+fscrypt_policy_fnames_mode(constunionfscrypt_policy*policy)+{+switch(policy->v1.version){+caseFSCRYPT_POLICY_VERSION_LEGACY:+returnpolicy->v1.filenames_encryption_mode;+caseFSCRYPT_POLICY_VERSION_2:+returnpolicy->v2.filenames_encryption_mode;+}+BUG();+}++staticinlineint+fscrypt_policy_fname_padding(constunionfscrypt_policy*policy)+{+switch(policy->v1.version){+caseFSCRYPT_POLICY_VERSION_LEGACY:+return4<<(policy->v1.flags&FSCRYPT_POLICY_FLAGS_PAD_MASK);+caseFSCRYPT_POLICY_VERSION_2:+return4<<(policy->v2.flags&FSCRYPT_POLICY_FLAGS_PAD_MASK);+}+BUG();+}/*-*Apointertothisstructureisstoredinthefilesystem'sin-core-*representationofaninode.+*fscrypt_info-the"encryption key"foraninode+*+*Whenanencryptedfile'skeyismadeavailable,aninstanceofthisstructis+*allocatedandstoredin->i_crypt_info.Oncecreated,itremainsuntilthe+*inodeisevicted.*/structfscrypt_info{-u8ci_data_mode;-u8ci_filename_mode;-u8ci_flags;++/* The actual crypto transforms needed for encryption and decryption */structcrypto_skcipher*ci_ctfm;structcrypto_cipher*ci_essiv_tfm;-u8ci_master_key_descriptor[FSCRYPT_KEY_DESCRIPTOR_SIZE];/**Themasterkeywithwhichthisinodewasunlocked(decrypted).This
@@ -78,6 +198,9 @@ struct fscrypt_info {*->ci_master_keyisset.*/structinode*ci_inode;++/* The encryption policy used by this file */+unionfscrypt_policyci_policy;};typedefenum{
@@ -786,7 +786,7 @@ static int find_and_derive_key_legacy(const struct inode *inode,*itsfscrypt_infointo->mk_decrypted_inodes.*/staticintfind_and_derive_key(conststructinode*inode,-conststructfscrypt_context*ctx,+constunionfscrypt_context*ctx,u8*derived_key,unsignedintderived_keysize,structkey**master_key_ret){
@@ -795,9 +795,15 @@ static int find_and_derive_key(const struct inode *inode,structfscrypt_key_specifiermk_spec;interr;-mk_spec.type=FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR;-memcpy(mk_spec.descriptor,ctx->master_key_descriptor,-FSCRYPT_KEY_DESCRIPTOR_SIZE);+switch(ctx->v1.version){+caseFSCRYPT_CONTEXT_V1:+mk_spec.type=FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR;+memcpy(mk_spec.descriptor,ctx->v1.master_key_descriptor,+FSCRYPT_KEY_DESCRIPTOR_SIZE);+break;+default:+return-EOPNOTSUPP;+}key=find_master_key(inode->i_sb,&mk_spec);if(IS_ERR(key)){
@@ -807,7 +813,7 @@ static int find_and_derive_key(const struct inode *inode,*Asalegacyfallback,wesearchthecurrenttask'ssubscribed*keyringsinadditionto->s_master_keys.*/-returnfind_and_derive_key_legacy(inode,ctx,derived_key,+returnfind_and_derive_key_legacy(inode,&ctx->v1,derived_key,derived_keysize);}mk=key->payload.data[0];
@@ -833,7 +839,7 @@ static int find_and_derive_key(const struct inode *inode,gotoout_release_key;}-err=derive_key_aes(mk->mk_secret.raw,ctx,+err=derive_key_aes(mk->mk_secret.raw,&ctx->v1,derived_key,derived_keysize);if(err)gotoout_release_key;
@@ -862,17 +868,10 @@ static int determine_cipher_type(struct fscrypt_info *ci, struct inode *inode,{u32mode;-if(!fscrypt_valid_enc_modes(ci->ci_data_mode,ci->ci_filename_mode)){-pr_warn_ratelimited("fscrypt: inode %lu uses unsupported encryption modes (contents mode %d, filenames mode %d)\n",-inode->i_ino,-ci->ci_data_mode,ci->ci_filename_mode);-return-EINVAL;-}-if(S_ISREG(inode->i_mode)){-mode=ci->ci_data_mode;+mode=fscrypt_policy_contents_mode(&ci->ci_policy);}elseif(S_ISDIR(inode->i_mode)||S_ISLNK(inode->i_mode)){-mode=ci->ci_filename_mode;+mode=fscrypt_policy_fnames_mode(&ci->ci_policy);}else{WARN_ONCE(1,"fscrypt: filesystem tried to load encryption info for inode %lu, which is not encryptable (file type %d)\n",inode->i_ino,(inode->i_mode&S_IFMT));
@@ -1006,33 +1005,31 @@ int fscrypt_get_encryption_info(struct inode *inode)returnres;/* Fake up a context for an unencrypted directory */memset(&ctx,0,sizeof(ctx));-ctx.format=FS_ENCRYPTION_CONTEXT_FORMAT_V1;-ctx.contents_encryption_mode=FSCRYPT_MODE_AES_256_XTS;-ctx.filenames_encryption_mode=FSCRYPT_MODE_AES_256_CTS;-memset(ctx.master_key_descriptor,0x42,+ctx.v1.version=FSCRYPT_CONTEXT_V1;+ctx.v1.contents_encryption_mode=FSCRYPT_MODE_AES_256_XTS;+ctx.v1.filenames_encryption_mode=FSCRYPT_MODE_AES_256_CTS;+memset(ctx.v1.master_key_descriptor,0x42,FSCRYPT_KEY_DESCRIPTOR_SIZE);-}elseif(res!=sizeof(ctx)){-return-EINVAL;+res=sizeof(ctx.v1);}-if(ctx.format!=FS_ENCRYPTION_CONTEXT_FORMAT_V1)-return-EINVAL;--if(ctx.flags&~FSCRYPT_POLICY_FLAGS_VALID)+if(!fscrypt_valid_context_format(&ctx,res))return-EINVAL;crypt_info=kmem_cache_zalloc(fscrypt_info_cachep,GFP_NOFS);if(!crypt_info)return-ENOMEM;-crypt_info->ci_flags=ctx.flags;-crypt_info->ci_data_mode=ctx.contents_encryption_mode;-crypt_info->ci_filename_mode=ctx.filenames_encryption_mode;-memcpy(crypt_info->ci_master_key_descriptor,ctx.master_key_descriptor,-FSCRYPT_KEY_DESCRIPTOR_SIZE);+fscrypt_context_to_policy(&ctx,&crypt_info->ci_policy);+if(!fscrypt_supported_policy(&crypt_info->ci_policy)){+res=-EINVAL;+pr_warn_ratelimited("fscrypt: inode %lu uses unsupported encryption policy\n",+inode->i_ino);+gotoout;+}-res=determine_cipher_type(crypt_info,inode,-&cipher_str,&derived_keysize);+res=determine_cipher_type(crypt_info,inode,&cipher_str,+&derived_keysize);if(res)gotoout;
@@ -1065,7 +1062,8 @@ int fscrypt_get_encryption_info(struct inode *inode)gotoout;if(S_ISREG(inode->i_mode)&&-crypt_info->ci_data_mode==FSCRYPT_MODE_AES_128_CBC){+(fscrypt_policy_contents_mode(&crypt_info->ci_policy)==+FSCRYPT_MODE_AES_128_CBC)){res=init_essiv_generator(crypt_info,derived_key,derived_keysize);if(res){
@@ -13,84 +14,227 @@#include<linux/mount.h>#include"fscrypt_private.h"-/*-*checkwhetheranencryptionpolicyisconsistentwithanencryptioncontext-*/-staticboolis_encryption_context_consistent_with_policy(-conststructfscrypt_context*ctx,-conststructfscrypt_policy*policy)+boolfscrypt_policies_equal(constunionfscrypt_policy*policy1,+constunionfscrypt_policy*policy2)+{+if(policy1->v1.version!=policy2->v1.version)+returnfalse;++return!memcmp(policy1,policy2,fscrypt_policy_size(policy1));+}++boolfscrypt_supported_policy(constunionfscrypt_policy*policy_u)+{+switch(policy_u->v1.version){+caseFSCRYPT_POLICY_VERSION_LEGACY:{+conststructfscrypt_policy_v1*policy=&policy_u->v1;++if(!fscrypt_valid_enc_modes(policy->contents_encryption_mode,+policy->filenames_encryption_mode))+returnfalse;++if(policy->flags&~FSCRYPT_POLICY_FLAGS_VALID)+returnfalse;++returntrue;+}+caseFSCRYPT_POLICY_VERSION_2:{+conststructfscrypt_policy_v2*policy=&policy_u->v2;++if(!fscrypt_valid_enc_modes(policy->contents_encryption_mode,+policy->filenames_encryption_mode))+returnfalse;++if(policy->flags&~FSCRYPT_POLICY_FLAGS_VALID)+returnfalse;++if(memchr_inv(policy->reserved,0,sizeof(policy->reserved)))+returnfalse;++returntrue;+}+}+returnfalse;+}++staticvoidfscrypt_policy_to_context(constunionfscrypt_policy*policy_u,+unionfscrypt_context*ctx_u){-returnmemcmp(ctx->master_key_descriptor,policy->master_key_descriptor,-FSCRYPT_KEY_DESCRIPTOR_SIZE)==0&&-(ctx->flags==policy->flags)&&-(ctx->contents_encryption_mode==-policy->contents_encryption_mode)&&-(ctx->filenames_encryption_mode==-policy->filenames_encryption_mode);+memset(ctx_u,0,sizeof(*ctx_u));++switch(policy_u->v1.version){+caseFSCRYPT_POLICY_VERSION_LEGACY:{+conststructfscrypt_policy_v1*policy=&policy_u->v1;+structfscrypt_context_v1*ctx=&ctx_u->v1;++ctx->version=FSCRYPT_CONTEXT_V1;+ctx->contents_encryption_mode=+policy->contents_encryption_mode;+ctx->filenames_encryption_mode=+policy->filenames_encryption_mode;+ctx->flags=policy->flags;+memcpy(ctx->master_key_descriptor,+policy->master_key_descriptor,+sizeof(ctx->master_key_descriptor));+get_random_bytes(ctx->nonce,sizeof(ctx->nonce));+break;+}+caseFSCRYPT_POLICY_VERSION_2:{+conststructfscrypt_policy_v2*policy=&policy_u->v2;+structfscrypt_context_v2*ctx=&ctx_u->v2;++ctx->version=FSCRYPT_CONTEXT_V2;+ctx->contents_encryption_mode=+policy->contents_encryption_mode;+ctx->filenames_encryption_mode=+policy->filenames_encryption_mode;+ctx->flags=policy->flags;+memcpy(ctx->reserved,policy->reserved,sizeof(ctx->reserved));+memcpy(ctx->master_key_identifier,+policy->master_key_identifier,+sizeof(ctx->master_key_identifier));+get_random_bytes(ctx->nonce,sizeof(ctx->nonce));+break;+}+default:+BUG();+}}-staticintcreate_encryption_context_from_policy(structinode*inode,-conststructfscrypt_policy*policy)+voidfscrypt_context_to_policy(constunionfscrypt_context*ctx_u,+unionfscrypt_policy*policy_u){-structfscrypt_contextctx;+memset(policy_u,0,sizeof(*policy_u));++switch(ctx_u->v1.version){+caseFSCRYPT_CONTEXT_V1:{+conststructfscrypt_context_v1*ctx=&ctx_u->v1;+structfscrypt_policy_v1*policy=&policy_u->v1;++policy->version=FSCRYPT_POLICY_VERSION_LEGACY;+policy->contents_encryption_mode=+ctx->contents_encryption_mode;+policy->filenames_encryption_mode=+ctx->filenames_encryption_mode;+policy->flags=ctx->flags;+memcpy(policy->master_key_descriptor,+ctx->master_key_descriptor,+sizeof(policy->master_key_descriptor));+return;+}+caseFSCRYPT_CONTEXT_V2:{+conststructfscrypt_context_v2*ctx=&ctx_u->v2;+structfscrypt_policy_v2*policy=&policy_u->v2;++policy->version=FSCRYPT_POLICY_VERSION_2;+policy->contents_encryption_mode=+ctx->contents_encryption_mode;+policy->filenames_encryption_mode=+ctx->filenames_encryption_mode;+policy->flags=ctx->flags;+memcpy(policy->reserved,ctx->reserved,+sizeof(policy->reserved));+memcpy(policy->master_key_identifier,+ctx->master_key_identifier,+sizeof(policy->master_key_identifier));+return;+}+default:+BUG();+}+}-ctx.format=FS_ENCRYPTION_CONTEXT_FORMAT_V1;-memcpy(ctx.master_key_descriptor,policy->master_key_descriptor,-FSCRYPT_KEY_DESCRIPTOR_SIZE);+staticintfscrypt_get_policy(structinode*inode,unionfscrypt_policy*policy)+{+unionfscrypt_contextctx;+intret;++if(inode->i_crypt_info){+*policy=inode->i_crypt_info->ci_policy;+return0;+}++if(!IS_ENCRYPTED(inode))+return-ENODATA;-if(!fscrypt_valid_enc_modes(policy->contents_encryption_mode,-policy->filenames_encryption_mode))+ret=inode->i_sb->s_cop->get_context(inode,&ctx,sizeof(ctx));+if(ret<0)+return(ret==-ERANGE)?-EINVAL:ret;+if(!fscrypt_valid_context_format(&ctx,ret))return-EINVAL;+fscrypt_context_to_policy(&ctx,policy);+return0;+}++staticintset_encryption_policy(structinode*inode,+constunionfscrypt_policy*policy)+{+unionfscrypt_contextctx;-if(policy->flags&~FSCRYPT_POLICY_FLAGS_VALID)+if(!fscrypt_supported_policy(policy))return-EINVAL;-ctx.contents_encryption_mode=policy->contents_encryption_mode;-ctx.filenames_encryption_mode=policy->filenames_encryption_mode;-ctx.flags=policy->flags;-BUILD_BUG_ON(sizeof(ctx.nonce)!=FS_KEY_DERIVATION_NONCE_SIZE);-get_random_bytes(ctx.nonce,FS_KEY_DERIVATION_NONCE_SIZE);+fscrypt_policy_to_context(policy,&ctx);++if(policy->v1.version==FSCRYPT_POLICY_VERSION_LEGACY){+/*+*Theoriginalencryptionpolicyversionprovidednowayof+*verifyingthatthecorrectmasterkeywassupplied,whichwas+*insecureinscenarioswheremultipleusershaveaccesstothe+*sameencryptedfiles(evenjustread-onlyaccess).Thenew+*encryptionpolicyversionfixesthisandalsoimpliesuseof+*animprovedkeyderivationfunctionandallowsnon-rootusers+*tosecurelyremovekeys.Soaslongascompatibilitywith+*oldkernelsisn'trequired,itisrecommendedtousethenew+*policyversionforallnewencrypteddirectories.+*/+pr_warn_once("%s (pid %d) is setting less secure v1 encryption policy; recommend upgrading to v2.\n",+current->comm,current->pid);+}-returninode->i_sb->s_cop->set_context(inode,&ctx,sizeof(ctx),NULL);+returninode->i_sb->s_cop->set_context(inode,&ctx,+fscrypt_context_size(&ctx),+NULL);}intfscrypt_ioctl_set_policy(structfile*filp,constvoid__user*arg){-structfscrypt_policypolicy;+unionfscrypt_policypolicy;+unionfscrypt_policyexisting_policy;structinode*inode=file_inode(filp);+intsize;intret;-structfscrypt_contextctx;-if(copy_from_user(&policy,arg,sizeof(policy)))+if(copy_from_user(&policy,arg,sizeof(u8)))+return-EFAULT;++size=fscrypt_policy_size(&policy);+if(size==0)+return-EINVAL;++if(copy_from_user((u8*)&policy+1,arg+1,size-1))return-EFAULT;if(!inode_owner_or_capable(inode))return-EACCES;-if(policy.version!=0)-return-EINVAL;-ret=mnt_want_write_file(filp);if(ret)returnret;inode_lock(inode);-ret=inode->i_sb->s_cop->get_context(inode,&ctx,sizeof(ctx));+ret=fscrypt_get_policy(inode,&existing_policy);if(ret==-ENODATA){if(!S_ISDIR(inode->i_mode))ret=-ENOTDIR;elseif(!inode->i_sb->s_cop->empty_dir(inode))ret=-ENOTEMPTY;else-ret=create_encryption_context_from_policy(inode,-&policy);-}elseif(ret==sizeof(ctx)&&-is_encryption_context_consistent_with_policy(&ctx,-&policy)){-/* The file already uses the same encryption policy. */-ret=0;-}elseif(ret>=0||ret==-ERANGE){+ret=set_encryption_policy(inode,&policy);+}elseif(ret==-EINVAL||+(ret==0&&!fscrypt_policies_equal(&policy,+&existing_policy))){/* The file already uses a different encryption policy. */ret=-EEXIST;}
@@ -102,36 +246,61 @@ int fscrypt_ioctl_set_policy(struct file *filp, const void __user *arg)}EXPORT_SYMBOL(fscrypt_ioctl_set_policy);+/* Original ioctl version; can only get the original policy version */intfscrypt_ioctl_get_policy(structfile*filp,void__user*arg){-structinode*inode=file_inode(filp);-structfscrypt_contextctx;-structfscrypt_policypolicy;-intres;+unionfscrypt_policypolicy;+interr;-if(!IS_ENCRYPTED(inode))-return-ENODATA;+err=fscrypt_get_policy(file_inode(filp),&policy);+if(err)+returnerr;-res=inode->i_sb->s_cop->get_context(inode,&ctx,sizeof(ctx));-if(res<0&&res!=-ERANGE)-returnres;-if(res!=sizeof(ctx))+if(policy.v1.version!=FSCRYPT_POLICY_VERSION_LEGACY)return-EINVAL;-if(ctx.format!=FS_ENCRYPTION_CONTEXT_FORMAT_V1)++if(copy_to_user(arg,&policy,sizeof(policy.v1)))+return-EFAULT;+return0;+}+EXPORT_SYMBOL(fscrypt_ioctl_get_policy);++/* Extended ioctl version; can get policies of any version */+intfscrypt_ioctl_get_policy_ex(structfile*filp,void__user*_arg)+{+structfscrypt_get_policy_ex_args__user*arg=_arg;+__u64size;+__u64actual_size;+size_tpolicy_size;+unionfscrypt_policypolicy;+interr;++if(get_user(size,&arg->size))+return-EFAULT;++if(size<=offsetof(structfscrypt_get_policy_ex_args,policy)||+size>=65536)return-EINVAL;-policy.version=0;-policy.contents_encryption_mode=ctx.contents_encryption_mode;-policy.filenames_encryption_mode=ctx.filenames_encryption_mode;-policy.flags=ctx.flags;-memcpy(policy.master_key_descriptor,ctx.master_key_descriptor,-FSCRYPT_KEY_DESCRIPTOR_SIZE);+err=fscrypt_get_policy(file_inode(filp),&policy);+if(err)+returnerr;++policy_size=fscrypt_policy_size(&policy);+actual_size=offsetof(structfscrypt_get_policy_ex_args,policy)++policy_size;-if(copy_to_user(arg,&policy,sizeof(policy)))+if(size<actual_size)+return-EOVERFLOW;++if(put_user(actual_size,&arg->size))+return-EFAULT;++if(copy_to_user(&arg->policy,&policy,policy_size))return-EFAULT;return0;}-EXPORT_SYMBOL(fscrypt_ioctl_get_policy);+EXPORT_SYMBOL(fscrypt_ioctl_get_policy_ex);/***fscrypt_has_permitted_context()-isafile'sencryptionpolicypermitted
@@ -155,10 +324,8 @@ EXPORT_SYMBOL(fscrypt_ioctl_get_policy);*/intfscrypt_has_permitted_context(structinode*parent,structinode*child){-conststructfscrypt_operations*cops=parent->i_sb->s_cop;-conststructfscrypt_info*parent_ci,*child_ci;-structfscrypt_contextparent_ctx,child_ctx;-intres;+unionfscrypt_policyparent_policy,child_policy;+interr;/* No restrictions on file types which are never encrypted */if(!S_ISREG(child->i_mode)&&!S_ISDIR(child->i_mode)&&
@@ -86,7 +86,7 @@ struct fscrypt_operations {};/* Maximum value for the third parameter of fscrypt_operations.set_context(). */-#define FSCRYPT_SET_CONTEXT_MAX_SIZE 28+#define FSCRYPT_SET_CONTEXT_MAX_SIZE 40staticinlineboolfscrypt_dummy_context_enabled(structinode*inode){
--
2.15.0.rc0.271.g36b669edcc-goog
------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
From: Eric Biggers <hidden> Date: 2017-10-23 21:42:33
From: Eric Biggers <redacted>
For v2 encryption policies, we need to hash the master key to compute a
master_key_identifier. Naively, we could just use a truncated SHA-512
or another common cryptographic hash function. However, that would
cause the same key material to be used in two different ways: both as
input to the hash function, and as input to the AES-ECB-based KDF when
deriving the per-inode encryption keys. There *probably* isn't any
practical attack on that, but still it would be better "crypto hygiene"
to use the key material for one purpose only, e.g. just for a KDF.
It also happens that the AES-based KDF is on our list of things to fix.
While it does generate unique derived keys with sufficient entropy, it
is nonstandard and has some problems that don't exist in standard KDFs
such as HKDF. For example, the AES-based KDF is reversible: given a
derived key and nonce, an attacker can easily compute the master key.
This was maybe okay for the original threat model of ext4 encryption
where the master key and derived keys were considered equally hard to
compromise. But now we would like to be more robust against threats
such as a derived key being compromised through a timing attack, or a
derived key for an in-use file being compromised after the master key
has already been wiped from memory via FS_IOC_REMOVE_ENCRYPTION_KEY.
HKDF also has other advantages over the AES-ECB-based KDF such as evenly
distributing the entropy from the input key material and being more
extensible to deriving other key material that may be needed.
Since we're introducing a new encryption policy version that already
includes an on-disk format change, and we also now have a good place to
cache an HMAC transform for each master key (struct fscrypt_master_key)
so that HKDF can be implemented efficiently, we finally have a chance to
switch to HKDF to derive the per-file keys.
In addition, we'll use HKDF to derive the master_key_identifier,
avoiding the need for a separate cryptographic hash primitive. This is
secure because the output from HKDF is cryptographically isolated, i.e.
sending some output in the clear doesn't reveal any other output, in a
computational sense. (This is assuming that application-specific info
strings aren't repeated between different uses of HKDF, but we'll use
context bytes to ensure that.)
Thus, this patch adds an implementation of HKDF to keyinfo.c, using an
HMAC transform allocated from the crypto API. Later patches will make
use of it.
Note that using HKDF-SHA512 as the key derivation function will
introduce a dependency on the security and implementation of SHA-512,
whereas before we were using only AES for both key derivation and
encryption. However, by using HMAC rather than the hash function
directly, HKDF is designed to remain secure even if various classes of
attacks, e.g. collision attacks, are found against the underlying
unkeyed hash function. Even HMAC-MD5 is still considered secure in
practice, despite MD5 itself having been heavily compromised. And
meanwhile, the AES-based KDF used the public nonce as the cipher *key*,
which is an unusual case which probably hasn't undergone much
cryptanalysis. HKDF-SHA512 seems like a safer bet.
We *could* actually avoid introducing a hash primitive by instantiating
HKDF-Expand with CMAC-AES256 as the pseudorandom function rather than
HMAC-SHA512. That would work; however, the HKDF specification doesn't
explicitly allow a non-HMAC pseudorandom function, so it would be less
standard. It would also require skipping HKDF-Extract and making the
API accept only 32-byte master keys, since otherwise HKDF-Extract using
CMAC-AES would produce a pseudorandom key only 16 bytes long which would
only be enough for AES-128, not AES-256.
References:
- RFC 5869. "HMAC-based Extract-and-Expand Key Derivation Function
(HKDF)". https://tools.ietf.org/html/rfc5869
- Krawczyk (2010). "Cryptographic Extraction and Key Derivation: The
HKDF Scheme". https://eprint.iacr.org/2010/264.pdf
Signed-off-by: Eric Biggers <redacted>
---
fs/crypto/Kconfig | 2 +
fs/crypto/keyinfo.c | 180 +++++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 181 insertions(+), 1 deletion(-)
@@ -14,10 +19,181 @@#include<linux/scatterlist.h>#include<linux/seq_file.h>#include<crypto/aes.h>+#include<crypto/hash.h>#include<crypto/sha.h>#include"fscrypt_private.h"-staticstructcrypto_shash*essiv_hash_tfm;+/*+*AnyunkeyedcryptographichashalgorithmcanbeusedwithHKDF,butweuse+*SHA-512becauseitisreasonablysecureandefficient;andsinceitproduces+*a64-bytedigest,derivinganAES-256-XTSkeypreservesall64bytesof+*entropyfromthemasterkeyandrequiresonlyoneiterationofHKDF-Expand.+*/+#define HKDF_HMAC_ALG "hmac(sha512)"+#define HKDF_HASHLEN SHA512_DIGEST_SIZE++/*+*HKDFconsistsoftwosteps:+*+*1.HKDF-Extract:extractafixed-lengthpseudorandomkeyfromthe+*inputkeyingmaterialandoptionalsalt.+*2.HKDF-Expand:expandthepseudorandomkeyintooutputkeyingmaterialof+*anylength,parameterizedbyanapplication-specificinfostring.+*+*HKDF-Extractcanbeskippediftheinputisalreadyagoodpseudorandomkey+*thatisatleastaslongasthehash.Whilethefscryptmasterkeysshould+*alreadybegoodpseudorandomkeys,whenusingencryptionalgorithmsthatuse+*shortkeys(e.g.AES-128-CBC)we'dliketopermitthemasterkeytobe+*shorterthanHKDF_HASHLENbytes.Thus,westillmustdoHKDF-Extract.+*+*Ideally,HKDF-Extractwouldbepassedarandomsaltforeachdistinctinput+*key.DetailsabouttheadvantagesofarandomsaltcanbefoundintheHKDF+*paper(Krawczyk,2010;"Cryptographic Extraction and Key Derivation: The HKDF+*Scheme"). However, we do not have the ability to store a salt on a+*per-master-keybasis.Thus,wehavetouseafixedsalt.Thisissufficient+*aslongasthemasterkeysarealreadypseudorandomandarelongenoughto+*makedictionaryattacksinfeasible.Thisshouldbethecaseifuserspace+*usedacryptographicallysecurerandomnumbergenerator,e.g./dev/urandom,+*togeneratethemasterkeysanditwasinitializedwithsufficiententropy.+*+*Forthefixedsaltweuse"fscrypt_hkdf_salt"ratherthandefaultofall0's+*definedbyRFC-5869.Thisisonlytobeslightlymorerobustagainst+*userspace(unwisely)reusingthemasterkeysfordifferentpurposes.+*Logically,it'smorelikelythatthekeyswouldbepassedtounsalted+*HKDF-SHA512thanspecificallyto"fscrypt_hkdf_salt"-saltedHKDF-SHA512.+*Ofcourse,arandomsaltwouldbebetterforthispurpose.+*/++#define HKDF_SALT "fscrypt_hkdf_salt"+#define HKDF_SALT_LEN (sizeof(HKDF_SALT) - 1)++/*+*HKDF-Extract(RFC-5869section2.2).Thisextractsapseudorandomkey'prk'+*fromtheinputkeymaterial'ikm'andasalt.Seeexplanationaboveforwhy+*weuseafixedsalt.+*/+staticinthkdf_extract(structcrypto_shash*hmac_tfm,+constu8*ikm,unsignedintikmlen,+u8prk[HKDF_HASHLEN])+{+SHASH_DESC_ON_STACK(desc,hmac_tfm);+interr;++desc->tfm=hmac_tfm;+desc->flags=0;++err=crypto_shash_setkey(hmac_tfm,HKDF_SALT,HKDF_SALT_LEN);+if(err)+gotoout;++err=crypto_shash_digest(desc,ikm,ikmlen,prk);+out:+shash_desc_zero(desc);+returnerr;+}++/*+*HKDF-Expand(RFC-5869section2.3).Thisexpandsthepseudorandomkey,which+*hasalreadybeenkeyedinto'hmac_tfm',into'okmlen'bytesofoutputkeying+*material,parameterizedbytheapplication-specificinformationstringof+*'info'prefixedwiththe'context'byte.('context'isn'tpartoftheHKDF+*specification;it'sjustaprefixweaddtoourapplication-specificinfo+*stringstoguaranteethatwedon'taccidentallyrepeataninfostringwhen+*usingHKDFfordifferentpurposes.)+*/+staticinthkdf_expand(structcrypto_shash*hmac_tfm,u8context,+constu8*info,unsignedintinfolen,+u8*okm,unsignedintokmlen)+{+SHASH_DESC_ON_STACK(desc,hmac_tfm);+interr;+constu8*prev=NULL;+unsignedinti;+u8counter=1;+u8tmp[HKDF_HASHLEN];++desc->tfm=hmac_tfm;+desc->flags=0;++if(unlikely(okmlen>255*HKDF_HASHLEN))+return-EINVAL;++for(i=0;i<okmlen;i+=HKDF_HASHLEN){++err=crypto_shash_init(desc);+if(err)+gotoout;++if(prev){+err=crypto_shash_update(desc,prev,HKDF_HASHLEN);+if(err)+gotoout;+}++err=crypto_shash_update(desc,&context,1);+if(err)+gotoout;++err=crypto_shash_update(desc,info,infolen);+if(err)+gotoout;++if(okmlen-i<HKDF_HASHLEN){+err=crypto_shash_finup(desc,&counter,1,tmp);+if(err)+gotoout;+memcpy(&okm[i],tmp,okmlen-i);+memzero_explicit(tmp,sizeof(tmp));+}else{+err=crypto_shash_finup(desc,&counter,1,&okm[i]);+if(err)+gotoout;+}+counter++;+prev=&okm[i];+}+err=0;+out:+shash_desc_zero(desc);+returnerr;+}++/*+*PrecomputeHKDF-Extractusingthemasterkeyastheinputkeymaterial,then+*returnanHMACtransformthatiskeyedusingtheresultingpseudorandomkey.+*ThiscanbeusedtoderivefurtherkeymaterialusingHKDF-Expand.+*/+staticstructcrypto_shash*allocate_hmac_tfm(constu8*master_key,u32size)+{+structcrypto_shash*hmac_tfm;+u8prk[HKDF_HASHLEN];+interr;++hmac_tfm=crypto_alloc_shash(HKDF_HMAC_ALG,0,0);+if(IS_ERR(hmac_tfm)){+pr_warn("fscrypt: error allocating "HKDF_HMAC_ALG": %ld\n",+PTR_ERR(hmac_tfm));+gotoout;+}++BUG_ON(crypto_shash_digestsize(hmac_tfm)!=sizeof(prk));++err=hkdf_extract(hmac_tfm,master_key,size,prk);+if(err)+gotofail;++err=crypto_shash_setkey(hmac_tfm,prk,sizeof(prk));+if(err)+gotofail;+out:+memzero_explicit(prk,sizeof(prk));+returnhmac_tfm;++fail:+crypto_free_shash(hmac_tfm);+hmac_tfm=ERR_PTR(err);+gotoout;+}/**fscrypt_master_key_secret-secretkeymaterialofanin-usemasterkey
--
2.15.0.rc0.271.g36b669edcc-goog
------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
From: Eric Biggers <hidden> Date: 2017-10-23 21:42:36
From: Eric Biggers <redacted>
Extend the FS_IOC_ADD_ENCRYPTION_KEY and FS_IOC_REMOVE_ENCRYPTION_KEY
ioctls to support adding and removing keys for use by v2 encryption
policies.
Keys for v2 encryption policies are identified by a 16-byte
"identifier", which is a cryptographic hash of the key, instead of by an
8-byte "descriptor". For FS_IOC_ADD_ENCRYPTION_KEY, the kernel
calculates the key identifier and copies it to userspace. Userspace is
not allowed to choose the key identifier, since the kernel would have to
recalculate it anyway to verify that it is correct.
For FS_IOC_REMOVE_ENCRYPTION_KEY, userspace provides the key identifier
rather than the key descriptor to identify the key to be removed. For
both ioctls, a type field indicates whether the old or new way of
specifying keys is being used. A common structure is used, 'struct
fscrypt_key_specifier', and it has some extra space just in case we have
to introduce a new way to identify keys in the future.
Note that keys for v1 and v2 encryption policies are both stored in
->s_master_keys, but their descriptions will be of different lengths.
Therefore, they cannot be mixed up when we search for a key.
For now the ioctls still always require capable(CAP_SYS_ADMIN). We'll
be able to relax that soon, but only for v2 policies.
Signed-off-by: Eric Biggers <redacted>
---
fs/crypto/keyinfo.c | 75 ++++++++++++++++++++++++++++++++++++++++----
include/uapi/linux/fscrypt.h | 2 ++
2 files changed, 71 insertions(+), 6 deletions(-)
@@ -872,6 +925,10 @@ static int derive_key_aes(const u8 *master_key,*Searchthecurrenttask'ssubscribedkeyringsfora"logon"keywith*descriptionprefix:descriptor,andiffoundacquireareadlockonitand*returnapointertoitsvalidatedpayloadin*payload_ret.+*+*Thisisonlyusedforv1encryptionpolicies,wherekeysareidentifiedby+*master_key_descriptor.Withnewerpolicyversions,onlythefilesystem-level+*keyring(->s_master_keys)issupported.*/staticstructkey*find_and_lock_process_key(constchar*prefix,
@@ -977,17 +1034,23 @@ static int find_and_derive_key(const struct inode *inode,memcpy(mk_spec.descriptor,ctx->v1.master_key_descriptor,FSCRYPT_KEY_DESCRIPTOR_SIZE);break;+caseFSCRYPT_CONTEXT_V2:+mk_spec.type=FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER;+memcpy(mk_spec.identifier,ctx->v2.master_key_identifier,+FSCRYPT_KEY_IDENTIFIER_SIZE);+break;default:-return-EOPNOTSUPP;+return-EOPNOTSUPP;/* should have been checked earlier too */}key=find_master_key(inode->i_sb,&mk_spec);if(IS_ERR(key)){-if(key!=ERR_PTR(-ENOKEY))+if(key!=ERR_PTR(-ENOKEY)||+ctx->v1.version!=FSCRYPT_CONTEXT_V1)returnPTR_ERR(key);/*-*Asalegacyfallback,wesearchthecurrenttask'ssubscribed-*keyringsinadditionto->s_master_keys.+*Asalegacyfallbackforv1policies,wesearchthecurrent+*task'ssubscribedkeyringsinadditionto->s_master_keys.*/returnfind_and_derive_key_legacy(inode,&ctx->v1,derived_key,derived_keysize);
--
2.15.0.rc0.271.g36b669edcc-goog
------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
From: Eric Biggers <hidden> Date: 2017-10-23 21:42:36
From: Eric Biggers <redacted>
The AES-ECB-based method we're using to derive the per-inode encryption
keys is nonstandard and has a number of problems, such as being
trivially reversible. Fix these problems for v2 encryption policies by
deriving the keys using HKDF-SHA512 instead. The inode's nonce prefixed
with a context byte is used as the application-specific info string.
Supposedly, one of the reasons that HKDF wasn't used originally was
because of performance concerns. However, we actually can derive on the
order of 1 million keys per second, so it's likely not a bottleneck in
practice. Moreover, although HKDF-SHA512 can require a bit more actual
crypto work per key derivation than the old KDF, the real world
performance is actually just as good or even better than the old KDF.
This is because the old KDF has to allocate and key a new "ecb(aes)"
transform for every key derivation (since it's keyed with the nonce
rather than the master key), whereas with HKDF we simply use a cached,
pre-keyed "hmac(sha512)" transform. And the old KDF often spends more
time allocating its crypto transform than doing actual crypto work.
Another benefit to switching to HKDF is that we no longer need to hold
the raw master key in memory, but rather only an HMAC transform keyed by
a pseudorandom key extracted from the master key. Of course, for the
software HMAC implementation there is no security benefit, since
compromising the state of the HMAC transform is equivalent to
compromising the raw master key. However, there could be a security
benefit if used with an HMAC implementation that holds the secret in
crypto accelerator hardware rather than in main memory.
Signed-off-by: Eric Biggers <redacted>
---
fs/crypto/fscrypt_private.h | 4 ++--
fs/crypto/keyinfo.c | 58 ++++++++++++++++++++++++++++++++++-----------
2 files changed, 46 insertions(+), 16 deletions(-)
@@ -213,10 +214,18 @@ static struct crypto_shash *allocate_hmac_tfm(const u8 *master_key, u32 size)*/structfscrypt_master_key_secret{-/* Size of the raw key in bytes */+/*+*Forv2policykeys:anHMACtransformkeyedbythepseudorandomkey+*generatedbycomputingHKDF-Extractwiththerawmasterkeyasthe+*inputkeymaterial.Thisisusedtoefficientlyderivetheper-inode+*encryptionkeysusingHKDF-Expandlater.+*/+structcrypto_shash*hmac_tfm;++/* Size of the raw key in bytes. Set even if ->raw isn't set. */u32size;-/* The raw key */+/* For v1 policy keys: the raw key. */u8raw[FSCRYPT_MAX_KEY_SIZE];};
@@ -571,14 +581,17 @@ int fscrypt_ioctl_add_key(struct file *filp, void __user *_uarg)gotoout_wipe_secret;if(arg.key_spec.type==FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER){-structcrypto_shash*hmac_tfm;-hmac_tfm=allocate_hmac_tfm(secret.raw,secret.size);-if(IS_ERR(hmac_tfm)){-err=PTR_ERR(hmac_tfm);+secret.hmac_tfm=allocate_hmac_tfm(secret.raw,secret.size);+if(IS_ERR(secret.hmac_tfm)){+err=PTR_ERR(secret.hmac_tfm);+secret.hmac_tfm=NULL;gotoout_wipe_secret;}+/* The raw key is no longer needed */+memzero_explicit(secret.raw,sizeof(secret.raw));+/**Hashthemasterkeytogetthekeyidentifier,thenreturnit*touserspace.Specifically,wederivethekeyidentifier
@@ -1078,8 +1095,21 @@ static int find_and_derive_key(const struct inode *inode,gotoout_release_key;}-err=derive_key_aes(mk->mk_secret.raw,&ctx->v1,-derived_key,derived_keysize);+/*+*Derivetheinode'sencryptionkey,giventhemasterkeyandthenonce+*fromtheinode'sfscrypt_context.v1policiesusedanAES-ECB-based+*KDF(KeyDerivationFunction).NewerpoliciesuseHKDF-SHA512,which+*fixesanumberofproblemswiththeAES-ECB-basedKDF.+*/+if(ctx->v1.version==FSCRYPT_CONTEXT_V1){+err=derive_key_aes(mk->mk_secret.raw,&ctx->v1,+derived_key,derived_keysize);+}else{+err=hkdf_expand(mk->mk_secret.hmac_tfm,+HKDF_CONTEXT_PER_FILE_KEY,+ctx->v2.nonce,sizeof(ctx->v2.nonce),+derived_key,derived_keysize);+}if(err)gotoout_release_key;
@@ -1275,8 +1305,8 @@ int fscrypt_get_encryption_info(struct inode *inode)gotoout;/*-*Thiscannotbeastackbufferbecauseitispassedtothescatterlist-*cryptoAPIaspartofkeyderivation.+*Thiscannotbeastackbufferbecauseitmaybepassedtothe+*scatterlistcryptoAPIduringkeyderivation.*/res=-ENOMEM;derived_key=kmalloc(FS_MAX_KEY_SIZE,GFP_NOFS);
--
2.15.0.rc0.271.g36b669edcc-goog
------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
From: Eric Biggers <hidden> Date: 2017-10-23 21:42:38
From: Eric Biggers <redacted>
Having the FS_IOC_ADD_ENCRYPTION_KEY and FS_IOC_REMOVE_ENCRYPTION_KEY
ioctls be root-only is sufficient for some users of filesystem
encryption, e.g. Android and Chromium OS where all the encryption keys
are managed by a privileged process. However, it is not sufficient for
general use where non-root users are setting up encrypted directories.
If these ioctls were root-only, such users would have to continue to use
process-subscribed keyrings and would continue to run into all the
problems noted earlier, including visibility problems when processes
running under different UIDs need to be able to access the files, and
the inability to remove the key, "locking" the directory.
Fortunately, we can indeed make the ioctls unprivileged, but only for v2
encryption policies and only after a few additional changes which this
patch implements.
First, to allow any user to add a key with filesystem-level visibility,
the keys must be identified using a cryptographic hash so that users
cannot add the wrong key for other users' files. We use the
key_identifier for this, which is why v2 encryption policies are a
requirement.
Second, we charge each key a user adds to their quota for the keyrings
service. Thus, a user can't cause a denial of service by adding a very
large number of keys.
Third, we have to be careful about when a key is allowed to be removed,
given that multiple users may add the same key (although that should
*not* normally be the case; it's astronomically unlikely for keys to
collide by chance, so it should only happen as a result of explicit
sharing or compromise). One might consider only allowing the first user
who added a key to remove it, or allowing any user who knows a key to
remove it. But neither of those are good enough because we don't want a
user on the system who knows another user's key to be able to cause a
denial of service where the former user removes the latter user's key at
an inopportune time. After all, it *should* be the case that if you
have an encrypted directory and you give everyone in the world the key,
including malicious users on the same system, it should still be no less
secure than *not* using encryption.
The solution is to keep track of which users have added a key and only
really remove the key once all users have removed it.
However, it is tolerated that a user will be unable to remove a key,
i.e. unable to "lock" their encrypted files, if another user has added
the same key. But in a sense, this is actually a good thing because it
will avoid providing a false notion of security where a key appears to
have been "removed" when actually it's still in memory, available to any
attacker who compromises the operating system kernel.
Signed-off-by: Eric Biggers <redacted>
---
fs/crypto/crypto.c | 7 +
fs/crypto/fscrypt_private.h | 1 +
fs/crypto/keyinfo.c | 341 ++++++++++++++++++++++++++++++++++++++++---
include/uapi/linux/fscrypt.h | 11 +-
4 files changed, 338 insertions(+), 22 deletions(-)
@@ -510,12 +682,31 @@ static int add_master_key(struct super_block *sb,gotoout_put_key;}else{structfscrypt_master_key*mk=key->payload.data[0];+structkey*mk_user;boolrekey;/* Found the key in ->s_master_keys */down_write(&key->sem);+/*+*Ifthecurrentuserisalreadyin->mk_users,thenthere's+*nothingtodo.+*/+if(mk->mk_users){+mk_user=find_master_key_user(mk);+if(mk_user!=ERR_PTR(-ENOKEY)){+up_write(&key->sem);+if(IS_ERR(mk_user)){+err=PTR_ERR(mk_user);+}else{+key_put(mk_user);+err=0;+}+gotoout_put_key;+}+}+/**Takeareferenceifwe'llbere-adding->mk_secret.Ifwe*couldn'ttakeareference,thenthekeyisbeingremovedfrom
@@ -531,9 +722,24 @@ static int add_master_key(struct super_block *sb,gotoretry;}+/* Add the current user to ->mk_users */+if(mk->mk_users){+err=add_master_key_user(mk);+if(err){+up_write(&key->sem);+if(rekey&&+refcount_dec_and_test(&mk->mk_refcount))+key_invalidate(key);+gotoout_put_key;+}+}+/* Re-add the secret key material if needed */-if(rekey)+if(rekey){+down_write(&mk->mk_secret_sem);move_master_key_secret(&mk->mk_secret,secret);+up_write(&mk->mk_secret_sem);+}up_write(&key->sem);}err=0;
@@ -548,6 +754,23 @@ static int add_master_key(struct super_block *sb,*Addamasterencryptionkeytothefilesystem,causingallfileswhichwere*encryptedwithittoappear"unlocked"(decrypted)whenaccessed.Thekey*canberemovedlaterbyFS_IOC_REMOVE_ENCRYPTION_KEY.+*+*Whenaddingakeyforusebyv1encryptionpolicies,thisioctlis+*privileged,anduserspacemustprovidethe'key_descriptor'.+*+*Whenaddingakeyforusebyv2+encryptionpolicies,thisioctlis+*unprivileged.Thisisneeded,ingeneral,toallownon-rootuserstouse+*encryptionwithoutencounteringthevisibilityproblemsofprocess-subscribed+*keyringsandtheinabilitytoproperlyremovekeys.Thisworksbyhaving+*eachkeyidentifiedbyitscryptographicallysecurehash---the+*'key_identifier'.Thecryptographichashensuresthatamalicioususer+*cannotaddthewrongkeyforagivenidentifier.Furthermore,eachaddedkey+*ischargedtotheappropriateuser'squotaforthekeyringsservice,which+*preventsamalicioususerfromaddingtoomanykeys.Finally,weforbida+*userfromremovingakeywhileotherusershaveaddedittoo,whichprevents+*auserwhoknowsanotheruser'skeyfromcausingadenial-of-serviceby+*removingitataninopportunetime.(Wetoleratethatauserwhoknowsakey+*canpreventotherusersfromremovingit.)*/intfscrypt_ioctl_add_key(structfile*filp,void__user*_uarg){
@@ -1029,11 +1329,12 @@ static int find_and_derive_key_legacy(const struct inode *inode,*Findthemasterkey,thenderivetheinode'sactualencryptionkey.**Ifthemasterkeyisfoundinthefilesystem-levelkeyring,thenthe-*corresponding'structkey'isreturnedread-lockedin*master_key_ret.This-*isneededbecauseweneedtoholdthesemaphoreuntilwelinkthenew-*fscrypt_infointo->mk_decrypted_inodes,butinthecasewheremultipletasks-*areracingtosetupaninode's->i_crypt_info,onlythewinnershouldlink-*itsfscrypt_infointo->mk_decrypted_inodes.+*corresponding'structkey'isreturnedin*master_key_retwith+*->mk_secret_semread-locked.Thisisneededbecauseweneedtohold+*->mk_secret_semuntilwelinkthenewfscrypt_infointo+*->mk_decrypted_inodes,butinthecasewheremultipletasksareracingtoset+*upaninode's->i_crypt_info,onlythewinnershouldlinkitsfscrypt_info+*into->mk_decrypted_inodes.*/staticintfind_and_derive_key(conststructinode*inode,constunionfscrypt_context*ctx,
@@ -1073,7 +1374,7 @@ static int find_and_derive_key(const struct inode *inode,derived_keysize);}mk=key->payload.data[0];-down_read(&key->sem);+down_read(&mk->mk_secret_sem);/* Has the secret been removed using FS_IOC_REMOVE_ENCRYPTION_KEY? */if(!is_master_key_secret_present(&mk->mk_secret)){
@@ -1117,7 +1418,7 @@ static int find_and_derive_key(const struct inode *inode,return0;out_release_key:-up_read(&key->sem);+up_read(&mk->mk_secret_sem);key_put(key);returnerr;}
@@ -1361,7 +1662,9 @@ int fscrypt_get_encryption_info(struct inode *inode)}out:if(master_key){-up_read(&master_key->sem);+structfscrypt_master_key*mk=master_key->payload.data[0];++up_read(&mk->mk_secret_sem);key_put(master_key);}if(res==-ENOKEY)
--
2.15.0.rc0.271.g36b669edcc-goog
------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
From: Eric Biggers <hidden> Date: 2017-10-23 21:42:38
From: Eric Biggers <redacted>
By looking up the master keys in a filesystem-level keyring rather than
in the calling processes' key hierarchy, it becomes possible for a user
to set an encryption policy which refers to some key they don't actually
know, then encrypt their files using that key. Cryptographically this
shouldn't actually be a major problem; for one, every file will still be
encrypted with a unique derived key, rather than with the master key
directly. But to be on the safe side, enforce that a v2 encryption
policy can only be set if the user has previously added the key, or has
capable(CAP_FOWNER).
We tolerate that this problem will continue to exist for v1 encryption
policies, however; there is no way around that.
Signed-off-by: Eric Biggers <redacted>
---
fs/crypto/fscrypt_private.h | 2 ++
fs/crypto/keyinfo.c | 42 ++++++++++++++++++++++++++++++++++++++++++
fs/crypto/policy.c | 6 ++++++
3 files changed, 50 insertions(+)
@@ -170,6 +170,7 @@ static int set_encryption_policy(struct inode *inode,constunionfscrypt_policy*policy){unionfscrypt_contextctx;+interr;if(!fscrypt_supported_policy(policy))return-EINVAL;
@@ -190,6 +191,11 @@ static int set_encryption_policy(struct inode *inode,*/pr_warn_once("%s (pid %d) is setting less secure v1 encryption policy; recommend upgrading to v2.\n",current->comm,current->pid);+}else{+err=fscrypt_verify_key_added(inode->i_sb,+policy->v2.master_key_identifier);+if(err)+returnerr;}returninode->i_sb->s_cop->set_context(inode,&ctx,
--
2.15.0.rc0.271.g36b669edcc-goog
------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
From: Eric Biggers <hidden> Date: 2017-10-23 21:42:39
From: Eric Biggers <redacted>
FS_IOC_GET_ENCRYPTION_POLICY_EX allows filesystem encryption users to
retrieve the encryption policy for files and directories that use a v2
encryption policy. Unlike the original FS_IOC_GET_ENCRYPTION_POLICY,
FS_IOC_GET_ENCRYPTION_POLICY_EX is also extensible to new versions of
the policy struct that may be added in the future.
Signed-off-by: Eric Biggers <redacted>
---
fs/ext4/ioctl.c | 4 ++++
1 file changed, 4 insertions(+)
@@ -978,6 +978,9 @@ long ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)caseEXT4_IOC_GET_ENCRYPTION_POLICY:returnfscrypt_ioctl_get_policy(filp,(void__user*)arg);+caseFS_IOC_GET_ENCRYPTION_POLICY_EX:+returnfscrypt_ioctl_get_policy_ex(filp,(void__user*)arg);+caseFS_IOC_ADD_ENCRYPTION_KEY:if(!ext4_has_feature_encrypt(sb))return-EOPNOTSUPP;
@@ -1117,6 +1120,7 @@ long ext4_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)caseEXT4_IOC_SET_ENCRYPTION_POLICY:caseEXT4_IOC_GET_ENCRYPTION_PWSALT:caseEXT4_IOC_GET_ENCRYPTION_POLICY:+caseFS_IOC_GET_ENCRYPTION_POLICY_EX:caseFS_IOC_ADD_ENCRYPTION_KEY:caseFS_IOC_REMOVE_ENCRYPTION_KEY:caseFS_IOC_GET_ENCRYPTION_KEY_STATUS:
--
2.15.0.rc0.271.g36b669edcc-goog
------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
From: Eric Biggers <hidden> Date: 2017-10-23 21:42:41
From: Eric Biggers <redacted>
FS_IOC_GET_ENCRYPTION_POLICY_EX allows filesystem encryption users to
retrieve the encryption policy for files and directories that use a v2
encryption policy. Unlike the original FS_IOC_GET_ENCRYPTION_POLICY,
FS_IOC_GET_ENCRYPTION_POLICY_EX is also extensible to new versions of
the policy struct that may be added in the future.
Signed-off-by: Eric Biggers <redacted>
---
fs/f2fs/file.c | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
@@ -1887,11 +1887,6 @@ static int f2fs_ioc_set_encryption_policy(struct file *filp, unsigned long arg)returnfscrypt_ioctl_set_policy(filp,(constvoid__user*)arg);}-staticintf2fs_ioc_get_encryption_policy(structfile*filp,unsignedlongarg)-{-returnfscrypt_ioctl_get_policy(filp,(void__user*)arg);-}-staticintf2fs_ioc_get_encryption_pwsalt(structfile*filp,unsignedlongarg){structinode*inode=file_inode(filp);
@@ -2647,10 +2642,12 @@ long f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)returnf2fs_ioc_fitrim(filp,arg);caseF2FS_IOC_SET_ENCRYPTION_POLICY:returnf2fs_ioc_set_encryption_policy(filp,arg);-caseF2FS_IOC_GET_ENCRYPTION_POLICY:-returnf2fs_ioc_get_encryption_policy(filp,arg);caseF2FS_IOC_GET_ENCRYPTION_PWSALT:returnf2fs_ioc_get_encryption_pwsalt(filp,arg);+caseF2FS_IOC_GET_ENCRYPTION_POLICY:+returnfscrypt_ioctl_get_policy(filp,(void__user*)arg);+caseFS_IOC_GET_ENCRYPTION_POLICY_EX:+returnfscrypt_ioctl_get_policy_ex(filp,(void__user*)arg);caseFS_IOC_ADD_ENCRYPTION_KEY:returnfscrypt_ioctl_add_key(filp,(void__user*)arg);caseFS_IOC_REMOVE_ENCRYPTION_KEY:
@@ -2737,6 +2734,7 @@ long f2fs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)caseF2FS_IOC_SET_ENCRYPTION_POLICY:caseF2FS_IOC_GET_ENCRYPTION_PWSALT:caseF2FS_IOC_GET_ENCRYPTION_POLICY:+caseFS_IOC_GET_ENCRYPTION_POLICY_EX:caseFS_IOC_ADD_ENCRYPTION_KEY:caseFS_IOC_REMOVE_ENCRYPTION_KEY:caseFS_IOC_GET_ENCRYPTION_KEY_STATUS:
From: Eric Biggers <hidden> Date: 2017-10-23 21:42:42
From: Eric Biggers <redacted>
FS_IOC_GET_ENCRYPTION_POLICY_EX allows filesystem encryption users to
retrieve the encryption policy for files and directories that use a v2
encryption policy. Unlike the original FS_IOC_GET_ENCRYPTION_POLICY,
FS_IOC_GET_ENCRYPTION_POLICY_EX is also extensible to new versions of
the policy struct that may be added in the future.
Signed-off-by: Eric Biggers <redacted>
---
fs/ubifs/ioctl.c | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
@@ -197,13 +197,12 @@ long ubifs_ioctl(struct file *file, unsigned int cmd, unsigned long arg)return-EOPNOTSUPP;#endif}-caseFS_IOC_GET_ENCRYPTION_POLICY:{-#ifdef CONFIG_UBIFS_FS_ENCRYPTION++caseFS_IOC_GET_ENCRYPTION_POLICY:returnfscrypt_ioctl_get_policy(file,(void__user*)arg);-#else-return-EOPNOTSUPP;-#endif-}++caseFS_IOC_GET_ENCRYPTION_POLICY_EX:+returnfscrypt_ioctl_get_policy_ex(file,(void__user*)arg);caseFS_IOC_ADD_ENCRYPTION_KEY:returnfscrypt_ioctl_add_key(file,(void__user*)arg);
@@ -231,6 +230,7 @@ long ubifs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)break;caseFS_IOC_SET_ENCRYPTION_POLICY:caseFS_IOC_GET_ENCRYPTION_POLICY:+caseFS_IOC_GET_ENCRYPTION_POLICY_EX:caseFS_IOC_ADD_ENCRYPTION_KEY:caseFS_IOC_REMOVE_ENCRYPTION_KEY:caseFS_IOC_GET_ENCRYPTION_KEY_STATUS:
From: Eric Biggers <hidden> Date: 2017-10-23 21:42:44
From: Eric Biggers <redacted>
Update the fscrypt documentation file to catch up to all the latest
changes, including the new ioctls to manage master encryption keys in
the filesystem-level keyring, the support for v2 encryption policies,
and the new key derivation function.
Signed-off-by: Eric Biggers <redacted>
---
Documentation/filesystems/fscrypt.rst | 565 ++++++++++++++++++++++++++++------
1 file changed, 472 insertions(+), 93 deletions(-)
@@ -72,6 +72,9 @@ Online attacks fscrypt (and storage encryption in general) can only provide limited protection, if any at all, against online attacks. In detail:+Side-channel attacks+~~~~~~~~~~~~~~~~~~~~+ fscrypt is only resistant to side-channel attacks, such as timing or electromagnetic attacks, to the extent that the underlying Linux Cryptographic API algorithms are. If a vulnerable algorithm is used,
@@ -80,29 +83,86 @@ attacker to mount a side channel attack against the online system. Side channel attacks may also be mounted against applications consuming decrypted data.-After an encryption key has been provided, fscrypt is not designed to-hide the plaintext file contents or filenames from other users on the-same system, regardless of the visibility of the keyring key.-Instead, existing access control mechanisms such as file mode bits,-POSIX ACLs, LSMs, or mount namespaces should be used for this purpose.-Also note that as long as the encryption keys are *anywhere* in-memory, an online attacker can necessarily compromise them by mounting-a physical attack or by exploiting any kernel security vulnerability-which provides an arbitrary memory read primitive.--While it is ostensibly possible to "evict" keys from the system,-recently accessed encrypted files will remain accessible at least-until the filesystem is unmounted or the VFS caches are dropped, e.g.-using ``echo 2 > /proc/sys/vm/drop_caches``. Even after that, if the-RAM is compromised before being powered off, it will likely still be-possible to recover portions of the plaintext file contents, if not-some of the encryption keys as well. (Since Linux v4.12, all-in-kernel keys related to fscrypt are sanitized before being freed.-However, userspace would need to do its part as well.)--Currently, fscrypt does not prevent a user from maliciously providing-an incorrect key for another user's existing encrypted files. A-protection against this is planned.+Unauthorized file access+~~~~~~~~~~~~~~~~~~~~~~~~++After an encryption key has been added, fscrypt does not hide the+plaintext file contents or filenames from other users on the same+system. Instead, existing access control mechanisms such as file mode+bits, POSIX ACLs, LSMs, or namespaces should be used for this purpose.++(For the reasoning behind this, understand that while the key is+added, the confidentiality of the data, from the perspective of the+system itself, is *not* protected by the mathematical properties of+encryption but rather only by the correctness of the kernel.+Therefore, any encryption-specific access control checks would merely+be enforced by kernel *code* and therefore would be largely redundant+with the wide variety of access control mechanisms already available.)++Kernel compromise+~~~~~~~~~~~~~~~~~++An attacker who compromises the system enough to read from arbitrary+memory, e.g. by mounting a physical attack or by exploiting a kernel+security vulnerability, can compromise all encryption keys that are+currently in use.++However, fscrypt does allow an encryption key to be removed from the+kernel, which may protect it from later compromise.++In more detail, the FS_IOC_REMOVE_ENCRYPTION_KEY ioctl will wipe a+master encryption key from kernel memory. Moreover, it will try to+evict all cached inodes which had been "unlocked" using the key,+thereby wiping their derived encryption keys and making them once+again appear "locked", i.e. in ciphertext or encrypted form.++However, FS_IOC_REMOVE_ENCRYPTION_KEY has some limitations:++- Derived keys for in-use files will *not* be removed or wiped.+ Therefore, for maximum effect, userspace should close the relevant+ encrypted files and directories before removing a master key, as+ well as kill any processes whose working directory is in an affected+ encrypted directory.++- The kernel cannot magically wipe copies of the master key(s) that+ userspace might have as well. Therefore, userspace must wipe all+ copies of the master key(s) it makes as well. Naturally, the same+ also applies to all higher levels in the key hierarchy, e.g. to all+ key(s) that are used to wrap or derive the fscrypt master keys.+ Userspace should also follow other security precautions such as+ mlock()ing memory containing keys to prevent it from being swapped+ out.++- In general, decrypted contents and filenames in the kernel VFS+ caches are freed but not wiped. Therefore, portions thereof may be+ recoverable from freed memory, even after the corresponding key(s)+ were wiped. To partially solve this, you may enable page poisoning+ by enabling CONFIG_PAGE_POISONING in your kernel config and adding+ page_poison=1 to your kernel command line. However, that has a+ performance cost.++- Secret keys might still exist in CPU registers, in crypto+ accelerator hardware (if used by the crypto API to provide any of+ the algorithms), or in other places not explicitly considered here.++Limitations of v1 policies+~~~~~~~~~~~~~~~~~~~~~~~~~~++The original encryption policy version (which we call "v1") had some+weaknesses with respect to online attacks:++- There was no verification that the provided master key was correct.+ Therefore, malicious users could associate the wrong key with+ encrypted files, even files to which they had only read-only access.++- A compromise of any file's derived encryption key also compromised+ the master key it was derived from.++- Non-root users could not securely remove encryption keys.++All the above problems are fixed with v2 encryption policies+(:c:type:`fscrypt_policy_v2`). For this reason, it's recommended to+use v2 encryption policies for all new encrypted directories. Key hierarchy =============
@@ -167,19 +227,27 @@ master keys or to support rotating master keys. Instead, the master keys may be wrapped in userspace, e.g. as done by the `fscrypt<https://github.com/google/fscrypt>`_ tool.-The current KDF encrypts the master key using the 16-byte nonce as an-AES-128-ECB key. The output is used as the derived key. If the-output is longer than needed, then it is truncated to the needed-length. Truncation is the norm for directories and symlinks, since-those use the CTS-CBC encryption mode which requires a key half as-long as that required by the XTS encryption mode.--Note: this KDF meets the primary security requirement, which is to-produce unique derived keys that preserve the entropy of the master-key, assuming that the master key is already a good pseudorandom key.-However, it is nonstandard and has some problems such as being-reversible, so it is generally considered to be a mistake! It may be-replaced with HKDF or another more standard KDF in the future.+A different KDF is used depending on the encryption policy version:++For v1 encryption policies, the KDF is somewhat ad-hoc: we encrypt the+master key with AES-128-ECB using the file's 16-byte nonce as the AES+key, and the resulting ciphertext is used as the derived key. If the+master key is longer than the derived key, then only the needed prefix+of the ciphertext is used. Truncation is the norm for directories and+symlinks, since those use the CTS-CBC encryption mode which requires a+key half as long as that required by the XTS encryption mode.++For v2 encryption policies, the KDF is HKDF-SHA512. HKDF is preferred+to the AES-based KDF because HKDF is standardized and has a number of+desirable properties such as being nonreversible and evenly+distributing the entropy from the master key. To derive a file's+encryption key using HKDF, the master key is used as the "input key+material", a fixed value is used as the "salt", and the file's 16-byte+nonce prefixed with a context byte is used as the+"application-specific information string". (A fixed salt is used+because there is no random salt available on a per-master-key basis,+and the master keys should already be good pseudorandom keys that are+long enough to make dictionary attacks infeasible.) Encryption modes and usage ==========================
@@ -249,21 +317,38 @@ Setting an encryption policy The FS_IOC_SET_ENCRYPTION_POLICY ioctl sets an encryption policy on an empty directory or verifies that a directory or regular file already has the specified encryption policy. It takes in a pointer to a-:c:type:`struct fscrypt_policy`, defined as follows::-- #define FSCRYPT_KEY_DESCRIPTOR_SIZE 8+:c:type:`struct fscrypt_policy_v1` or a :c:type:`struct+fscrypt_policy_v2`, defined as follows::- struct fscrypt_policy {+ #define FSCRYPT_POLICY_VERSION_LEGACY 0+ #define FSCRYPT_KEY_DESCRIPTOR_SIZE 8+ struct fscrypt_policy_v1 { __u8 version; __u8 contents_encryption_mode; __u8 filenames_encryption_mode; __u8 flags; __u8 master_key_descriptor[FSCRYPT_KEY_DESCRIPTOR_SIZE]; };+ #define fscrypt_policy fscrypt_policy_v1++ #define FSCRYPT_POLICY_VERSION_2 2+ #define FSCRYPT_KEY_IDENTIFIER_SIZE 16+ struct fscrypt_policy_v2 {+ __u8 version;+ __u8 contents_encryption_mode;+ __u8 filenames_encryption_mode;+ __u8 flags;+ __u8 reserved[4];+ __u8 master_key_identifier[FSCRYPT_KEY_IDENTIFIER_SIZE];+ }; This structure must be initialized as follows:--``version`` must be 0.+-``version`` must be FSCRYPT_POLICY_VERSION_LEGACY (0) if the struct+ is :c:type:`fscrypt_policy_v1` or FSCRYPT_POLICY_VERSION_2 (2) if+ the struct is :c:type:`fscrypt_policy_v2`. (Note: we refer to the+ legacy policy version as "v1", though its version number was really+ 0.) For new encrypted directories, use v2 policies.-``contents_encryption_mode`` and ``filenames_encryption_mode`` must be set to constants from ``<linux/fs.h>`` which identify the
@@ -276,15 +361,25 @@ This structure must be initialized as follows: identifies the amount of NUL-padding to use when encrypting filenames. If unsure, use FSCRYPT_POLICY_FLAGS_PAD_32 (0x3).--``master_key_descriptor`` specifies how to find the master key in- the keyring; see `Adding keys`_. It is up to userspace to choose a- unique ``master_key_descriptor`` for each master key. The e4crypt- and fscrypt tools use the first 8 bytes of+- For v2 encryption policies, ``reserved`` must be zeroed.++- For v1 encryption policies, ``master_key_descriptor`` specifies how+ to find the master key in a keyring; see `Adding keys`_. It is up+ to userspace to choose a unique ``master_key_descriptor`` for each+ master key. The e4crypt and fscrypt tools use the first 8 bytes of``SHA-512(SHA-512(master_key))``, but this particular scheme is not required. Also, the master key need not be in the keyring yet when FS_IOC_SET_ENCRYPTION_POLICY is executed. However, it must be added before any files can be created in the encrypted directory.+ For v2 encryption policies, ``master_key_descriptor`` has been+ replaced with ``master_key_identifier``, which is longer and cannot+ be arbitrarily chosen. Instead, the key must first be added using+ FS_IOC_ADD_ENCRYPTION_KEY, as described in `Adding keys`_. Then,+ the ``key_spec.identifier`` the kernel returned in the+:c:type:`struct fscrypt_add_key_args` must be used as the+``master_key_identifier`` in the ``struct fscrypt_policy_v2``.+ If the file is not yet encrypted, then FS_IOC_SET_ENCRYPTION_POLICY verifies that the file is an empty directory. If so, the specified encryption policy is assigned to the directory, turning it into an
@@ -300,6 +395,15 @@ policy exactly matches the actual one. If they match, then the ioctl returns 0. Otherwise, it fails with EEXIST. This works on both regular files and directories, including nonempty directories.+When a v2 encryption policy is assigned to a directory, it is also+required that either the specified key has been added by the current+user, or the caller has CAP_FOWNER in the initial user namespace.+(This is needed to prevent a user from encrypting their data with+another user's key.) The key must remain added while+FS_IOC_SET_ENCRYPTION_POLICY is executing. However, if the new+encrypted directory does not need to be accessed immediately, then the+key can be removed right away afterwards.+ Note that the ext4 filesystem does not allow the root directory to be encrypted, even if it is empty. Users who want to encrypt an entire filesystem with one key should consider using dm-crypt instead.
@@ -312,7 +416,9 @@ FS_IOC_SET_ENCRYPTION_POLICY can fail with the following errors:-``EEXIST``: the file is already encrypted with an encryption policy different from the one specified-``EINVAL``: an invalid encryption policy was specified (invalid- version, mode(s), or flags)+ version, mode(s), or flags; or reserved bits were set)+-``ENOKEY``: a v2 encryption policy was specified, but the key with+ the specified ``master_key_identifier`` has not been added-``ENOTDIR``: the file is unencrypted and is a regular file, not a directory-``ENOTEMPTY``: the file is unencrypted and is a nonempty directory
@@ -331,25 +437,82 @@ FS_IOC_SET_ENCRYPTION_POLICY can fail with the following errors: Getting an encryption policy -----------------------------The FS_IOC_GET_ENCRYPTION_POLICY ioctl retrieves the :c:type:`struct-fscrypt_policy`, if any, for a directory or regular file. See above-for the struct definition. No additional permissions are required-beyond the ability to open the file.+Two ioctls are available to get a file's encryption policy:++- FS_IOC_GET_ENCRYPTION_POLICY_EX+- FS_IOC_GET_ENCRYPTION_POLICY++The extended (_EX) version of the ioctl is more general and is+recommended to use when possible. However, on older kernels only the+original ioctl is available. Applications should try the extended+version, and if it fails with ENOTTY fall back to the original+version.++Preferred method+~~~~~~~~~~~~~~~~++The FS_IOC_GET_ENCRYPTION_POLICY_EX ioctl retrieves the encryption+policy, if any, for a directory or regular file. No additional+permissions are required beyond the ability to open the file. It+takes in a pointer to a buffer formatted as a :c:type:`struct+fscrypt_get_policy_ex_args`, defined as follows::++ struct fscrypt_get_policy_ex_args {+ __u64 size;+ union {+ __u8 version;+ struct fscrypt_policy_v1 v1;+ struct fscrypt_policy_v2 v2;+ } policy;+ };++The caller must initialize ``size`` to the size of the buffer in+bytes, including both the ``size`` field and the space available for+the policy struct. It is recommended to use ``sizeof(struct+fscrypt_get_policy_ex_args)``.++On successful return, ``size`` is set to the actual number of bytes+returned, including both the ``size`` field and the actual size of the+returned policy struct. In addition, the ``version`` field should be+used to determine the actual policy version returned. Note that the+version code for the "v1" policy is actually 0+(FSCRYPT_POLICY_VERSION_LEGACY).-FS_IOC_GET_ENCRYPTION_POLICY can fail with the following errors:+FS_IOC_GET_ENCRYPTION_POLICY_EX can fail with the following errors:-``EINVAL``: the file is encrypted, but it uses an unrecognized- encryption context format+ encryption policy version; or, an invalid ``size`` was provided-``ENODATA``: the file is not encrypted--``ENOTTY``: this type of filesystem does not implement encryption+-``ENOTTY``: this type of filesystem does not implement encryption,+ or this kernel is too old to support FS_IOC_GET_ENCRYPTION_POLICY_EX+ (try FS_IOC_GET_ENCRYPTION_POLICY instead)-``EOPNOTSUPP``: the kernel was not configured with encryption support for this filesystem+-``EOVERFLOW``: the file is encrypted and uses a recognized+ encryption policy version, but the policy struct does not fit into+ the provided buffer Note: if you only need to know whether a file is encrypted or not, on most filesystems it is also possible to use the FS_IOC_GETFLAGS ioctl and check for FS_ENCRYPT_FL, or to use the statx() system call and check for STATX_ATTR_ENCRYPTED in stx_attributes.+Legacy method+~~~~~~~~~~~~~++The FS_IOC_GET_ENCRYPTION_POLICY ioctl can also retrieve the+encryption policy, if any, for a directory or regular file. However,+unlike the extended version (FS_IOC_GET_ENCRYPTION_POLICY_EX),+FS_IOC_GET_ENCRYPTION_POLICY only supports the original policy+version. It takes in a pointer directly to a :c:type:`struct+fscrypt_policy_v1` rather than a :c:type:`struct+fscrypt_get_policy_ex_args`.++The error codes for FS_IOC_GET_ENCRYPTION_POLICY are the same as those+for FS_IOC_GET_ENCRYPTION_POLICY_EX, except that+FS_IOC_GET_ENCRYPTION_POLICY also returns ``EINVAL`` if the file is+encrypted using a newer encryption policy version.+ Getting the per-filesystem salt -------------------------------
@@ -365,8 +528,97 @@ generate and manage any needed salt(s) in userspace. Adding keys ------------To provide a master key, userspace must add it to an appropriate-keyring using the add_key() system call (see:+Preferred method+~~~~~~~~~~~~~~~~++The FS_IOC_ADD_ENCRYPTION_KEY ioctl adds a master encryption key to+the filesystem, making all files on the filesystem which were+encrypted using that key appear "unlocked", i.e. in plaintext form.+It takes in a pointer to a :c:type:`struct fscrypt_add_key_args`,+defined as follows::++ struct fscrypt_add_key_args {+ __u32 raw_size;+ __u32 reserved1;+ __u64 reserved2[2];+ struct fscrypt_key_specifier key_spec;+ __u8 raw[];+ };++ struct fscrypt_key_specifier {+ __u32 type;+ #define FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR 1+ #define FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER 2+ __u32 reserved;+ union {+ __u8 max_specifier[32];+ __u8 descriptor[FSCRYPT_KEY_DESCRIPTOR_SIZE];+ __u8 identifier[FSCRYPT_KEY_IDENTIFIER_SIZE];+ };+ };++:c:type:`struct fscrypt_add_key_args` must be initialized as follows:++-``raw_size`` must be the size of the ``raw`` key provided, in bytes.++- If the key is being added for use by v1 encryption policies, then+``key_spec.type`` must contain FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR, and+``key_spec.descriptor`` must contain the descriptor of the key being+ added, corresponding to the value in the ``master_key_descriptor``+ field of :c:type:`struct fscrypt_policy_v1`. To add this type of+ key, the calling process must have the CAP_SYS_ADMIN capability in+ the initial user namespace.++ Alternatively, if the key is being added for use by v2 encryption+ policies, then ``key_spec.type`` must contain+ FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER, and ``key_spec.identifier`` is an+*output* field which the kernel fills in with a cryptographic hash+ of the key. To add this type of key, the calling process does not+ need any privileges. However, the number of keys that can be added+ is limited by the user's quota for the keyrings service (see+``Documentation/security/keys/core.rst``).++-``raw`` is a variable-length field which must contain the actual+ key, ``raw_size`` bytes long.++- All reserved fields must be zeroed.++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+-``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+-``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+ had encryption enabled on it++Legacy method+~~~~~~~~~~~~~++For v1 encryption policies, a master encryption key can also be+provided by adding it to a process-subscribed keyring, e.g. to a+session keyring, or to a user keyring if the user keyring is linked+into the session keyring.++This method is deprecated (and not supported for v2 encryption+policies) for several reasons. First, it cannot be used in+combination with FS_IOC_REMOVE_ENCRYPTION_KEY (see `Removing keys`_),+so for removing a key a workaround such as keyctl_unlink() in+combination with ``sync; echo 2 > /proc/sys/vm/drop_caches`` would+have to be used. Second, it doesn't match the fact that the+locked/unlocked status of encrypted files (i.e. whether they appear to+be in plaintext form or in ciphertext form) is global. This mismatch+has caused much confusion as well as real problems when processes+running under different UIDs, such as a ``sudo`` command, need to+access encrypted files.++Nevertheless, to add a key to one of the process-subscribed keyrings,+the add_key() system call can be used (see:``Documentation/security/keys/core.rst``). The key type must be "logon"; keys of this type are kept in kernel memory and cannot be read back by userspace. The key description must be "fscrypt:"
@@ -391,26 +643,143 @@ with a filesystem-specific prefix such as "ext4:". However, the filesystem-specific prefixes are deprecated and should not be used in new programs.-There are several different types of keyrings in which encryption keys-may be placed, such as a session keyring, a user session keyring, or a-user keyring. Each key must be placed in a keyring that is "attached"-to all processes that might need to access files encrypted with it, in-the sense that request_key() will find the key. Generally, if only-processes belonging to a specific user need to access a given-encrypted directory and no session keyring has been installed, then-that directory's key should be placed in that user's user session-keyring or user keyring. Otherwise, a session keyring should be-installed if needed, and the key should be linked into that session-keyring, or in a keyring linked into that session keyring.--Note: introducing the complex visibility semantics of keyrings here-was arguably a mistake --- especially given that by design, after any-process successfully opens an encrypted file (thereby setting up the-per-file key), possessing the keyring key is not actually required for-any process to read/write the file until its in-memory inode is-evicted. In the future there probably should be a way to provide keys-directly to the filesystem instead, which would make the intended-semantics clearer.+Removing keys+-------------++The FS_IOC_REMOVE_ENCRYPTION_KEY ioctl can be used to remove a master+encryption key from the kernel, wiping the corresponding secrets from+memory and causing any files which were "unlocked" with the key to+appear "locked" again. It takes in a pointer to a :c:type:`struct+fscrypt_remove_key_args`, defined as follows::++ struct fscrypt_remove_key_args {+ __u32 flags;+ #define FSCRYPT_REMOVE_KEY_FLAG_ALL_USERS 0x00000001+ __u32 reserved1;+ __u64 reserved2[2];+ struct fscrypt_key_specifier key_spec;+ };++This structure must be initialized as follows:++-``flags`` can contain the following flags:++-``FSCRYPT_REMOVE_KEY_FLAG_ALL_USERS`` specifies that the key+ should be removed even if it has also been added by other users.+ Specifying this flag requires the CAP_SYS_ADMIN capability in+ the initial user namespace.++- The key to remove is specified by ``key_spec``:++- To remove a key used by v1 encryption policies, set+``key_spec.type`` to FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR and fill+ in ``key_spec.descriptor``. To remove this type of key, the+ calling process must have the CAP_SYS_ADMIN capability in the+ initial user namespace.++- To remove a key used by v2 encryption policies, set+``key_spec.type`` to FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER and fill+ in ``key_spec.identifier``. To remove this type of key, no+ privileges are needed. However, users can only remove keys that+ they added themselves, subject to privileged override with+ FSCRYPT_REMOVE_KEY_FLAG_ALL_USERS.++- All reserved fields must be zeroed.++FS_IOC_REMOVE_ENCRYPTION_KEY can fail with the following errors:++-``EACCES``: The FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR key specifier type+ and/or the FSCRYPT_REMOVE_KEY_FLAG_ALL_USERS flag was specified, but+ the caller does not have the CAP_SYS_ADMIN capability in the initial+ user namespace+-``EBUSY``: the master key secret was wiped from memory, but some+ files which were unlocked with it are still in use. Such files+ could not be locked, nor could their per-file keys be wiped from+ memory. The ioctl may be retried later to re-attempt locking the+ remaining files.+-``EINVAL``: invalid flags or key specifier type, or reserved bits+ were set+-``ENOKEY``: the key is not present or has already been removed+-``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+ had encryption enabled on it+-``EUSERS``: the key cannot be removed because other users have added+ it too++Before using this ioctl, please read the `Kernel compromise`_ section+for a discussion of the security goals and limitations of this ioctl.++Getting key status+------------------++The FS_IOC_GET_ENCRYPTION_KEY_STATUS ioctl retrieves the status of a+master encryption key. It takes in a pointer to a :c:type:`struct+fscrypt_get_key_status_args`, defined as follows::++ struct fscrypt_get_key_status_args {+ /* input */+ __u64 reserved1[3];+ struct fscrypt_key_specifier key_spec;++ /* output */+ __u32 status;+ #define FSCRYPT_KEY_STATUS_ABSENT 1+ #define FSCRYPT_KEY_STATUS_PRESENT 2+ #define FSCRYPT_KEY_STATUS_INCOMPLETELY_REMOVED 3+ __u32 status_flags;+ #define FSCRYPT_KEY_STATUS_FLAG_ADDED_BY_SELF 0x00000001+ __u32 user_count;+ __u32 reserved2;+ __u64 reserved3[6];+ };++The caller must zero ``reserved1``, then fill in ``key_spec``:++- To get the status of a key for v1 encryption policies, set+``key_spec.type`` to FSCRYPT_KEY_SPEC_TYPE_DESCRIPTOR and fill+ in ``key_spec.descriptor``.++- To get the status of a key for v2 encryption policies, set+``key_spec.type`` to FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER and fill+ in ``key_spec.identifier``.++On success, 0 is returned and the kernel fills in the output fields:++-``status`` indicates whether the key is absent, present, or+ incompletely removed. Incompletely removed means that the master+ secret has been removed, but some files are still in use; i.e.,+ FS_IOC_REMOVE_ENCRYPTION_KEY returned EBUSY.++-``status_flags`` can contain the following flags:++-``FSCRYPT_KEY_STATUS_FLAG_ADDED_BY_SELF`` indicates that the key+ has added by the current user. This is only set for keys+ identified by ``identifier`` rather than by ``descriptor``.++-``user_count`` specifies the number of users who have added the key.+ This is only set for keys identified by ``identifier`` rather than+ by ``descriptor``.++FS_IOC_GET_ENCRYPTION_KEY_STATUS can fail with the following errors:++-``EINVAL``: invalid key specifier type, or reserved bits were set+-``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+ had encryption enabled on it++Among other use cases, FS_IOC_GET_ENCRYPTION_KEY_STATUS might be+useful for determining whether the key for a given encrypted directory+needs to be added before prompting the user for the passphrase needed+to derive the key.++FS_IOC_GET_ENCRYPTION_KEY_STATUS can only get the status of keys in+the filesystem-level keyring, i.e. the keyring managed by+FS_IOC_ADD_ENCRYPTION_KEY and FS_IOC_REMOVE_ENCRYPTION_KEY. It cannot+get the status of a key that has only been added for use by v1+encryption policies using the legacy mechanism involving+process-subscribed keyrings. Access semantics ================
@@ -459,7 +828,7 @@ Without the key Some filesystem operations may be performed on encrypted regular files, directories, and symlinks even before their encryption key has-been provided:+been added, or after their encryption key has been removed:- File metadata may be read, e.g. using stat().
@@ -524,20 +893,20 @@ Encryption context ------------------ An encryption policy is represented on-disk by a :c:type:`struct-fscrypt_context`. It is up to individual filesystems to decide where-to store it, but normally it would be stored in a hidden extended-attribute. It should *not* be exposed by the xattr-related system-calls such as getxattr() and setxattr() because of the special-semantics of the encryption xattr. (In particular, there would be-much confusion if an encryption policy were to be added to or removed-from anything other than an empty directory.) The struct is defined-as follows::+fscrypt_context_v1` or a :c:type:`struct fscrypt_context_v2`. It is+up to individual filesystems to decide where to store it, but normally+it would be stored in a hidden extended attribute. It should *not* be+exposed by the xattr-related system calls such as getxattr() and+setxattr() because of the special semantics of the encryption xattr.+(In particular, there would be much confusion if an encryption policy+were to be added to or removed from anything other than an empty+directory.) These structs are defined as follows::- #define FSCRYPT_KEY_DESCRIPTOR_SIZE 8 #define FS_KEY_DERIVATION_NONCE_SIZE 16- struct fscrypt_context {- u8 format;+ #define FSCRYPT_KEY_DESCRIPTOR_SIZE 8+ struct fscrypt_context_v1 {+ u8 version; u8 contents_encryption_mode; u8 filenames_encryption_mode; u8 flags;
@@ -545,12 +914,22 @@ as follows:: u8 nonce[FS_KEY_DERIVATION_NONCE_SIZE]; };-Note that :c:type:`struct fscrypt_context` contains the same-information as :c:type:`struct fscrypt_policy` (see `Setting an-encryption policy`_), except that :c:type:`struct fscrypt_context`-also contains a nonce. The nonce is randomly generated by the kernel-and is used to derive the inode's encryption key as described in-`Per-file keys`_.+ #define FSCRYPT_KEY_IDENTIFIER_SIZE 16+ struct fscrypt_context_v2 {+ u8 version;+ u8 contents_encryption_mode;+ u8 filenames_encryption_mode;+ u8 flags;+ u8 reserved[4];+ u8 master_key_identifier[FSCRYPT_KEY_IDENTIFIER_SIZE];+ u8 nonce[FS_KEY_DERIVATION_NONCE_SIZE];+ };++Note that the context structs contain the same information as the+corresponding policy structs (see `Setting an encryption policy`_),+except that the context structs also contain a nonce. The nonce is+randomly generated by the kernel and is used to derive the inode's+encryption key as described in `Per-file keys`_. Data path changes -----------------
--
2.15.0.rc0.271.g36b669edcc-goog
------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
From: Michael Halcrow <hidden> Date: 2017-10-27 18:01:44
On Mon, Oct 23, 2017 at 02:40:34PM -0700, Eric Biggers wrote:
From: Eric Biggers <redacted>
There are going to be more filesystem encryption definitions added, and
we don't want to use a disproportionate amount of space in <linux/fs.h>
for filesystem encryption stuff. So move the fscrypt definitions to a
new header <linux/fscrypt.h>.
For compatibility with existing userspace programs which may be
including <linux/fs.h>, <linux/fs.h> still includes the new header.
(It's debatable whether we really need this, though; the filesystem
encryption API is new enough that most if not all programs that are
using it have to declare it themselves anyway.)
Signed-off-by: Eric Biggers <redacted>
From: Michael Halcrow <hidden> Date: 2017-10-27 18:03:00
On Mon, Oct 23, 2017 at 02:40:35PM -0700, Eric Biggers wrote:
From: Eric Biggers <redacted>
Prefix all filesystem encryption UAPI constants except the ioctl numbers
with "FSCRYPT_" rather than with "FS_". This namespaces the constants
more appropriately and makes it clear that they are related specifically
to the filesystem encryption feature, and to the 'fscrypt_*' structures.
With some of the old names like "FS_POLICY_FLAGS_VALID", it was not
immediately clear that the constant had anything to do with encryption.
This is also useful because we'll be adding more encryption-related
constants, e.g. for the policy version, and we'd otherwise have to
choose whether to use unclear names like FS_POLICY_VERSION_* or
inconsistent names like FS_ENCRYPTION_POLICY_VERSION_*.
For source compatibility with older userspace programs, keep the old
names defined as aliases to the new ones. (It's debatable whether we
really need this, though; the filesystem encryption API is new enough
that most if not all programs that are using it have to declare it
themselves anyway.)
Signed-off-by: Eric Biggers <redacted>
@@ -251,14 +251,14 @@ empty directory or verifies that a directory or regular file already has the specified encryption policy. It takes in a pointer to a:c:type:`struct fscrypt_policy`, defined as follows::- #define FS_KEY_DESCRIPTOR_SIZE 8+ #define FSCRYPT_KEY_DESCRIPTOR_SIZE 8 struct fscrypt_policy { __u8 version; __u8 contents_encryption_mode; __u8 filenames_encryption_mode; __u8 flags;- __u8 master_key_descriptor[FS_KEY_DESCRIPTOR_SIZE];+ __u8 master_key_descriptor[FSCRYPT_KEY_DESCRIPTOR_SIZE]; }; This structure must be initialized as follows:
@@ -274,7 +274,7 @@ This structure must be initialized as follows:-``flags`` must be set to a value from ``<linux/fs.h>`` which identifies the amount of NUL-padding to use when encrypting- filenames. If unsure, use FS_POLICY_FLAGS_PAD_32 (0x3).+ filenames. If unsure, use FSCRYPT_POLICY_FLAGS_PAD_32 (0x3).-``master_key_descriptor`` specifies how to find the master key in the keyring; see `Adding keys`_. It is up to userspace to choose a
@@ -374,11 +374,11 @@ followed by the 16-character lower case hex representation of the``master_key_descriptor`` that was set in the encryption policy. The key payload must conform to the following structure::- #define FS_MAX_KEY_SIZE 64+ #define FSCRYPT_MAX_KEY_SIZE 64 struct fscrypt_key { u32 mode;- u8 raw[FS_MAX_KEY_SIZE];+ u8 raw[FSCRYPT_MAX_KEY_SIZE]; u32 size; };
@@ -533,7 +533,7 @@ much confusion if an encryption policy were to be added to or removed from anything other than an empty directory.) The struct is defined as follows::- #define FS_KEY_DESCRIPTOR_SIZE 8+ #define FSCRYPT_KEY_DESCRIPTOR_SIZE 8 #define FS_KEY_DERIVATION_NONCE_SIZE 16 struct fscrypt_context {
@@ -38,16 +39,36 @@ struct fscrypt_policy {#define FS_IOC_GET_ENCRYPTION_POLICY _IOW('f', 21, struct fscrypt_policy)/* Parameters for passing an encryption key into the kernel keyring */-#define FS_KEY_DESC_PREFIX "fscrypt:"-#define FS_KEY_DESC_PREFIX_SIZE 8+#define FSCRYPT_KEY_DESC_PREFIX "fscrypt:"+#define FSCRYPT_KEY_DESC_PREFIX_SIZE 8/* Structure that userspace passes to the kernel keyring */-#define FS_MAX_KEY_SIZE 64+#define FSCRYPT_MAX_KEY_SIZE 64structfscrypt_key{__u32mode;-__u8raw[FS_MAX_KEY_SIZE];+__u8raw[FSCRYPT_MAX_KEY_SIZE];__u32size;};+/**********************************************************************/++/* old names; don't add anything new here! */+#define FS_POLICY_FLAGS_PAD_4 FSCRYPT_POLICY_FLAGS_PAD_4+#define FS_POLICY_FLAGS_PAD_8 FSCRYPT_POLICY_FLAGS_PAD_8+#define FS_POLICY_FLAGS_PAD_16 FSCRYPT_POLICY_FLAGS_PAD_16+#define FS_POLICY_FLAGS_PAD_32 FSCRYPT_POLICY_FLAGS_PAD_32+#define FS_POLICY_FLAGS_PAD_MASK FSCRYPT_POLICY_FLAGS_PAD_MASK+#define FS_POLICY_FLAGS_VALID FSCRYPT_POLICY_FLAGS_VALID+#define FS_KEY_DESCRIPTOR_SIZE FSCRYPT_KEY_DESCRIPTOR_SIZE+#define FS_ENCRYPTION_MODE_INVALID FSCRYPT_MODE_INVALID+#define FS_ENCRYPTION_MODE_AES_256_XTS FSCRYPT_MODE_AES_256_XTS+#define FS_ENCRYPTION_MODE_AES_256_GCM FSCRYPT_MODE_AES_256_GCM+#define FS_ENCRYPTION_MODE_AES_256_CBC FSCRYPT_MODE_AES_256_CBC+#define FS_ENCRYPTION_MODE_AES_256_CTS FSCRYPT_MODE_AES_256_CTS+#define FS_ENCRYPTION_MODE_AES_128_CBC FSCRYPT_MODE_AES_128_CBC+#define FS_ENCRYPTION_MODE_AES_128_CTS FSCRYPT_MODE_AES_128_CTS+#define FS_KEY_DESC_PREFIX FSCRYPT_KEY_DESC_PREFIX+#define FS_KEY_DESC_PREFIX_SIZE FSCRYPT_KEY_DESC_PREFIX_SIZE+#define FS_MAX_KEY_SIZE FSCRYPT_MAX_KEY_SIZE#endif /* _UAPI_LINUX_FSCRYPT_H */
--
2.15.0.rc0.271.g36b669edcc-goog
------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
From: Michael Halcrow <hidden> Date: 2017-10-27 18:06:40
On Mon, Oct 23, 2017 at 02:40:36PM -0700, Eric Biggers wrote:
From: Eric Biggers <redacted>
Update the filesystem encryption kernel code to use the new names for
the UAPI constants rather than the old names.
Signed-off-by: Eric Biggers <redacted>
From: Michael Halcrow <hidden> Date: 2017-10-27 18:23:06
On Mon, Oct 23, 2017 at 02:40:37PM -0700, Eric Biggers wrote:
From: Eric Biggers <redacted>
In preparation for introducing a new way to find the master keys and
derive the per-file keys, clean up the current method. This includes:
- Introduce a helper function find_and_derive_key() so that we don't
have to add more code directly to fscrypt_get_encryption_info().
- Don't pass the 'struct fscrypt_key' directly into derive_key_aes().
This is in preparation for the case where we find the master key in a
filesystem-level keyring, where (for good reasons) the key payload
will *not* be formatted as the UAPI 'struct fscrypt_key'.
- Separate finding the key from key derivation. In particular, it
*only* makes sense to fall back to the alternate key description
prefix if searching for the "fscrypt:" prefix returns -ENOKEY. It
doesn't make sense to do so when derive_key_aes() fails, for example.
- Improve the error messages for when the fscrypt_key is invalid.
- Rename 'raw_key' to 'derived_key' for clarity.
With all the crypto code I've delt with where a 'key' is actually just
a handle or a reference, I've developed a personal habit is to call
the buffer that contains the actual key bytes 'raw' to emphasize that
there's tangible secret material present.
That said, it's probably fine to call it 'derived_key' in this
instance.
Since we've already crossed the threshold of returning at least one
object via a ptr-to-ptr param, maybe doing that for struct key too and
just making the return value of the function be an int err would be
cleaner than PTR_ERR/ERR_PTR?
{
char *description;
- struct key *keyring_key;
- struct fscrypt_key *master_key;
+ struct key *key;
const struct user_key_payload *ukp;
- int res;
+ const struct fscrypt_key *payload;
description = kasprintf(GFP_NOFS, "%s%*phN", prefix,
- FSCRYPT_KEY_DESCRIPTOR_SIZE,
- ctx->master_key_descriptor);
+ FSCRYPT_KEY_DESCRIPTOR_SIZE, descriptor);
if (!description)
- return -ENOMEM;
+ return ERR_PTR(-ENOMEM);
- keyring_key = request_key(&key_type_logon, description, NULL);
+ key = request_key(&key_type_logon, description, NULL);
kfree(description);
- if (IS_ERR(keyring_key))
- return PTR_ERR(keyring_key);
- down_read(&keyring_key->sem);
-
- if (keyring_key->type != &key_type_logon) {
- printk_once(KERN_WARNING
- "%s: key type must be logon\n", __func__);
- res = -ENOKEY;
- goto out;
- }
- ukp = user_key_payload_locked(keyring_key);
- if (!ukp) {
- /* key was revoked before we acquired its semaphore */
- res = -EKEYREVOKED;
- goto out;
+ if (IS_ERR(key))
+ return key;
+
+ down_read(&key->sem);
+ ukp = user_key_payload_locked(key);
+
+ if (!ukp) /* was the key revoked before we acquired its semaphore? */
+ goto invalid;
+
+ payload = (const struct fscrypt_key *)ukp->data;
+
+ if (ukp->datalen != sizeof(struct fscrypt_key) ||
+ payload->size < 1 || payload->size > FSCRYPT_MAX_KEY_SIZE) {
+ pr_warn_ratelimited("fscrypt: key with description '%s' has invalid payload\n",
+ key->description);
+ goto invalid;
}
- if (ukp->datalen != sizeof(struct fscrypt_key)) {
- res = -EINVAL;
- goto out;
+
+ if (payload->size < min_keysize) {
+ pr_warn_ratelimited("fscrypt: key with description '%s' is too short "
+ "(got %u bytes, need %u+ bytes)\n",
+ key->description,
+ payload->size, min_keysize);
+ goto invalid;
A common (yet high-impact) mistake is to pass in only 256 bits of
entropic key material in the 512-bit buffer for AES-256-XTS, leaving
the second half all 0's. I've actually seen that done in pre-release
code. It would be an easy check just to see if userspace did that,
but then we're on the slippery slope of how much key strength
validation we should do on the key, if we're going to do any at all.
@@ -256,8 +281,8 @@ int fscrypt_get_encryption_info(struct inode *inode) struct fscrypt_context ctx; struct crypto_skcipher *ctfm; const char *cipher_str;- int keysize;- u8 *raw_key = NULL;+ unsigned int derived_keysize;+ u8 *derived_key = NULL; int res; if (inode->i_crypt_info)
@@ -301,7 +326,8 @@ int fscrypt_get_encryption_info(struct inode *inode) memcpy(crypt_info->ci_master_key, ctx.master_key_descriptor, sizeof(crypt_info->ci_master_key));- res = determine_cipher_type(crypt_info, inode, &cipher_str, &keysize);+ res = determine_cipher_type(crypt_info, inode,+ &cipher_str, &derived_keysize); if (res) goto out;
@@ -310,24 +336,14 @@ int fscrypt_get_encryption_info(struct inode *inode) * crypto API as part of key derivation. */ res = -ENOMEM;- raw_key = kmalloc(FSCRYPT_MAX_KEY_SIZE, GFP_NOFS);- if (!raw_key)+ derived_key = kmalloc(FS_MAX_KEY_SIZE, GFP_NOFS);+ if (!derived_key) goto out;- res = validate_user_key(crypt_info, &ctx, raw_key,- FSCRYPT_KEY_DESC_PREFIX, keysize);- if (res && inode->i_sb->s_cop->key_prefix) {- int res2 = validate_user_key(crypt_info, &ctx, raw_key,- inode->i_sb->s_cop->key_prefix,- keysize);- if (res2) {- if (res2 == -ENOKEY)- res = -ENOKEY;- goto out;- }- } else if (res) {+ res = find_and_derive_key(inode, &ctx, derived_key, derived_keysize);+ if (res) goto out;- }+ ctfm = crypto_alloc_skcipher(cipher_str, 0, 0); if (!ctfm || IS_ERR(ctfm)) { res = ctfm ? PTR_ERR(ctfm) : -ENOMEM;
@@ -338,17 +354,14 @@ int fscrypt_get_encryption_info(struct inode *inode) crypt_info->ci_ctfm = ctfm; crypto_skcipher_clear_flags(ctfm, ~0); crypto_skcipher_set_flags(ctfm, CRYPTO_TFM_REQ_WEAK_KEY);- /*- * if the provided key is longer than keysize, we use the first- * keysize bytes of the derived key only- */- res = crypto_skcipher_setkey(ctfm, raw_key, keysize);+ res = crypto_skcipher_setkey(ctfm, derived_key, derived_keysize); if (res) goto out; if (S_ISREG(inode->i_mode) && crypt_info->ci_data_mode == FSCRYPT_MODE_AES_128_CBC) {- res = init_essiv_generator(crypt_info, raw_key, keysize);+ res = init_essiv_generator(crypt_info, derived_key,+ derived_keysize); if (res) { pr_debug("%s: error %d (inode %lu) allocating essiv tfm\n", __func__, res, inode->i_ino);
@@ -361,7 +374,7 @@ int fscrypt_get_encryption_info(struct inode *inode) if (res == -ENOKEY) res = 0; put_crypt_info(crypt_info);- kzfree(raw_key);+ kzfree(derived_key); return res; } EXPORT_SYMBOL(fscrypt_get_encryption_info);
From: Michael Halcrow <hidden> Date: 2017-10-27 18:26:36
On Mon, Oct 23, 2017 at 02:40:38PM -0700, Eric Biggers wrote:
From: Eric Biggers <redacted>
Add an ->s_master_keys keyring to 'struct super_block' for holding
encryption keys which have been added to the filesystem. This keyring
will be populated using a new fscrypt ioctl.
This is needed for several reasons, including:
- To solve the visibility problems of having filesystem encryption keys
stored in process-subscribed keyrings, while the VFS state of the
filesystem is actually global.
- To implement a proper API for removing keys, which among other things
will require maintaining the list of inodes that are using each master
key so that we can evict the inodes when the key is removed.
- To allow caching a crypto transform for each master key so that we
don't have to repeatedly allocate one over and over.
See later patches for full details, including why it wouldn't be enough
to add the concept of a "global keyring" to the keyrings API instead.
->s_master_keys will only be allocated when someone tries to add a key
for the first time. Otherwise it will stay NULL.
Note that this could go in the filesystem-specific superblocks instead.
However, we already have three filesystems using fs/crypto/, so it's
useful to have it in the VFS.
Signed-off-by: Eric Biggers <redacted>
@@ -1440,6 +1440,10 @@ struct super_block {spinlock_ts_inode_wblist_lock;structlist_heads_inodes_wb;/* writeback inodes */++#if IS_ENABLED(CONFIG_FS_ENCRYPTION)+structkey*s_master_keys;/* master crypto keys in use */+#endif}__randomize_layout;/* Helper functions so that in most cases filesystems will
From: Michael Halcrow <hidden> Date: 2017-10-27 20:14:49
On Mon, Oct 23, 2017 at 02:40:39PM -0700, Eric Biggers wrote:
By having an API to add a key to the *filesystem* we'll be able to
eliminate all the above hacks and better express the intended semantics:
the "locked/unlocked" status of an encrypted directory is global. And
orthogonally to encryption, existing mechanisms such as file permissions
and LSMs can and should continue to be used for the purpose of *access
control*.
At some point I'd like to try to tackle the problem of making the
encryption policy somehow *reflect* the access control policy.
For now this change cleans up a real mess and makes things much more
manageable and predictable.
@@ -449,6 +450,8 @@ int fscrypt_initialize(unsigned int cop_flags)*/staticint__initfscrypt_init(void){+interr=-ENOMEM;+fscrypt_read_workqueue=alloc_workqueue("fscrypt_read_queue",WQ_HIGHPRI,0);if(!fscrypt_read_workqueue)
@@ -462,14 +465,20 @@ static int __init fscrypt_init(void)if(!fscrypt_info_cachep)gotofail_free_ctx;+err=register_key_type(&key_type_fscrypt_mk);+if(err)+gotofail_free_info;+return0;+fail_free_info:+kmem_cache_destroy(fscrypt_info_cachep);fail_free_ctx:kmem_cache_destroy(fscrypt_ctx_cachep);fail_free_queue:destroy_workqueue(fscrypt_read_workqueue);fail:-return-ENOMEM;+returnerr;}module_init(fscrypt_init)
@@ -9,14 +9,307 @@*/#include<keys/user-type.h>-#include<linux/scatterlist.h>+#include<linux/key-type.h>#include<linux/ratelimit.h>+#include<linux/scatterlist.h>+#include<linux/seq_file.h>#include<crypto/aes.h>#include<crypto/sha.h>#include"fscrypt_private.h"staticstructcrypto_shash*essiv_hash_tfm;+/*+*fscrypt_master_key_secret-secretkeymaterialofanin-usemasterkey+*/+structfscrypt_master_key_secret{++/* Size of the raw key in bytes */+u32size;++/* The raw key */+u8raw[FSCRYPT_MAX_KEY_SIZE];+};
With structs fscrypt introduces, I suggest __randomize_layout wherever
feasible.
What function do the " - 1" and " + 1" parts serve here? Readability?
+ key = find_master_key(inode->i_sb, &mk_spec);
+ if (IS_ERR(key)) {
+ if (key != ERR_PTR(-ENOKEY))
+ return PTR_ERR(key);
+ /*
+ * As a legacy fallback, we search the current task's subscribed
+ * keyrings in addition to ->s_master_keys.
Please add an explicit comment that it's important for security that
the ordering of these two searches be preserved.
+ */
+ return find_and_derive_key_legacy(inode, ctx, derived_key,
+ derived_keysize);
+ }
+ mk = key->payload.data[0];
+
+ /*
+ * Require that the master key be at least as long as the derived key.
+ * Otherwise, the derived key cannot possibly contain as much entropy as
+ * that required by the encryption mode it will be used for.
+ */
+ if (mk->mk_secret.size < derived_keysize) {
As I've mentioned in a previous patch in this set, if we're going to
get opinionated about source entropy, there's more we could
measure/estimate than just the length.
From: Michael Halcrow <hidden> Date: 2017-10-27 20:28:31
On Mon, Oct 23, 2017 at 02:40:40PM -0700, Eric Biggers wrote:
From: Eric Biggers <redacted>
When a filesystem encryption key is removed, we need all files which had
been "unlocked" (had ->i_crypt_info set up) with it to appear "locked"
again. This is most easily done by evicting the inodes. This can
currently be done using 'echo 2 > /proc/sys/vm/drop_caches'; however,
that is overkill and not usable by non-root users. In preparation for
allowing fs/crypto/ to evict just the needed inodes, export
inode_lru_list_del() to modules.
Signed-off-by: Eric Biggers <redacted>