Taking ownership of the FPU in kernel mode disables preemption, and
this may result in excessive scheduling blackouts if the size of the
data being processed on the FPU is unbounded.

Given that taking and releasing the FPU is cheap these days on x86, we
can limit the impact of this issue easily for skcipher implementations,
by moving the FPU begin/end calls inside the skcipher walk processing
loop. Considering that skcipher walks operate on at most one page at a
time, doing so fully mitigates this issue.

This also permits the skcipher walk logic to use non-atomic kmalloc()
calls etc so we can change the 'atomic' bool argument in the calls to
skcipher_walk_virt() to false as well.

Signed-off-by: Ard Biesheuvel <a...@kernel.org>
---
 arch/x86/crypto/aesni-intel_glue.c | 73 +++++++++-----------
 1 file changed, 32 insertions(+), 41 deletions(-)

diff --git a/arch/x86/crypto/aesni-intel_glue.c 
b/arch/x86/crypto/aesni-intel_glue.c
index d96685457196..2144e54a6c89 100644
--- a/arch/x86/crypto/aesni-intel_glue.c
+++ b/arch/x86/crypto/aesni-intel_glue.c
@@ -283,16 +283,16 @@ static int ecb_encrypt(struct skcipher_request *req)
        unsigned int nbytes;
        int err;
 
-       err = skcipher_walk_virt(&walk, req, true);
+       err = skcipher_walk_virt(&walk, req, false);
 
-       kernel_fpu_begin();
        while ((nbytes = walk.nbytes)) {
+               kernel_fpu_begin();
                aesni_ecb_enc(ctx, walk.dst.virt.addr, walk.src.virt.addr,
                              nbytes & AES_BLOCK_MASK);
+               kernel_fpu_end();
                nbytes &= AES_BLOCK_SIZE - 1;
                err = skcipher_walk_done(&walk, nbytes);
        }
-       kernel_fpu_end();
 
        return err;
 }
@@ -305,16 +305,16 @@ static int ecb_decrypt(struct skcipher_request *req)
        unsigned int nbytes;
        int err;
 
-       err = skcipher_walk_virt(&walk, req, true);
+       err = skcipher_walk_virt(&walk, req, false);
 
-       kernel_fpu_begin();
        while ((nbytes = walk.nbytes)) {
+               kernel_fpu_begin();
                aesni_ecb_dec(ctx, walk.dst.virt.addr, walk.src.virt.addr,
                              nbytes & AES_BLOCK_MASK);
+               kernel_fpu_end();
                nbytes &= AES_BLOCK_SIZE - 1;
                err = skcipher_walk_done(&walk, nbytes);
        }
-       kernel_fpu_end();
 
        return err;
 }
@@ -327,16 +327,16 @@ static int cbc_encrypt(struct skcipher_request *req)
        unsigned int nbytes;
        int err;
 
-       err = skcipher_walk_virt(&walk, req, true);
+       err = skcipher_walk_virt(&walk, req, false);
 
-       kernel_fpu_begin();
        while ((nbytes = walk.nbytes)) {
+               kernel_fpu_begin();
                aesni_cbc_enc(ctx, walk.dst.virt.addr, walk.src.virt.addr,
                              nbytes & AES_BLOCK_MASK, walk.iv);
+               kernel_fpu_end();
                nbytes &= AES_BLOCK_SIZE - 1;
                err = skcipher_walk_done(&walk, nbytes);
        }
-       kernel_fpu_end();
 
        return err;
 }
@@ -349,16 +349,16 @@ static int cbc_decrypt(struct skcipher_request *req)
        unsigned int nbytes;
        int err;
 
-       err = skcipher_walk_virt(&walk, req, true);
+       err = skcipher_walk_virt(&walk, req, false);
 
-       kernel_fpu_begin();
        while ((nbytes = walk.nbytes)) {
+               kernel_fpu_begin();
                aesni_cbc_dec(ctx, walk.dst.virt.addr, walk.src.virt.addr,
                              nbytes & AES_BLOCK_MASK, walk.iv);
+               kernel_fpu_end();
                nbytes &= AES_BLOCK_SIZE - 1;
                err = skcipher_walk_done(&walk, nbytes);
        }
-       kernel_fpu_end();
 
        return err;
 }
@@ -476,21 +476,6 @@ static int cts_cbc_decrypt(struct skcipher_request *req)
 }
 
 #ifdef CONFIG_X86_64
-static void ctr_crypt_final(struct crypto_aes_ctx *ctx,
-                           struct skcipher_walk *walk)
-{
-       u8 *ctrblk = walk->iv;
-       u8 keystream[AES_BLOCK_SIZE];
-       u8 *src = walk->src.virt.addr;
-       u8 *dst = walk->dst.virt.addr;
-       unsigned int nbytes = walk->nbytes;
-
-       aesni_enc(ctx, keystream, ctrblk);
-       crypto_xor_cpy(dst, keystream, src, nbytes);
-
-       crypto_inc(ctrblk, AES_BLOCK_SIZE);
-}
-
 static void aesni_ctr_enc_avx_tfm(struct crypto_aes_ctx *ctx, u8 *out,
                              const u8 *in, unsigned int len, u8 *iv)
 {
@@ -512,27 +497,33 @@ static int ctr_crypt(struct skcipher_request *req)
 {
        struct crypto_skcipher *tfm = crypto_skcipher_reqtfm(req);
        struct crypto_aes_ctx *ctx = aes_ctx(crypto_skcipher_ctx(tfm));
+       u8 keystream[AES_BLOCK_SIZE];
        struct skcipher_walk walk;
        unsigned int nbytes;
        int err;
 
-       err = skcipher_walk_virt(&walk, req, true);
+       err = skcipher_walk_virt(&walk, req, false);
 
-       kernel_fpu_begin();
-       while ((nbytes = walk.nbytes) >= AES_BLOCK_SIZE) {
-               static_call(aesni_ctr_enc_tfm)(ctx, walk.dst.virt.addr,
-                                              walk.src.virt.addr,
-                                              nbytes & AES_BLOCK_MASK,
-                                              walk.iv);
-               nbytes &= AES_BLOCK_SIZE - 1;
+       while ((nbytes = walk.nbytes) > 0) {
+               kernel_fpu_begin();
+               if (nbytes & AES_BLOCK_MASK)
+                       static_call(aesni_ctr_enc_tfm)(ctx, walk.dst.virt.addr,
+                                                      walk.src.virt.addr,
+                                                      nbytes & AES_BLOCK_MASK,
+                                                      walk.iv);
+               nbytes &= ~AES_BLOCK_MASK;
+
+               if (walk.nbytes == walk.total && nbytes > 0) {
+                       aesni_enc(ctx, keystream, walk.iv);
+                       crypto_xor_cpy(walk.dst.virt.addr + walk.nbytes - 
nbytes,
+                                      walk.src.virt.addr + walk.nbytes - 
nbytes,
+                                      keystream, nbytes);
+                       crypto_inc(walk.iv, AES_BLOCK_SIZE);
+                       nbytes = 0;
+               }
+               kernel_fpu_end();
                err = skcipher_walk_done(&walk, nbytes);
        }
-       if (walk.nbytes) {
-               ctr_crypt_final(ctx, &walk);
-               err = skcipher_walk_done(&walk, 0);
-       }
-       kernel_fpu_end();
-
        return err;
 }
 
-- 
2.17.1

Reply via email to