[SRU,Trusty,Artful,1/1] crypto: hmac - require that the underlying hash algorithm is unkeyed

Message ID 20180201160610.4262-2-kleber.souza@canonical.com
State New
Headers show
Series
  • Fix for CVE-2017-17806
Related show

Commit Message

Kleber Souza Feb. 1, 2018, 4:06 p.m.
From: Eric Biggers <ebiggers@google.com>

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>
Cc: <stable@vger.kernel.org>
Signed-off-by: Eric Biggers <ebiggers@google.com>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>

CVE-2017-17806
(cherry picked from commit af3ff8045bbf3e32f1a448542e73abb4c8ceb6f1)
Signed-off-by: Kleber Sacilotto de Souza <kleber.souza@canonical.com>
---
 crypto/hmac.c                  | 6 +++++-
 crypto/shash.c                 | 5 +++--
 include/crypto/internal/hash.h | 8 ++++++++
 3 files changed, 16 insertions(+), 3 deletions(-)

Comments

Stefan Bader Feb. 2, 2018, 10 a.m. | #1
On 01.02.2018 17:06, Kleber Sacilotto de Souza wrote:
> From: Eric Biggers <ebiggers@google.com>
> 
> 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>
> Cc: <stable@vger.kernel.org>
> Signed-off-by: Eric Biggers <ebiggers@google.com>
> Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
> 
> CVE-2017-17806
> (cherry picked from commit af3ff8045bbf3e32f1a448542e73abb4c8ceb6f1)
> Signed-off-by: Kleber Sacilotto de Souza <kleber.souza@canonical.com>
> ---
>  crypto/hmac.c                  | 6 +++++-
>  crypto/shash.c                 | 5 +++--
>  include/crypto/internal/hash.h | 8 ++++++++
>  3 files changed, 16 insertions(+), 3 deletions(-)
> 
> diff --git a/crypto/hmac.c b/crypto/hmac.c
> index ade790b454e9..758688b4bc6b 100644
> --- a/crypto/hmac.c
> +++ b/crypto/hmac.c
> @@ -197,11 +197,15 @@ static int hmac_create(struct crypto_template *tmpl, struct rtattr **tb)
>  	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;
> diff --git a/crypto/shash.c b/crypto/shash.c
> index ac4d76350d1b..956979787efb 100644
> --- 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)
> diff --git a/include/crypto/internal/hash.h b/include/crypto/internal/hash.h
> index 821eae8cbd8c..0af57a99c6a1 100644
> --- a/include/crypto/internal/hash.h
> +++ b/include/crypto/internal/hash.h
> @@ -70,6 +70,14 @@ int ahash_register_instance(struct crypto_template *tmpl,
>  			    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);
>

Patch

diff --git a/crypto/hmac.c b/crypto/hmac.c
index ade790b454e9..758688b4bc6b 100644
--- a/crypto/hmac.c
+++ b/crypto/hmac.c
@@ -197,11 +197,15 @@  static int hmac_create(struct crypto_template *tmpl, struct rtattr **tb)
 	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;
diff --git a/crypto/shash.c b/crypto/shash.c
index ac4d76350d1b..956979787efb 100644
--- 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)
diff --git a/include/crypto/internal/hash.h b/include/crypto/internal/hash.h
index 821eae8cbd8c..0af57a99c6a1 100644
--- a/include/crypto/internal/hash.h
+++ b/include/crypto/internal/hash.h
@@ -70,6 +70,14 @@  int ahash_register_instance(struct crypto_template *tmpl,
 			    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);