| From af3ff8045bbf3e32f1a448542e73abb4c8ceb6f1 Mon Sep 17 00:00:00 2001 |
| From: Eric Biggers <ebiggers@google.com> |
| Date: Tue, 28 Nov 2017 18:01:38 -0800 |
| Subject: crypto: hmac - require that the underlying hash algorithm is unkeyed |
| |
| From: Eric Biggers <ebiggers@google.com> |
| |
| commit af3ff8045bbf3e32f1a448542e73abb4c8ceb6f1 upstream. |
| |
| Because the HMAC template didn't check that its underlying hash |
| algorithm is unkeyed, trying to use "hmac(hmac(sha3-512-generic))" |
| through AF_ALG or through KEYCTL_DH_COMPUTE resulted in the inner HMAC |
| being used without having been keyed, resulting in sha3_update() being |
| called without sha3_init(), causing a stack buffer overflow. |
| |
| This is a very old bug, but it seems to have only started causing real |
| problems when SHA-3 support was added (requires CONFIG_CRYPTO_SHA3) |
| because the innermost hash's state is ->import()ed from a zeroed buffer, |
| and it just so happens that other hash algorithms are fine with that, |
| but SHA-3 is not. However, there could be arch or hardware-dependent |
| hash algorithms also affected; I couldn't test everything. |
| |
| Fix the bug by introducing a function crypto_shash_alg_has_setkey() |
| which tests whether a shash algorithm is keyed. Then update the HMAC |
| template to require that its underlying hash algorithm is unkeyed. |
| |
| Here is a reproducer: |
| |
| #include <linux/if_alg.h> |
| #include <sys/socket.h> |
| |
| int main() |
| { |
| int algfd; |
| struct sockaddr_alg addr = { |
| .salg_type = "hash", |
| .salg_name = "hmac(hmac(sha3-512-generic))", |
| }; |
| char key[4096] = { 0 }; |
| |
| algfd = socket(AF_ALG, SOCK_SEQPACKET, 0); |
| bind(algfd, (const struct sockaddr *)&addr, sizeof(addr)); |
| setsockopt(algfd, SOL_ALG, ALG_SET_KEY, key, sizeof(key)); |
| } |
| |
| Here was the KASAN report from syzbot: |
| |
| BUG: KASAN: stack-out-of-bounds in memcpy include/linux/string.h:341 [inline] |
| BUG: KASAN: stack-out-of-bounds in sha3_update+0xdf/0x2e0 crypto/sha3_generic.c:161 |
| Write of size 4096 at addr ffff8801cca07c40 by task syzkaller076574/3044 |
| |
| CPU: 1 PID: 3044 Comm: syzkaller076574 Not tainted 4.14.0-mm1+ #25 |
| Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 01/01/2011 |
| Call Trace: |
| __dump_stack lib/dump_stack.c:17 [inline] |
| dump_stack+0x194/0x257 lib/dump_stack.c:53 |
| print_address_description+0x73/0x250 mm/kasan/report.c:252 |
| kasan_report_error mm/kasan/report.c:351 [inline] |
| kasan_report+0x25b/0x340 mm/kasan/report.c:409 |
| check_memory_region_inline mm/kasan/kasan.c:260 [inline] |
| check_memory_region+0x137/0x190 mm/kasan/kasan.c:267 |
| memcpy+0x37/0x50 mm/kasan/kasan.c:303 |
| memcpy include/linux/string.h:341 [inline] |
| sha3_update+0xdf/0x2e0 crypto/sha3_generic.c:161 |
| crypto_shash_update+0xcb/0x220 crypto/shash.c:109 |
| shash_finup_unaligned+0x2a/0x60 crypto/shash.c:151 |
| crypto_shash_finup+0xc4/0x120 crypto/shash.c:165 |
| hmac_finup+0x182/0x330 crypto/hmac.c:152 |
| crypto_shash_finup+0xc4/0x120 crypto/shash.c:165 |
| shash_digest_unaligned+0x9e/0xd0 crypto/shash.c:172 |
| crypto_shash_digest+0xc4/0x120 crypto/shash.c:186 |
| hmac_setkey+0x36a/0x690 crypto/hmac.c:66 |
| crypto_shash_setkey+0xad/0x190 crypto/shash.c:64 |
| shash_async_setkey+0x47/0x60 crypto/shash.c:207 |
| crypto_ahash_setkey+0xaf/0x180 crypto/ahash.c:200 |
| hash_setkey+0x40/0x90 crypto/algif_hash.c:446 |
| alg_setkey crypto/af_alg.c:221 [inline] |
| alg_setsockopt+0x2a1/0x350 crypto/af_alg.c:254 |
| SYSC_setsockopt net/socket.c:1851 [inline] |
| SyS_setsockopt+0x189/0x360 net/socket.c:1830 |
| entry_SYSCALL_64_fastpath+0x1f/0x96 |
| |
| Reported-by: syzbot <syzkaller@googlegroups.com> |
| Signed-off-by: Eric Biggers <ebiggers@google.com> |
| Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| crypto/hmac.c | 6 +++++- |
| crypto/shash.c | 5 +++-- |
| include/crypto/internal/hash.h | 8 ++++++++ |
| 3 files changed, 16 insertions(+), 3 deletions(-) |
| |
| --- a/crypto/hmac.c |
| +++ b/crypto/hmac.c |
| @@ -194,11 +194,15 @@ static int hmac_create(struct crypto_tem |
| salg = shash_attr_alg(tb[1], 0, 0); |
| if (IS_ERR(salg)) |
| return PTR_ERR(salg); |
| + alg = &salg->base; |
| |
| + /* The underlying hash algorithm must be unkeyed */ |
| err = -EINVAL; |
| + if (crypto_shash_alg_has_setkey(salg)) |
| + goto out_put_alg; |
| + |
| ds = salg->digestsize; |
| ss = salg->statesize; |
| - alg = &salg->base; |
| if (ds > alg->cra_blocksize || |
| ss < alg->cra_blocksize) |
| goto out_put_alg; |
| --- a/crypto/shash.c |
| +++ b/crypto/shash.c |
| @@ -24,11 +24,12 @@ |
| |
| static const struct crypto_type crypto_shash_type; |
| |
| -static int shash_no_setkey(struct crypto_shash *tfm, const u8 *key, |
| - unsigned int keylen) |
| +int shash_no_setkey(struct crypto_shash *tfm, const u8 *key, |
| + unsigned int keylen) |
| { |
| return -ENOSYS; |
| } |
| +EXPORT_SYMBOL_GPL(shash_no_setkey); |
| |
| static int shash_setkey_unaligned(struct crypto_shash *tfm, const u8 *key, |
| unsigned int keylen) |
| --- a/include/crypto/internal/hash.h |
| +++ b/include/crypto/internal/hash.h |
| @@ -83,6 +83,14 @@ int ahash_register_instance(struct crypt |
| struct ahash_instance *inst); |
| void ahash_free_instance(struct crypto_instance *inst); |
| |
| +int shash_no_setkey(struct crypto_shash *tfm, const u8 *key, |
| + unsigned int keylen); |
| + |
| +static inline bool crypto_shash_alg_has_setkey(struct shash_alg *alg) |
| +{ |
| + return alg->setkey != shash_no_setkey; |
| +} |
| + |
| int crypto_init_ahash_spawn(struct crypto_ahash_spawn *spawn, |
| struct hash_alg_common *alg, |
| struct crypto_instance *inst); |