Vulnerability: The crypto_skcipher_init_tfm function in crypto/skcipher.c in the Linux kernel through 4.11.2 relies on a setkey function that lacks a key-size check, which allows local users to cause a denial of service (NULL pointer dereference) via a crafted application.
Vulnerability ID: CVE-2017-9211
CWE: CWE-476
Severity score: 6.9
Project: https://github.com/torvalds/linux
Published: 2017-05-23
File: crypto/skcipher.c
Commit message: crypto: skcipher - Add missing API setkey checks

The API setkey checks for key sizes and alignment went AWOL during the
skcipher conversion.  This patch restores them.

Cc: <stable@vger.kernel.org>
Fixes: 4e6c3df4d729 ("crypto: skcipher - Add low-level skcipher...")
Reported-by: Baozeng <sploving1@gmail.com>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
Commit author: Herbert Xu

Vulnerable version (before fix):
 static int crypto_init_skcipher_ops_ablkcipher(struct crypto_tfm *tfm) 	return 0; }  static void crypto_skcipher_exit_tfm(struct crypto_tfm *tfm) { 	struct crypto_skcipher *skcipher = __crypto_skcipher_cast(tfm); static int crypto_skcipher_init_tfm(struct crypto_tfm *tfm) 	    tfm->__crt_alg->cra_type == &crypto_givcipher_type) 		return crypto_init_skcipher_ops_ablkcipher(tfm);  	skcipher->setkey = alg->setkey; 	skcipher->encrypt = alg->encrypt; 	skcipher->decrypt = alg->decrypt; 	skcipher->ivsize = alg->ivsize;

Fixed version (after fix):
 static int crypto_init_skcipher_ops_ablkcipher(struct crypto_tfm *tfm) 	return 0; }  static int skcipher_setkey_unaligned(struct crypto_skcipher *tfm, 				     const u8 *key, unsigned int keylen) { 	unsigned long alignmask = crypto_skcipher_alignmask(tfm); 	struct skcipher_alg *cipher = crypto_skcipher_alg(tfm); 	u8 *buffer, *alignbuffer; 	unsigned long absize; 	int ret;  	absize = keylen + alignmask; 	buffer = kmalloc(absize, GFP_ATOMIC); 	if (!buffer) 		return -ENOMEM;  	alignbuffer = (u8 *)ALIGN((unsigned long)buffer, alignmask + 1); 	memcpy(alignbuffer, key, keylen); 	ret = cipher->setkey(tfm, alignbuffer, keylen); 	kzfree(buffer); 	return ret; }  static int skcipher_setkey(struct crypto_skcipher *tfm, const u8 *key, 			   unsigned int keylen) { 	struct skcipher_alg *cipher = crypto_skcipher_alg(tfm); 	unsigned long alignmask = crypto_skcipher_alignmask(tfm);  	if (keylen < cipher->min_keysize || keylen > cipher->max_keysize) { 		crypto_skcipher_set_flags(tfm, CRYPTO_TFM_RES_BAD_KEY_LEN); 		return -EINVAL; 	}  	if ((unsigned long)key & alignmask) 		return skcipher_setkey_unaligned(tfm, key, keylen);  	return cipher->setkey(tfm, key, keylen); }  static void crypto_skcipher_exit_tfm(struct crypto_tfm *tfm) { 	struct crypto_skcipher *skcipher = __crypto_skcipher_cast(tfm); static int crypto_skcipher_init_tfm(struct crypto_tfm *tfm) 	    tfm->__crt_alg->cra_type == &crypto_givcipher_type) 		return crypto_init_skcipher_ops_ablkcipher(tfm);  	skcipher->setkey = skcipher_setkey; 	skcipher->encrypt = alg->encrypt; 	skcipher->decrypt = alg->decrypt; 	skcipher->ivsize = alg->ivsize;

Patch:
patch: @@ -764,6 +764,44 @@ static int crypto_init_skcipher_ops_ablkcipher(struct crypto_tfm *tfm)
 	return 0;
 }
 
+static int skcipher_setkey_unaligned(struct crypto_skcipher *tfm,
+				     const u8 *key, unsigned int keylen)
+{
+	unsigned long alignmask = crypto_skcipher_alignmask(tfm);
+	struct skcipher_alg *cipher = crypto_skcipher_alg(tfm);
+	u8 *buffer, *alignbuffer;
+	unsigned long absize;
+	int ret;
+
+	absize = keylen + alignmask;
+	buffer = kmalloc(absize, GFP_ATOMIC);
+	if (!buffer)
+		return -ENOMEM;
+
+	alignbuffer = (u8 *)ALIGN((unsigned long)buffer, alignmask + 1);
+	memcpy(alignbuffer, key, keylen);
+	ret = cipher->setkey(tfm, alignbuffer, keylen);
+	kzfree(buffer);
+	return ret;
+}
+
+static int skcipher_setkey(struct crypto_skcipher *tfm, const u8 *key,
+			   unsigned int keylen)
+{
+	struct skcipher_alg *cipher = crypto_skcipher_alg(tfm);
+	unsigned long alignmask = crypto_skcipher_alignmask(tfm);
+
+	if (keylen < cipher->min_keysize || keylen > cipher->max_keysize) {
+		crypto_skcipher_set_flags(tfm, CRYPTO_TFM_RES_BAD_KEY_LEN);
+		return -EINVAL;
+	}
+
+	if ((unsigned long)key & alignmask)
+		return skcipher_setkey_unaligned(tfm, key, keylen);
+
+	return cipher->setkey(tfm, key, keylen);
+}
+
 static void crypto_skcipher_exit_tfm(struct crypto_tfm *tfm)
 {
 	struct crypto_skcipher *skcipher = __crypto_skcipher_cast(tfm);
@@ -784,7 +822,7 @@ static int crypto_skcipher_init_tfm(struct crypto_tfm *tfm)
 	    tfm->__crt_alg->cra_type == &crypto_givcipher_type)
 		return crypto_init_skcipher_ops_ablkcipher(tfm);
 
-	skcipher->setkey = alg->setkey;
+	skcipher->setkey = skcipher_setkey;
 	skcipher->encrypt = alg->encrypt;
 	skcipher->decrypt = alg->decrypt;
 	skcipher->ivsize = alg->ivsize;
path: crypto/skcipher.c