| From 2fc2b430f559fdf32d5d1dd5ceaa40e12fb77bdf Mon Sep 17 00:00:00 2001 |
| From: Eric Biggers <ebiggers@google.com> |
| Date: Sat, 5 Jun 2021 00:50:33 -0700 |
| Subject: fscrypt: fix derivation of SipHash keys on big endian CPUs |
| |
| From: Eric Biggers <ebiggers@google.com> |
| |
| commit 2fc2b430f559fdf32d5d1dd5ceaa40e12fb77bdf upstream. |
| |
| Typically, the cryptographic APIs that fscrypt uses take keys as byte |
| arrays, which avoids endianness issues. However, siphash_key_t is an |
| exception. It is defined as 'u64 key[2];', i.e. the 128-bit key is |
| expected to be given directly as two 64-bit words in CPU endianness. |
| |
| fscrypt_derive_dirhash_key() and fscrypt_setup_iv_ino_lblk_32_key() |
| forgot to take this into account. Therefore, the SipHash keys used to |
| index encrypted+casefolded directories differ on big endian vs. little |
| endian platforms, as do the SipHash keys used to hash inode numbers for |
| IV_INO_LBLK_32-encrypted directories. This makes such directories |
| non-portable between these platforms. |
| |
| Fix this by always using the little endian order. This is a breaking |
| change for big endian platforms, but this should be fine in practice |
| since these features (encrypt+casefold support, and the IV_INO_LBLK_32 |
| flag) aren't known to actually be used on any big endian platforms yet. |
| |
| Fixes: aa408f835d02 ("fscrypt: derive dirhash key for casefolded directories") |
| Fixes: e3b1078bedd3 ("fscrypt: add support for IV_INO_LBLK_32 policies") |
| Cc: <stable@vger.kernel.org> # v5.6+ |
| Link: https://lore.kernel.org/r/20210605075033.54424-1-ebiggers@kernel.org |
| Signed-off-by: Eric Biggers <ebiggers@google.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| fs/crypto/keysetup.c | 40 ++++++++++++++++++++++++++++++++-------- |
| 1 file changed, 32 insertions(+), 8 deletions(-) |
| |
| --- a/fs/crypto/keysetup.c |
| +++ b/fs/crypto/keysetup.c |
| @@ -210,15 +210,40 @@ out_unlock: |
| return err; |
| } |
| |
| +/* |
| + * Derive a SipHash key from the given fscrypt master key and the given |
| + * application-specific information string. |
| + * |
| + * Note that the KDF produces a byte array, but the SipHash APIs expect the key |
| + * as a pair of 64-bit words. Therefore, on big endian CPUs we have to do an |
| + * endianness swap in order to get the same results as on little endian CPUs. |
| + */ |
| +static int fscrypt_derive_siphash_key(const struct fscrypt_master_key *mk, |
| + u8 context, const u8 *info, |
| + unsigned int infolen, siphash_key_t *key) |
| +{ |
| + int err; |
| + |
| + err = fscrypt_hkdf_expand(&mk->mk_secret.hkdf, context, info, infolen, |
| + (u8 *)key, sizeof(*key)); |
| + if (err) |
| + return err; |
| + |
| + BUILD_BUG_ON(sizeof(*key) != 16); |
| + BUILD_BUG_ON(ARRAY_SIZE(key->key) != 2); |
| + le64_to_cpus(&key->key[0]); |
| + le64_to_cpus(&key->key[1]); |
| + return 0; |
| +} |
| + |
| int fscrypt_derive_dirhash_key(struct fscrypt_info *ci, |
| const struct fscrypt_master_key *mk) |
| { |
| int err; |
| |
| - err = fscrypt_hkdf_expand(&mk->mk_secret.hkdf, HKDF_CONTEXT_DIRHASH_KEY, |
| - ci->ci_nonce, FSCRYPT_FILE_NONCE_SIZE, |
| - (u8 *)&ci->ci_dirhash_key, |
| - sizeof(ci->ci_dirhash_key)); |
| + err = fscrypt_derive_siphash_key(mk, HKDF_CONTEXT_DIRHASH_KEY, |
| + ci->ci_nonce, FSCRYPT_FILE_NONCE_SIZE, |
| + &ci->ci_dirhash_key); |
| if (err) |
| return err; |
| ci->ci_dirhash_key_initialized = true; |
| @@ -253,10 +278,9 @@ static int fscrypt_setup_iv_ino_lblk_32_ |
| if (mk->mk_ino_hash_key_initialized) |
| goto unlock; |
| |
| - err = fscrypt_hkdf_expand(&mk->mk_secret.hkdf, |
| - HKDF_CONTEXT_INODE_HASH_KEY, NULL, 0, |
| - (u8 *)&mk->mk_ino_hash_key, |
| - sizeof(mk->mk_ino_hash_key)); |
| + err = fscrypt_derive_siphash_key(mk, |
| + HKDF_CONTEXT_INODE_HASH_KEY, |
| + NULL, 0, &mk->mk_ino_hash_key); |
| if (err) |
| goto unlock; |
| /* pairs with smp_load_acquire() above */ |